Commit 66a390fe authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[#10,!3] Docgen sources are now documented.

parent 84a5a8af
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
...@@ -13,14 +19,19 @@ using namespace isc; ...@@ -13,14 +19,19 @@ using namespace isc;
using namespace isc::data; using namespace isc::data;
/// @brief API documentation generator
class DocGen { class DocGen {
public: public:
/// Output location of a file.
const string OUTPUT = "guide/api.xml"; const string OUTPUT = "guide/api.xml";
/// Controls whether to print out extra information.
bool verbose = false; bool verbose = false;
/// @brief Load JSON files that each contain description of an API command
///
/// @param files a vector with names of files.
void loadFiles(const vector<string>& files) { void loadFiles(const vector<string>& files) {
map <string, ElementPtr> commands; map <string, ElementPtr> commands;
...@@ -61,42 +72,57 @@ public: ...@@ -61,42 +72,57 @@ public:
<< " file(s)" << endl; << " file(s)" << endl;
} }
/// @brief checks if mandatory string parameter is specified
///
/// @param x a map that is being checked
/// @param name name of the string element expected to be there
/// @param fname name of the file (used in error reporting)
/// @throw Unexpected if missing, different type or empty
void requireString(const ElementPtr& x, const string& name, const string& fname) { void requireString(const ElementPtr& x, const string& name, const string& fname) {
if (!x->contains(name)) { if (!x->contains(name)) {
isc_throw(Unexpected, "Mandatory '" + name + " field missing while " isc_throw(Unexpected, "Mandatory '" + name + " field missing while "
"processing file " + fname); "processing file " + fname);
} }
if (x->get(name)->getType() != Element::string) { if (x->get(name)->getType() != Element::string) {
isc_throw(BadValue, "'" + name + " field is present, but is not a string" isc_throw(Unexpected, "'" + name + " field is present, but is not a string"
" in file " + fname); " in file " + fname);
} }
if (x->get(name)->stringValue().empty()) { if (x->get(name)->stringValue().empty()) {
isc_throw(BadValue, "'" + name + " field is present, is a string, but is " isc_throw(Unexpected, "'" + name + " field is present, is a string, but is "
"empty in file " + fname); "empty in file " + fname);
} }
} }
/// @brief checks if mandatory list parameter is specified
///
/// @param x a map that is being checked
/// @param name name of the list element expected to be there
/// @param fname name of the file (used in error reporting)
/// @throw Unexpected if missing, different type or empty
void requireList(const ElementPtr& x, const string& name, const string& fname) { void requireList(const ElementPtr& x, const string& name, const string& fname) {
if (!x->contains(name)) { if (!x->contains(name)) {
isc_throw(Unexpected, "Mandatory '" + name + " field missing while " isc_throw(Unexpected, "Mandatory '" + name + " field missing while "
"processing file " + fname); "processing file " + fname);
} }
if (x->get(name)->getType() != Element::list) { if (x->get(name)->getType() != Element::list) {
isc_throw(BadValue, "'" + name + " field is present, but is not a list " isc_throw(Unexpected, "'" + name + " field is present, but is not a list "
"in file " + fname); "in file " + fname);
} }
ConstElementPtr l = x->get(name); ConstElementPtr l = x->get(name);
if (l->size() == 0) { if (l->size() == 0) {
isc_throw(BadValue, "'" + name + " field is a list, but is empty in file " isc_throw(Unexpected, "'" + name + " field is a list, but is empty in file "
+ fname); + fname);
} }
// todo: check that every element is a string // todo: check that every element is a string
} }
/// @brief Checks that the essential parameters for each command are defined
///
/// @param fname name of the file the data was read from (printed if error is detected)
/// @param x a JSON map that contains content of the file
void sanityCheck(const string& fname, const ElementPtr& x) { void sanityCheck(const string& fname, const ElementPtr& x) {
requireString(x, "name", fname); requireString(x, "name", fname);
requireString(x, "brief", fname); requireString(x, "brief", fname);
...@@ -111,6 +137,9 @@ public: ...@@ -111,6 +137,9 @@ public:
//requireString(x, "resp-comment", fname); //requireString(x, "resp-comment", fname);
} }
/// @brief Writes ISC copyright note to the stream
///
/// @param f stream to write copyrights to
void generateCopyright(stringstream& f) { void generateCopyright(stringstream& f) {
f << "<!--" << endl; f << "<!--" << endl;
f << " - Copyright (C) 2018 Internet Systems Consortium, Inc. (\"ISC\")" << endl; f << " - Copyright (C) 2018 Internet Systems Consortium, Inc. (\"ISC\")" << endl;
...@@ -123,10 +152,23 @@ public: ...@@ -123,10 +152,23 @@ public:
f << "<!-- autogenerated using cmd_docgen. Do not edit by hand! -->" << endl; f << "<!-- autogenerated using cmd_docgen. Do not edit by hand! -->" << endl;
} }
/// @brief generates a link to command
///
/// @param f stream to write the generated link to
/// @param cmd name of the command
void generateCmdLink(stringstream& f, const string& cmd) { void generateCmdLink(stringstream& f, const string& cmd) {
f << "<command><link linkend=\"ref-" << cmd << "\">" << cmd << "</link></command>" << endl; f << "<command><link linkend=\"ref-" << cmd << "\">" << cmd
<< "</link></command>" << endl;
} }
/// @brief generates lists of all commands.
///
/// Currently there are several lists (or rather lists of lists). They all enumerate
/// commands, but each list serving a different purpose:
/// - list of commands supported by a daemon
/// - list of commands provided by a hook
///
/// @param f stream to write the generated lists to
void generateLists(stringstream& f) { void generateLists(stringstream& f) {
// Generate a list of all commands // Generate a list of all commands
f << " <para>Kea currently supports " << cmds_.size() << " commands:" << endl; f << " <para>Kea currently supports " << cmds_.size() << " commands:" << endl;
...@@ -163,8 +205,8 @@ public: ...@@ -163,8 +205,8 @@ public:
} }
} }
cout << "### " << all_daemons.size() << " daemon(s) detected." << endl; cout << all_daemons.size() << " daemon(s) detected." << endl;
cout << "### " << all_hooks.size() << " hook lib(s) detected." << endl; cout << all_hooks.size() << " hook lib(s) detected." << endl;
for (auto daemon : all_daemons) { for (auto daemon : all_daemons) {
f << "<para xml:id=\"commands-" << daemon << "\">" f << "<para xml:id=\"commands-" << daemon << "\">"
...@@ -214,6 +256,7 @@ public: ...@@ -214,6 +256,7 @@ public:
} }
/// @brief generates the whole API documentation
void generateOutput() { void generateOutput() {
stringstream f; stringstream f;
...@@ -244,6 +287,9 @@ public: ...@@ -244,6 +287,9 @@ public:
cout << "Output written to " << OUTPUT << endl; cout << "Output written to " << OUTPUT << endl;
} }
/// @brief generate sections for all commands
///
/// @param f stream to write the commands to
void generateCommands(stringstream& f){ void generateCommands(stringstream& f){
for (auto cmd : cmds_) { for (auto cmd : cmds_) {
...@@ -257,118 +303,139 @@ public: ...@@ -257,118 +303,139 @@ public:
} }
} }
void replaceAll(std::string& str, const std::string& from, const std::string& to) { /// @brief replace all strings
if(from.empty()) ///
return; /// @param str [in,out] this string will have some replacements
size_t start_pos = 0; /// @param from what to replace
while((start_pos = str.find(from, start_pos)) != std::string::npos) { /// @param to what to replace with
str.replace(start_pos, from.length(), to); void replaceAll(std::string& str, const std::string& from, const std::string& to) {
start_pos += to.length(); if(from.empty())
return;
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length();
}
} }
}
string escapeString(string txt) {
replaceAll(txt, "<", "&lt;");
replaceAll(txt, ">", "&gt;");
return (txt);
}
string standardResponseSyntax() { /// @brief escapes string to be safe for XML (docbook)
stringstream t; ///
/// @param txt string to be escaped
/// @return escaped string
string escapeString(string txt) {
t << "{" << endl replaceAll(txt, "<", "&lt;");
<< " \"result\": <integer>," << endl replaceAll(txt, ">", "&gt;");
<< " \"text\": <string>" << endl return (txt);
<< "}" << endl; }
return (t.str());
}
string standardResponseComment() { /// @brief generates standard description of command's response
stringstream t; ///
/// If a command doesn't have response syntax specified, we will
t << "Result is an integer representation of the status. Currently supported" /// assume it follows the usual syntax and provide the default description.
<< " statuses are:" << endl string standardResponseSyntax() {
<< "<itemizedlist>" << endl stringstream t;
<< " <listitem><para>0 - success</para></listitem>" << endl
<< " <listitem><para>1 - error</para></listitem>" << endl t << "{" << endl
<< " <listitem><para>2 - unsupported</para></listitem>" << endl << " \"result\": <integer>," << endl
<< " <listitem><para>3 - empty (command was completed successfully, but " << " \"text\": <string>" << endl
<< "no data was affected or returned)</para>" << "}" << endl;
<< "</listitem>" << endl return (t.str());
<< "</itemizedlist>" << endl; }
return (t.str());
}
void generateCommand(stringstream& f, const ElementPtr& cmd) { /// @brief generates standard description of command's comment
///
/// If a command doesn't have response syntax comment specified, we will
/// assume it follows the usual syntax and provide the default description.
string standardResponseComment() {
stringstream t;
t << "Result is an integer representation of the status. Currently supported"
<< " statuses are:" << endl
<< "<itemizedlist>" << endl
<< " <listitem><para>0 - success</para></listitem>" << endl
<< " <listitem><para>1 - error</para></listitem>" << endl
<< " <listitem><para>2 - unsupported</para></listitem>" << endl
<< " <listitem><para>3 - empty (command was completed successfully, but "
<< "no data was affected or returned)</para>"
<< "</listitem>" << endl
<< "</itemizedlist>" << endl;
return (t.str());
}
// command overview /// @brief generates command description
f << "<para xml:id=\"ref-" << cmd->get("name")->stringValue() << "\"><command>" ///
<< cmd->get("name")->stringValue() << "</command> - " /// @param f stream to write the description to
<< cmd->get("brief")->stringValue() << "</para>" << endl << endl; /// @param cmd pointer to JSON structure that describes the command
void generateCommand(stringstream& f, const ElementPtr& cmd) {
// command overview
f << "<para xml:id=\"ref-" << cmd->get("name")->stringValue() << "\"><command>"
<< cmd->get("name")->stringValue() << "</command> - "
<< cmd->get("brief")->stringValue() << "</para>" << endl << endl;
// command can be issued to the following daemons
f << "<para>Supported by: ";
ConstElementPtr daemons = cmd->get("support");
for (int i = 0; i < daemons->size(); i++) {
if (i) {
f << ", ";
}
// command can be issued to the following daemons f << "<command><link linkend=\"commands-" << daemons->get(i)->stringValue()
f << "<para>Supported by: "; << "\">" << daemons->get(i)->stringValue() << "</link></command>";
ConstElementPtr daemons = cmd->get("support"); }
for (int i = 0; i < daemons->size(); i++) { f << "</para>" << endl << endl;
if (i) {
f << ", "; // availability
f << "<para>Availability: " << cmd->get("avail")->stringValue();
auto hook = cmd->get("hook");
if (hook) {
f << " (<link linkend=\"commands-" << hook->stringValue() << "-lib\">"
<< hook->stringValue() << "</link>)";
} else {
f << " (built-in)";
} }
f << "<command><link linkend=\"commands-" << daemons->get(i)->stringValue() f << "</para>" << endl << endl;
<< "\">" << daemons->get(i)->stringValue() << "</link></command>";
} // description and examples
f << "</para>" << endl << endl; f << "<para>Description and examples: See <xref linkend=\"command-"
<< cmd->get("name")->stringValue() << "\"/></para>" << endl << endl;
// availability
f << "<para>Availability: " << cmd->get("avail")->stringValue(); // Command syntax:
auto hook = cmd->get("hook"); f << "<para>Command syntax:" << endl;
if (hook) { if (cmd->contains("cmd-syntax")) {
f << " (<link linkend=\"commands-" << hook->stringValue() << "-lib\">" f << " <screen>" << escapeString(cmd->get("cmd-syntax")->stringValue())
<< hook->stringValue() << "</link>)"; << "</screen>" << endl;
} else { } else {
f << " (built-in)"; f << " <screen>{" << endl
} << " \"command\": \"" << cmd->get("name")->stringValue() << "\"" << endl
<< "}</screen>" << endl;
f << "</para>" << endl << endl; }
if (cmd->contains("cmd-comment")) {
// description and examples f << cmd->get("cmd-comment")->stringValue();
f << "<para>Description and examples: See <xref linkend=\"command-" }
<< cmd->get("name")->stringValue() << "\"/></para>" << endl << endl; f << "</para>" << endl << endl;
// Command syntax:
f << "<para>Command syntax:" << endl;
if (cmd->contains("cmd-syntax")) {
f << " <screen>" << escapeString(cmd->get("cmd-syntax")->stringValue())
<< "</screen>" << endl;
} else {
f << " <screen>{" << endl
<< " \"command\": \"" << cmd->get("name")->stringValue() << "\"" << endl
<< "}</screen>" << endl;
}
if (cmd->contains("cmd-comment")) {
f << cmd->get("cmd-comment")->stringValue();
}
f << "</para>" << endl << endl;
// Response syntax // Response syntax
f << "<para>Response syntax:" << endl f << "<para>Response syntax:" << endl
<< " <screen>"; << " <screen>";
if (cmd->contains("resp-syntax")) { if (cmd->contains("resp-syntax")) {
f << escapeString(cmd->get("resp-syntax")->stringValue()); f << escapeString(cmd->get("resp-syntax")->stringValue());
} else { } else {
f << escapeString(standardResponseSyntax()); f << escapeString(standardResponseSyntax());
} }
f << "</screen>" << endl; f << "</screen>" << endl;
if (cmd->contains("resp-comment")) { if (cmd->contains("resp-comment")) {
f << cmd->get("resp-comment")->stringValue(); f << cmd->get("resp-comment")->stringValue();
} else { } else {
f << standardResponseComment(); f << standardResponseComment();
}
f << "</para>" << endl << endl;
} }
f << "</para>" << endl << endl;
}
map<string, ElementPtr> cmds_; map<string, ElementPtr> cmds_;
}; };
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment