diff --git a/src/BroDoc.cc b/src/BroDoc.cc deleted file mode 100644 index 1239c4efce..0000000000 --- a/src/BroDoc.cc +++ /dev/null @@ -1,667 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "BroDoc.h" -#include "BroDocObj.h" -#include "util.h" -#include "plugin/Manager.h" -#include "analyzer/Manager.h" -#include "analyzer/Component.h" -#include "file_analysis/Manager.h" -#include "broxygen/Manager.h" - -BroDoc::BroDoc(const std::string& rel, const std::string& abs) - { - size_t f_pos = abs.find_last_of('/'); - - if ( std::string::npos == f_pos ) - source_filename = abs; - else - source_filename = abs.substr(f_pos + 1); - - if ( rel[0] == '/' || rel[0] == '.' ) - { - // The Bro script isn't being loaded via BROPATH, so just use basename - // as the document title. - doc_title = source_filename; - } - else - { - // Keep the relative directory as part of the document title. - if ( rel.size() == 0 || rel[rel.size() - 1] == '/' ) - doc_title = rel + source_filename; - else - doc_title = rel + "/" + source_filename; - } - - downloadable_filename = source_filename; - -#if 0 - size_t ext_pos = downloadable_filename.find(".bif.bro"); - if ( std::string::npos != ext_pos ) - downloadable_filename.erase(ext_pos + 4); -#endif - - reST_filename = doc_title; - size_t ext_pos = reST_filename.find(".bro"); - - if ( std::string::npos == ext_pos ) - reST_filename += ".rst"; - else - reST_filename.replace(ext_pos, 4, ".rst"); - - reST_filename = doc_title.substr(0, ext_pos); - reST_filename += ".rst"; - - // Instead of re-creating the directory hierarchy based on related - // loads, just replace the directory separatories such that the reST - // output will all be placed in a flat directory (the working dir). - std::for_each(reST_filename.begin(), reST_filename.end(), replace_slash()); - - reST_file = fopen(reST_filename.c_str(), "w"); - - if ( ! reST_file ) - fprintf(stderr, "Failed to open %s\n", reST_filename.c_str()); - -#ifdef DOCDEBUG - fprintf(stdout, "Documenting absolute source: %s\n", abs.c_str()); - fprintf(stdout, "\trelative dir: %s\n", rel.c_str()); - fprintf(stdout, "\tdoc title: %s\n", doc_title.c_str()); - fprintf(stdout, "\tbro file: %s\n", source_filename.c_str()); - fprintf(stdout, "\trst file: %s\n", reST_filename.c_str()); -#endif - } - -BroDoc::~BroDoc() - { - if ( reST_file && fclose( reST_file ) ) - fprintf(stderr, "Failed to close %s\n", reST_filename.c_str()); - - FreeBroDocObjPtrList(all); - } - -void BroDoc::AddImport(const std::string& s) - { - /* - std::string lname(s); - // First strip any .bro extension. - size_t ext_pos = lname.find(".bro"); - if ( ext_pos != std::string::npos ) - lname = lname.substr(0, ext_pos); - - const char* full_filename = NULL; - const char* subpath = NULL; - - FILE* f = search_for_file(lname.c_str(), "bro", &full_filename, true, - &subpath); - - if ( f && full_filename && subpath ) - { - char* tmp = copy_string(full_filename); - char* filename = basename(tmp); - extern char* PACKAGE_LOADER; - - if ( streq(filename, PACKAGE_LOADER) ) - { - // link to the package's index - string pkg(subpath); - pkg += "/index"; - imports.push_back(pkg); - } - else - { - if ( subpath[0] == '/' || subpath[0] == '.' ) - { - // it's not a subpath of scripts/, so just add the name of it - // as it's given in the @load directive - imports.push_back(lname); - } - else - { - // combine the base file name of script in the @load directive - // with the subpath of BROPATH's scripts/ directory - string fname(subpath); - char* othertmp = copy_string(lname.c_str()); - fname.append("/").append(basename(othertmp)); - imports.push_back(fname); - delete [] othertmp; - } - } - - delete [] tmp; - } - - else - fprintf(stderr, "Failed to document '@load %s' in file: %s\n", - s.c_str(), reST_filename.c_str()); - - if ( f ) - fclose(f); - - delete [] full_filename; - delete [] subpath; - */ - } - -void BroDoc::SetPacketFilter(const std::string& s) - { - packet_filter = s; - size_t pos1 = s.find("{\n"); - size_t pos2 = s.find("}"); - - if ( pos1 != std::string::npos && pos2 != std::string::npos ) - packet_filter = s.substr(pos1 + 2, pos2 - 2); - - bool has_non_whitespace = false; - - for ( std::string::const_iterator it = packet_filter.begin(); - it != packet_filter.end(); ++it ) - { - if ( *it != ' ' && *it != '\t' && *it != '\n' && *it != '\r' ) - { - has_non_whitespace = true; - break; - } - } - - if ( ! has_non_whitespace ) - packet_filter.clear(); - } - -void BroDoc::WriteDocFile() const - { - WriteToDoc(reST_file, ".. Automatically generated. Do not edit.\n\n"); - - WriteToDoc(reST_file, ":tocdepth: 3\n\n"); - - WriteSectionHeading(reST_file, doc_title.c_str(), '='); - - WriteStringList(reST_file, ".. bro:namespace:: %s\n", modules); - - WriteToDoc(reST_file, "\n"); - - // WriteSectionHeading(reST_file, "Overview", '-'); - WriteStringList(reST_file, "%s\n", summary); - - WriteToDoc(reST_file, "\n"); - - if ( ! modules.empty() ) - { - WriteToDoc(reST_file, ":Namespace%s: ", (modules.size() > 1 ? "s" : "")); - // WriteStringList(reST_file, ":bro:namespace:`%s`", modules); - WriteStringList(reST_file, "``%s``, ", "``%s``", modules); - WriteToDoc(reST_file, "\n"); - } - - if ( ! imports.empty() ) - { - WriteToDoc(reST_file, ":Imports: "); - std::list::const_iterator it; - for ( it = imports.begin(); it != imports.end(); ++it ) - { - if ( it != imports.begin() ) - WriteToDoc(reST_file, ", "); - - string pretty(*it); - size_t pos = pretty.find("/index"); - if ( pos != std::string::npos && pos + 6 == pretty.size() ) - pretty = pretty.substr(0, pos); - WriteToDoc(reST_file, ":doc:`%s `", pretty.c_str(), it->c_str()); - } - WriteToDoc(reST_file, "\n"); - } - - WriteToDoc(reST_file, ":Source File: :download:`%s`\n", - downloadable_filename.c_str()); - - WriteToDoc(reST_file, "\n"); - - WriteInterface("Summary", '~', '#', true, true); - - if ( ! notices.empty() ) - WriteBroDocObjList(reST_file, notices, "Notices", '#'); - - if ( port_analysis.size() || packet_filter.size() ) - WriteSectionHeading(reST_file, "Configuration Changes", '#'); - - if ( ! port_analysis.empty() ) - { - WriteSectionHeading(reST_file, "Port Analysis", '^'); - WriteToDoc(reST_file, "Loading this script makes the following changes to " - ":bro:see:`dpd_config`.\n\n"); - WriteStringList(reST_file, "%s, ", "%s", port_analysis); - } - - if ( ! packet_filter.empty() ) - { - WriteSectionHeading(reST_file, "Packet Filter", '^'); - WriteToDoc(reST_file, "Loading this script makes the following changes to " - ":bro:see:`capture_filters`.\n\n"); - WriteToDoc(reST_file, "Filters added::\n\n"); - WriteToDoc(reST_file, "%s\n", packet_filter.c_str()); - } - - WriteInterface("Detailed Interface", '~', '#', true, false); - -#if 0 // Disabled for now. - BroDocObjList::const_iterator it; - bool hasPrivateIdentifiers = false; - - for ( it = all.begin(); it != all.end(); ++it ) - { - if ( ! IsPublicAPI(*it) ) - { - hasPrivateIdentifiers = true; - break; - } - } - - if ( hasPrivateIdentifiers ) - WriteInterface("Private Interface", '~', '#', false, false); -#endif - } - -void BroDoc::WriteInterface(const char* heading, char underline, - char sub, bool isPublic, bool isShort) const - { - WriteSectionHeading(reST_file, heading, underline); - WriteBroDocObjList(reST_file, options, isPublic, "Options", sub, isShort); - WriteBroDocObjList(reST_file, constants, isPublic, "Constants", sub, isShort); - WriteBroDocObjList(reST_file, state_vars, isPublic, "State Variables", sub, isShort); - WriteBroDocObjList(reST_file, types, isPublic, "Types", sub, isShort); - WriteBroDocObjList(reST_file, events, isPublic, "Events", sub, isShort); - WriteBroDocObjList(reST_file, hooks, isPublic, "Hooks", sub, isShort); - WriteBroDocObjList(reST_file, functions, isPublic, "Functions", sub, isShort); - WriteBroDocObjList(reST_file, redefs, isPublic, "Redefinitions", sub, isShort); - } - -void BroDoc::WriteStringList(FILE* f, const char* format, const char* last_format, - const std::list& l) - { - if ( l.empty() ) - { - WriteToDoc(f, "\n"); - return; - } - - std::list::const_iterator it; - std::list::const_iterator last = l.end(); - last--; - - for ( it = l.begin(); it != last; ++it ) - WriteToDoc(f, format, it->c_str()); - - WriteToDoc(f, last_format, last->c_str()); - } - -void BroDoc::WriteBroDocObjTable(FILE* f, const BroDocObjList& l) - { - int max_id_col = 0; - int max_com_col = 0; - BroDocObjList::const_iterator it; - - for ( it = l.begin(); it != l.end(); ++it ) - { - int c = (*it)->ColumnSize(); - - if ( c > max_id_col ) - max_id_col = c; - - c = (*it)->LongestShortDescLen(); - - if ( c > max_com_col ) - max_com_col = c; - } - - // Start table. - WriteRepeatedChar(f, '=', max_id_col); - WriteToDoc(f, " "); - - if ( max_com_col == 0 ) - WriteToDoc(f, "="); - else - WriteRepeatedChar(f, '=', max_com_col); - - WriteToDoc(f, "\n"); - - for ( it = l.begin(); it != l.end(); ++it ) - { - if ( it != l.begin() ) - WriteToDoc(f, "\n\n"); - (*it)->WriteReSTCompact(f, max_id_col); - } - - // End table. - WriteToDoc(f, "\n"); - WriteRepeatedChar(f, '=', max_id_col); - WriteToDoc(f, " "); - - if ( max_com_col == 0 ) - WriteToDoc(f, "="); - else - WriteRepeatedChar(f, '=', max_com_col); - - WriteToDoc(f, "\n\n"); - } - -void BroDoc::WriteBroDocObjList(FILE* f, const BroDocObjList& l, bool wantPublic, - const char* heading, char underline, bool isShort) - { - if ( l.empty() ) - return; - - BroDocObjList::const_iterator it; - bool (*f_ptr)(const BroDocObj* o) = 0; - - if ( wantPublic ) - f_ptr = IsPublicAPI; - else - f_ptr = IsPrivateAPI; - - it = std::find_if(l.begin(), l.end(), f_ptr); - - if ( it == l.end() ) - return; - - WriteSectionHeading(f, heading, underline); - - BroDocObjList filtered_list; - - while ( it != l.end() ) - { - filtered_list.push_back(*it); - it = find_if(++it, l.end(), f_ptr); - } - - if ( isShort ) - WriteBroDocObjTable(f, filtered_list); - else - WriteBroDocObjList(f, filtered_list); - } - -void BroDoc::WriteBroDocObjList(FILE* f, const BroDocObjMap& m, bool wantPublic, - const char* heading, char underline, bool isShort) - { - BroDocObjMap::const_iterator it; - BroDocObjList l; - - for ( it = m.begin(); it != m.end(); ++it ) - l.push_back(it->second); - - WriteBroDocObjList(f, l, wantPublic, heading, underline, isShort); - } - -void BroDoc::WriteBroDocObjList(FILE* f, const BroDocObjList& l, const char* heading, - char underline) - { - WriteSectionHeading(f, heading, underline); - WriteBroDocObjList(f, l); - } - -void BroDoc::WriteBroDocObjList(FILE* f, const BroDocObjList& l) - { - for ( BroDocObjList::const_iterator it = l.begin(); it != l.end(); ++it ) - (*it)->WriteReST(f); - } - -void BroDoc::WriteBroDocObjList(FILE* f, const BroDocObjMap& m, const char* heading, - char underline) - { - BroDocObjMap::const_iterator it; - BroDocObjList l; - - for ( it = m.begin(); it != m.end(); ++it ) - l.push_back(it->second); - - WriteBroDocObjList(f, l, heading, underline); - } - -void BroDoc::WriteToDoc(FILE* f, const char* format, ...) - { - va_list argp; - va_start(argp, format); - vfprintf(f, format, argp); - va_end(argp); - } - -void BroDoc::WriteSectionHeading(FILE* f, const char* heading, char underline) - { - WriteToDoc(f, "%s\n", heading); - WriteRepeatedChar(f, underline, strlen(heading)); - WriteToDoc(f, "\n"); - } - -void BroDoc::WriteRepeatedChar(FILE* f, char c, size_t n) - { - for ( size_t i = 0; i < n; ++i ) - WriteToDoc(f, "%c", c); - } - -void BroDoc::FreeBroDocObjPtrList(BroDocObjList& l) - { - for ( BroDocObjList::const_iterator it = l.begin(); it != l.end(); ++it ) - delete *it; - - l.clear(); - } - -void BroDoc::AddFunction(BroDocObj* o) - { - BroDocObjMap::const_iterator it = functions.find(o->Name()); - if ( it == functions.end() ) - { - functions[o->Name()] = o; - all.push_back(o); - } - else - functions[o->Name()]->Combine(o); - } - -static void WritePluginSectionHeading(FILE* f, const plugin::Plugin* p) - { - string name = p->Name(); - - fprintf(f, "%s\n", name.c_str()); - for ( size_t i = 0; i < name.size(); ++i ) - fprintf(f, "-"); - fprintf(f, "\n\n"); - - fprintf(f, "%s\n\n", p->Description()); - } - -static void WriteAnalyzerComponent(FILE* f, const analyzer::Component* c) - { - EnumType* atag = analyzer_mgr->GetTagEnumType(); - string tag = fmt("ANALYZER_%s", c->CanonicalName()); - - if ( atag->Lookup("Analyzer", tag.c_str()) < 0 ) - reporter->InternalError("missing analyzer tag for %s", tag.c_str()); - - fprintf(f, ":bro:enum:`Analyzer::%s`\n\n", tag.c_str()); - } - -static void WriteAnalyzerComponent(FILE* f, const file_analysis::Component* c) - { - EnumType* atag = file_mgr->GetTagEnumType(); - string tag = fmt("ANALYZER_%s", c->CanonicalName()); - - if ( atag->Lookup("Files", tag.c_str()) < 0 ) - reporter->InternalError("missing analyzer tag for %s", tag.c_str()); - - fprintf(f, ":bro:enum:`Files::%s`\n\n", tag.c_str()); - } - -static void WritePluginComponents(FILE* f, const plugin::Plugin* p) - { - plugin::Plugin::component_list components = p->Components(); - plugin::Plugin::component_list::const_iterator it; - - fprintf(f, "Components\n"); - fprintf(f, "++++++++++\n\n"); - - for ( it = components.begin(); it != components.end(); ++it ) - { - switch ( (*it)->Type() ) { - case plugin::component::ANALYZER: - { - const analyzer::Component* c = - dynamic_cast(*it); - - if ( c ) - WriteAnalyzerComponent(f, c); - else - reporter->InternalError("component type mismatch"); - } - break; - - case plugin::component::FILE_ANALYZER: - { - const file_analysis::Component* c = - dynamic_cast(*it); - - if ( c ) - WriteAnalyzerComponent(f, c); - else - reporter->InternalError("component type mismatch"); - } - break; - - case plugin::component::READER: - reporter->InternalError("docs for READER component unimplemented"); - - case plugin::component::WRITER: - reporter->InternalError("docs for WRITER component unimplemented"); - - default: - reporter->InternalError("docs for unknown component unimplemented"); - } - } - } - -static void WritePluginBifItems(FILE* f, const plugin::Plugin* p, - plugin::BifItem::Type t, const string& heading) - { - plugin::Plugin::bif_item_list bifitems = p->BifItems(); - plugin::Plugin::bif_item_list::iterator it = bifitems.begin(); - - while ( it != bifitems.end() ) - { - if ( it->GetType() != t ) - it = bifitems.erase(it); - else - ++it; - } - - if ( bifitems.empty() ) - return; - - fprintf(f, "%s\n", heading.c_str()); - for ( size_t i = 0; i < heading.size(); ++i ) - fprintf(f, "+"); - fprintf(f, "\n\n"); - - for ( it = bifitems.begin(); it != bifitems.end(); ++it ) - { - broxygen::IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc( - it->GetID()); - - if ( doc ) - fprintf(f, "%s\n\n", doc->ReStructuredText().c_str()); - else - reporter->InternalWarning("Broxygen ID lookup failed: %s\n", - it->GetID()); - } - } - -static void WriteAnalyzerTagDefn(FILE* f, const string& module) - { - string tag_id = module + "::Tag"; - - broxygen::IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc(tag_id); - - if ( ! doc ) - reporter->InternalError("Broxygen failed analyzer tag lookup: %s", - tag_id.c_str()); - - fprintf(f, "%s\n", doc->ReStructuredText().c_str()); - } - -static bool ComponentsMatch(const plugin::Plugin* p, plugin::component::Type t, - bool match_empty = false) - { - plugin::Plugin::component_list components = p->Components(); - plugin::Plugin::component_list::const_iterator it; - - if ( components.empty() ) - return match_empty; - - for ( it = components.begin(); it != components.end(); ++it ) - if ( (*it)->Type() != t ) - return false; - - return true; - } - -void CreateProtoAnalyzerDoc(FILE* f) - { - fprintf(f, "Protocol Analyzers\n"); - fprintf(f, "==================\n\n"); - fprintf(f, ".. contents::\n"); - fprintf(f, " :depth: 2\n\n"); - - WriteAnalyzerTagDefn(f, "Analyzer"); - - plugin::Manager::plugin_list plugins = plugin_mgr->Plugins(); - plugin::Manager::plugin_list::const_iterator it; - - for ( it = plugins.begin(); it != plugins.end(); ++it ) - { - if ( ! ComponentsMatch(*it, plugin::component::ANALYZER, true) ) - continue; - - WritePluginSectionHeading(f, *it); - WritePluginComponents(f, *it); - WritePluginBifItems(f, *it, plugin::BifItem::CONSTANT, - "Options/Constants"); - WritePluginBifItems(f, *it, plugin::BifItem::GLOBAL, "Globals"); - WritePluginBifItems(f, *it, plugin::BifItem::TYPE, "Types"); - WritePluginBifItems(f, *it, plugin::BifItem::EVENT, "Events"); - WritePluginBifItems(f, *it, plugin::BifItem::FUNCTION, "Functions"); - } - - fclose(f); - } - -void CreateFileAnalyzerDoc(FILE* f) - { - fprintf(f, "File Analyzers\n"); - fprintf(f, "==============\n\n"); - fprintf(f, ".. contents::\n"); - fprintf(f, " :depth: 2\n\n"); - - WriteAnalyzerTagDefn(f, "Files"); - - plugin::Manager::plugin_list plugins = plugin_mgr->Plugins(); - plugin::Manager::plugin_list::const_iterator it; - - for ( it = plugins.begin(); it != plugins.end(); ++it ) - { - if ( ! ComponentsMatch(*it, plugin::component::FILE_ANALYZER) ) - continue; - - WritePluginSectionHeading(f, *it); - WritePluginComponents(f, *it); - WritePluginBifItems(f, *it, plugin::BifItem::CONSTANT, - "Options/Constants"); - WritePluginBifItems(f, *it, plugin::BifItem::GLOBAL, "Globals"); - WritePluginBifItems(f, *it, plugin::BifItem::TYPE, "Types"); - WritePluginBifItems(f, *it, plugin::BifItem::EVENT, "Events"); - WritePluginBifItems(f, *it, plugin::BifItem::FUNCTION, "Functions"); - } - - fclose(f); - } diff --git a/src/BroDoc.h b/src/BroDoc.h deleted file mode 100644 index 926713d01e..0000000000 --- a/src/BroDoc.h +++ /dev/null @@ -1,422 +0,0 @@ -#ifndef brodoc_h -#define brodoc_h - -#include -#include -#include -#include - -#include "BroDocObj.h" - -/** - * This class is used to gather all data relevant to the automatic generation - * of a reStructuredText (reST) document from a given Bro script. - */ -class BroDoc { -public: - /** - * BroDoc constructor - * Given a Bro script, opens new file in the current working directory - * that will contain reST documentation generated from the parsing - * of the Bro script. The new reST file will be named similar to - * the filename of the Bro script that generates it, except any - * ".bro" file extension is stripped and ".rst" takes it place. - * If the filename doesn't end in ".bro", then ".rst" is just appended. - * Any '/' characters in the reST file name that result from choice of - * the 'rel' parameter are replaced with '^'. - * @param rel A string representing a subpath of the root Bro script - * source/install directory in which the source file is located. - * It can also be an absolute path, but then the parameter is - * ignored and the document title is just derived from file name - * @param abs The absolute path to the Bro script for which to generate - * documentation. - */ - BroDoc(const std::string& rel, const std::string& abs); - - /** - * BroDoc destructor - * Closes the file that was opened by the constructor and frees up - * memory taken by BroDocObj objects. - */ - virtual ~BroDoc(); - - /** - * Write out full reST documentation for the Bro script that was parsed. - * BroDoc's default implementation of this function will care - * about whether declarations made in the Bro script are part of - * the public versus private interface (whether things are declared in - * the export section). - */ - virtual void WriteDocFile() const; - - /** - * Schedules some summarizing text to be output directly into the reST doc. - * This should be called whenever the scanner sees a line in the Bro script - * starting with "##!" - * @param s The summary text to add to the reST doc. - */ - void AddSummary(const std::string& s) { summary.push_back(s); } - - /** - * Schedules an import (@load) to be documented. - * If the script being loaded has a .bro suffix, it is internally stripped. - * This should be called whenever the scanner sees an @load. - * @param s The name of the imported script. - */ - void AddImport(const std::string& s); - - /** - * Schedules a namespace (module) to be documented. - * This should be called whenever the parser sees a TOK_MODULE. - * @param s The namespace (module) identifier's name. - */ - void AddModule(const std::string& s) { modules.push_back(s); } - - /** - * Sets the way the script changes the "capture_filters" table. - * This is determined by the scanner checking for changes to - * the "capture_filters" table after each of Bro's input scripts - * (given as command line arguments to Bro) are finished being parsed. - * @param s The value "capture_filters" as given by TableVal::Describe() - */ - void SetPacketFilter(const std::string& s); - - /** - * Schedules documentation of a script option. An option is - * defined as any variable in the script that is declared 'const' - * and has the '&redef' attribute. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script option and - * also any associated comments about it. - */ - void AddOption(const BroDocObj* o) - { - options.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a script constant. An option is - * defined as any variable in the script that is declared 'const' - * and does *not* have the '&redef' attribute. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script constant and - * also any associated comments about it. - */ - void AddConstant(const BroDocObj* o) - { - constants.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a script state variable. A state variable - * is defined as any variable in the script that is declared 'global' - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script state variable - * and also any associated comments about it. - */ - void AddStateVar(const BroDocObj* o) - { - state_vars.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a type declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script option and - * also any associated comments about it. - */ - void AddType(const BroDocObj* o) - { - types.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a Notice (enum redef) declared by script - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the Notice and also - * any associated comments about it. - */ - void AddNotice(const BroDocObj* o) - { - notices.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of an event declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script event and - * also any associated comments about it. - */ - void AddEvent(const BroDocObj* o) - { - events.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of an event handler declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script event handler and - * also any associated comments about it. - */ - void AddEventHandler(const BroDocObj* o) - { - event_handlers.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a hook declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script hook and - * also any associated comments about it. - */ - void AddHook(const BroDocObj* o) - { - hooks.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a hook handler declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script hook handler and - * also any associated comments about it. - */ - void AddHookHandler(const BroDocObj* o) - { - hook_handlers.push_back(o); - all.push_back(o); - } - - /** - * Schedules documentation of a function declared by the script. - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script function and - * also any associated comments about it. - */ - void AddFunction(BroDocObj* o); - - /** - * Schedules documentation of a redef done by the script - * @param o A pointer to a BroDocObj which contains the internal - * Bro language representation of the script identifier - * that was redefined and also any associated comments. - */ - void AddRedef(const BroDocObj* o) - { - redefs.push_back(o); - all.push_back(o); - } - - /** - * Gets the name of the Bro script source file for which reST - * documentation is being generated. - * @return A char* to the start of the source file's name. - */ - const char* GetSourceFileName() const - { - return source_filename.c_str(); - } - - /** - * Gets the name of the generated reST documentation file. - * @return A char* to the start of the generated reST file's name. - */ - const char* GetOutputFileName() const - { - return reST_filename.c_str(); - } - - typedef std::list BroDocObjList; - typedef std::map BroDocObjMap; - - /** - * Writes out a table of BroDocObj's to the reST document - * @param f The file to write to. - * @param l A list of BroDocObj pointers - */ - static void WriteBroDocObjTable(FILE* f, const BroDocObjList& l); - - /** - * Writes out given number of characters to reST document - * @param f The file to write to. - * @param c the character to write - * @param n the number of characters to write - */ - static void WriteRepeatedChar(FILE* f, char c, size_t n); - - /** - * A wrapper to fprintf() that always uses the reST document - * for the FILE* argument. - * @param f The file to write to. - * @param format A printf style format string. - */ - static void WriteToDoc(FILE* f, const char* format, ...); - - /** - * Writes out a list of strings to the reST document. - * If the list is empty, prints a newline character. - * @param f The file to write to. - * @param format A printf style format string for elements of the list - * except for the last one in the list - * @param last_format A printf style format string to use for the last - * element of the list - * @param l A reference to a list of strings - */ - static void WriteStringList(FILE* f, const char* format, const char* last_format, - const std::list& l); - - /** - * @see WriteStringList(FILE* f, const char*, const char*, - * const std::list&>) - */ - static void WriteStringList(FILE* f, const char* format, - const std::list& l){ - WriteStringList(f, format, format, l); - } - - /** - * Writes out a list of BroDocObj objects to the reST document - * @param f The file to write to. - * @param l A list of BroDocObj pointers - * @param wantPublic If true, filter out objects that are not declared - * in the global scope. If false, filter out those that are in - * the global scope. - * @param heading The title of the section to create in the reST doc. - * @param underline The character to use to underline the reST - * section heading. - * @param isShort Whether to write the full documentation or a "short" - * version (a single sentence) - */ - static void WriteBroDocObjList(FILE* f, const BroDocObjList& l, bool wantPublic, - const char* heading, char underline, - bool isShort); - - /** - * Wraps the BroDocObjMap into a BroDocObjList and the writes that list - * to the reST document - * @see WriteBroDocObjList(FILE* f, const BroDocObjList&, bool, const char*, char, - bool) - */ - static void WriteBroDocObjList(FILE* f, const BroDocObjMap& m, bool wantPublic, - const char* heading, char underline, - bool isShort); - - /** - * Writes out a list of BroDocObj objects to the reST document - * @param l A list of BroDocObj pointers - * @param heading The title of the section to create in the reST doc. - * @param underline The character to use to underline the reST - * section heading. - */ - static void WriteBroDocObjList(FILE* f, const BroDocObjList& l, const char* heading, - char underline); - - /** - * Writes out a list of BroDocObj objects to the reST document - * @param l A list of BroDocObj pointers - */ - static void WriteBroDocObjList(FILE* f, const BroDocObjList& l); - - /** - * Wraps the BroDocObjMap into a BroDocObjList and the writes that list - * to the reST document - * @see WriteBroDocObjList(FILE* f, const BroDocObjList&, const char*, char) - */ - static void WriteBroDocObjList(FILE* f, const BroDocObjMap& m, const char* heading, - char underline); - - /** - * Writes out a reST section heading - * @param f The file to write to. - * @param heading The title of the heading to create - * @param underline The character to use to underline the section title - * within the reST document - */ - static void WriteSectionHeading(FILE* f, const char* heading, char underline); - -private: - FILE* reST_file; - std::string reST_filename; - std::string source_filename; // points to the basename of source file - std::string downloadable_filename; // file that will be linked for download - std::string doc_title; - std::string packet_filter; - - std::list modules; - std::list summary; - std::list imports; - std::list port_analysis; - - BroDocObjList options; - BroDocObjList constants; - BroDocObjList state_vars; - BroDocObjList types; - BroDocObjList notices; - BroDocObjList events; - BroDocObjList event_handlers; - BroDocObjList hooks; - BroDocObjList hook_handlers; - BroDocObjMap functions; - BroDocObjList redefs; - - BroDocObjList all; - - /** - * Writes out the reST for either the script's public or private interface - * @param heading The title of the interfaces section heading - * @param underline The underline character to use for the interface - * section - * @param subunderline The underline character to use for interface - * sub-sections - * @param isPublic Whether to write out the public or private script - * interface - * @param isShort Whether to write out the full documentation or a "short" - * description (a single sentence) - */ - void WriteInterface(const char* heading, char underline, char subunderline, - bool isPublic, bool isShort) const; - - /** - * Frees memory allocated to BroDocObj's objects in a given list. - * @param a reference to a list of BroDocObj pointers - */ - void FreeBroDocObjPtrList(BroDocObjList& l); - - static bool IsPublicAPI(const BroDocObj* o) - { - return o->IsPublicAPI(); - } - - static bool IsPrivateAPI(const BroDocObj* o) - { - return ! o->IsPublicAPI(); - } - - struct replace_slash { - void operator()(char& c) - { - if ( c == '/' ) c = '^'; - } - }; -}; - -/** - * Writes out plugin index documentation for all analyzer plugins. - * @param f an open file stream to write docs into. - */ -void CreateProtoAnalyzerDoc(FILE* f); - -/** - * Writes out plugin index documentation for all file analyzer plugins. - * @param f an open file stream to write docs into. - */ -void CreateFileAnalyzerDoc(FILE* f); - -#endif diff --git a/src/BroDocObj.cc b/src/BroDocObj.cc deleted file mode 100644 index 063bdb1941..0000000000 --- a/src/BroDocObj.cc +++ /dev/null @@ -1,195 +0,0 @@ -#include -#include -#include -#include "ID.h" -#include "BroDocObj.h" - -map doc_ids = map(); - -BroDocObj* BroDocObj::last = 0; - -BroDocObj::BroDocObj(const ID* id, std::list*& reST, - bool is_fake) - { - last = this; - broID = id; - reST_doc_strings = reST; - reST = 0; - is_fake_id = is_fake; - use_role = 0; - FormulateShortDesc(); - doc_ids[id->Name()] = this; - } - -BroDocObj::~BroDocObj() - { - if ( reST_doc_strings ) - delete reST_doc_strings; - - if ( is_fake_id ) - delete broID; - } - -void BroDocObj::WriteReSTCompact(FILE* file, int max_col) const - { - ODesc desc; - desc.SetQuotes(1); - broID->DescribeReSTShort(&desc); - - fprintf(file, "%s", desc.Description()); - - std::list::const_iterator it; - - for ( it = short_desc.begin(); it != short_desc.end(); ++it ) - { - int start_col; - - if ( it == short_desc.begin() ) - start_col = max_col - desc.Len() + 1; - else - { - start_col = max_col + 1; - fprintf(file, "\n"); - } - - for ( int i = 0; i < start_col; ++i ) - fprintf(file, " "); - - fprintf(file, "%s", it->c_str()); - } - } - -int BroDocObj::LongestShortDescLen() const - { - size_t max = 0; - - std::list::const_iterator it; - - for ( it = short_desc.begin(); it != short_desc.end(); ++it ) - { - if ( it->size() > max ) - max = it->size(); - } - - return max; - } - -static size_t end_of_first_sentence(string s) - { - size_t rval = 0; - - while ( (rval = s.find_first_of('.', rval)) != string::npos ) - { - if ( rval == s.size() - 1 ) - // Period is at end of string. - return rval; - - if ( isspace(s[rval + 1]) ) - // Period has a space after it. - return rval; - - // Period has some non-space character after it, keep looking. - ++rval; - } - - return rval; - } - -void BroDocObj::FormulateShortDesc() - { - if ( ! reST_doc_strings ) - return; - - short_desc.clear(); - std::list::const_iterator it; - - for ( it = reST_doc_strings->begin(); - it != reST_doc_strings->end(); ++it ) - { - // The short description stops at the first sentence or the - // first empty comment. - size_t end = end_of_first_sentence(*it); - - if ( end == string::npos ) - { - std::string::const_iterator s; - bool empty = true; - - for ( s = it->begin(); s != it->end(); ++s ) - { - if ( *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r' ) - { - empty = false; - short_desc.push_back(*it); - break; - } - } - - if ( empty ) - break; - } - else - { - short_desc.push_back(it->substr(0, end + 1)); - break; - } - } - } - -void BroDocObj::WriteReST(FILE* file) const - { - int indent_spaces = 3; - ODesc desc; - desc.SetIndentSpaces(indent_spaces); - desc.SetQuotes(1); - - broID->DescribeReST(&desc, use_role); - - fprintf(file, "%s", desc.Description()); - - if ( HasDocumentation() ) - { - fprintf(file, "\n"); - std::list::const_iterator it; - - for ( it = reST_doc_strings->begin(); - it != reST_doc_strings->end(); ++it) - { - for ( int i = 0; i < indent_spaces; ++i ) - fprintf(file, " "); - - fprintf(file, "%s\n", it->c_str()); - } - } - - fprintf(file, "\n"); - } - -int BroDocObj::ColumnSize() const - { - ODesc desc; - desc.SetQuotes(1); - broID->DescribeReSTShort(&desc); - return desc.Len(); - } - -bool BroDocObj::IsPublicAPI() const - { - return (broID->Scope() == SCOPE_GLOBAL) || - (broID->Scope() == SCOPE_MODULE && broID->IsExport()); - } - -void BroDocObj::Combine(const BroDocObj* o) - { - if ( o->reST_doc_strings ) - { - if ( ! reST_doc_strings ) - reST_doc_strings = new std::list(); - - reST_doc_strings->splice(reST_doc_strings->end(), - *(o->reST_doc_strings)); - } - - delete o; - FormulateShortDesc(); - } diff --git a/src/BroDocObj.h b/src/BroDocObj.h deleted file mode 100644 index ab42dc3c94..0000000000 --- a/src/BroDocObj.h +++ /dev/null @@ -1,143 +0,0 @@ -#ifndef brodocobj_h -#define brodocobj_h - -#include -#include -#include -#include - -#include "ID.h" - -/** - * This class wraps a Bro script identifier, providing methods relevant - * to automatic generation of reStructuredText (reST) documentation for it. - */ -class BroDocObj { -public: - /** - * BroDocObj constructor - * @param id a pointer to an identifier that is to be documented - * @param reST a reference to a pointer of a list of strings that - * represent the reST documentation for the ID. The pointer - * will be set to 0 after this constructor finishes. - * @param is_fake whether the ID* is a dummy just for doc purposes - */ - BroDocObj(const ID* id, std::list*& reST, - bool is_fake = false); - - /** - * BroDocObj destructor - * Deallocates the memory associated with the list of reST strings - */ - ~BroDocObj(); - - /** - * Writes the reST representation of this object which includes - * 1) a reST friendly description of the ID - * 2) "##" or "##<" stylized comments. - * Anything after these style of comments is inserted as-is into - * the reST document. - * @param file The (already opened) file to write the reST to. - */ - void WriteReST(FILE* file) const; - - /** - * Writes a compact version of the ID and associated documentation - * for insertion into a table. - * @param file The (already opened) file to write the reST to. - * @param max_col The maximum length of the first table column - */ - void WriteReSTCompact(FILE* file, int max_col) const; - - /** - * @return the column size required by the reST representation of the ID - */ - int ColumnSize() const; - - /** - * Check whether this documentation is part of the public API. In - * other words, this means that the identifier is declared as part of - * the global scope (has GLOBAL namespace or is exported from another - * namespace). - * @return true if the identifier is part of the script's public API - */ - bool IsPublicAPI() const; - - /** - * Return whether this object has documentation (## comments) - * @return true if the ID has comments associated with it - */ - bool HasDocumentation() const - { - return reST_doc_strings && reST_doc_strings->size() > 0; - } - - /** - * @return whether this object will use reST role (T) or directive (F) - * notation for the wrapped identifier. Roles are usually used - * for cross-referencing. - */ - bool UseRole() const { return use_role; } - - /** - * @param b whether this object will use reST role (T) or directive (F) - * notation for the wrapped identifier. Roles are usually used - * for cross-referencing. - */ - void SetRole(bool b) { use_role = b; } - - /** - * Append any reST documentation strings in a given BroDocObj to this - * object's list and then delete the given BroDocObj - * @param o a pointer to a BroDocObj to subsume - */ - void Combine(const BroDocObj* o); - - /** - * @return the name of the wrapped identifier - */ - const char* Name() const { return broID->Name(); } - - /** - * @return the longest string element of the short description's list of - * strings - */ - int LongestShortDescLen() const; - - /** - * Adds a reST documentation string to this BroDocObj's list. - * @param s the documentation string to append. - */ - void AddDocString(const std::string& s) - { - if ( ! reST_doc_strings ) - reST_doc_strings = new std::list(); - reST_doc_strings->push_back(s); - FormulateShortDesc(); - } - - static BroDocObj* last; - -protected: - std::list* reST_doc_strings; - std::list short_desc; - const ID* broID; - bool is_fake_id; /**< Whether the ID* is a dummy just for doc purposes */ - bool use_role; /**< Whether to use a reST role or directive for the ID */ - - /** - * Set the short_desc member to be a subset of reST_doc_strings. - * Specifically, short_desc will be everything in reST_doc_strings - * up until the first period or first empty string list element found. - */ - void FormulateShortDesc(); - -private: -}; - -/** - * Map identifiers to their broxygen documentation objects. - */ -extern map doc_ids; - -#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 48d651cd93..8e22b504e4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -250,8 +250,6 @@ set(bro_SRCS Attr.cc Base64.cc BPF_Program.cc - BroDoc.cc - BroDocObj.cc Brofiler.cc BroString.cc CCL.cc diff --git a/src/Type.cc b/src/Type.cc index 1e882f27d6..fa9480becf 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -1146,8 +1146,8 @@ void RecordType::DescribeFieldsReST(ODesc* d, bool func_args) const if ( func_args ) continue; - using broxygen::IdentifierDocument; - IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc(GetName()); + using broxygen::IdentifierInfo; + IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo(GetName()); if ( ! doc ) { @@ -1498,8 +1498,8 @@ void EnumType::DescribeReST(ODesc* d, bool roles_only) const else d->Add(fmt(".. bro:enum:: %s %s", it->second, GetName().c_str())); - using broxygen::IdentifierDocument; - IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc(it->second); + using broxygen::IdentifierInfo; + IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo(it->second); if ( ! doc ) { @@ -1514,7 +1514,7 @@ void EnumType::DescribeReST(ODesc* d, bool roles_only) const if ( doc->GetDeclaringScript() ) enum_from_script = doc->GetDeclaringScript()->Name(); - IdentifierDocument* type_doc = broxygen_mgr->GetIdentifierDoc(GetName()); + IdentifierInfo* type_doc = broxygen_mgr->GetIdentifierInfo(GetName()); if ( type_doc && type_doc->GetDeclaringScript() ) type_from_script = type_doc->GetDeclaringScript()->Name(); diff --git a/src/broxygen/CMakeLists.txt b/src/broxygen/CMakeLists.txt index 6c0b240e39..13cfed0496 100644 --- a/src/broxygen/CMakeLists.txt +++ b/src/broxygen/CMakeLists.txt @@ -6,9 +6,14 @@ include_directories(BEFORE ) set(broxygen_SRCS - Configuration.cc - Document.cc Manager.cc + Info.h + PackageInfo.cc + ScriptInfo.cc + IdentifierInfo.cc + Target.cc + Configuration.cc + ReStructuredTextTable.cc utils.cc ) diff --git a/src/broxygen/Configuration.cc b/src/broxygen/Configuration.cc index 19dc61aa1a..bb346935a9 100644 --- a/src/broxygen/Configuration.cc +++ b/src/broxygen/Configuration.cc @@ -1,5 +1,5 @@ #include "Configuration.h" -#include "Manager.h" +#include "utils.h" #include "util.h" #include "Reporter.h" @@ -7,417 +7,26 @@ #include #include #include -#include -#include -#include -#include -#include -#include using namespace broxygen; using namespace std; -typedef map target_factory_map; - -static target_factory_map create_target_factory_map() +static TargetFactory create_target_factory() { - target_factory_map rval; - rval["package_index"] = &PackageIndexTarget::Instantiate; - rval["package"] = &PackageTarget::Instantiate; - rval["proto_analyzer"] = &ProtoAnalyzerTarget::Instantiate; - rval["file_analyzer"] = &FileAnalyzerTarget::Instantiate; - rval["script_summary"] = &ScriptSummaryTarget::Instantiate; - rval["script_index"] = &ScriptIndexTarget::Instantiate; - rval["script"] = &ScriptTarget::Instantiate; - rval["identifier"] = &IdentifierTarget::Instantiate; + TargetFactory rval; + rval.Register("package_index"); + rval.Register("package"); + rval.Register("proto_analyzer"); + rval.Register("file_analyzer"); + rval.Register("script_summary"); + rval.Register("script_index"); + rval.Register("script"); + rval.Register("identifier"); return rval; } -static target_factory_map target_instantiators = create_target_factory_map(); - -struct TargetFile { - TargetFile(const string& arg_name) - : name(arg_name), f() - { - if ( name.find('/') != string::npos ) - { - string dir = SafeDirname(name).result; - - if ( ! ensure_intermediate_dirs(dir.c_str()) ) - reporter->FatalError("Broxygen failed to make dir %s", - dir.c_str()); - } - - f = fopen(name.c_str(), "w"); - - if ( ! f ) - reporter->FatalError("Broxygen failed to open '%s' for writing: %s", - name.c_str(), strerror(errno)); - } - - ~TargetFile() - { - if ( f ) - fclose(f); - - DBG_LOG(DBG_BROXYGEN, "Wrote out-of-date target '%s'", name.c_str()); - } - - string name; - FILE* f; -}; - -template -static vector filter_matching_docs(const vector& from, Target* t) - { - vector rval; - - for ( size_t i = 0; i < from.size(); ++i ) - { - T* d = dynamic_cast(from[i]); - - if ( ! d ) - continue; - - if ( t->MatchesPattern(d) ) - { - DBG_LOG(DBG_BROXYGEN, "Doc '%s' matched pattern for target '%s'", - d->Name().c_str(), t->Name().c_str()); - rval.push_back(d); - } - } - - return rval; - } - -Target::Target(const string& arg_name, const string& arg_pattern) - : name(arg_name), pattern(arg_pattern), prefix() - { - size_t pos = pattern.find('*'); - - if ( pos == 0 || pos == string::npos ) - return; - - prefix = pattern.substr(0, pos); - } - -bool Target::MatchesPattern(Document* doc) const - { - if ( pattern == "*" ) - return true; - - if ( prefix.empty() ) - return doc->Name() == pattern; - - return ! strncmp(doc->Name().c_str(), prefix.c_str(), prefix.size()); - } - -void AnalyzerTarget::DoFindDependencies(const std::vector& docs) - { - // TODO: really should add to dependency list the tag type's ID and - // all bif items for matching analyzer plugins, but that's all dependent - // on the bro binary itself, so I'm cheating. - } - -void AnalyzerTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), vector()) ) - return; - - if ( Pattern() != "*" ) - reporter->InternalWarning("Broxygen only implements analyzer target" - " pattern '*'"); - - TargetFile file(Name()); - doc_creator_callback(file.f); - } - -void PackageTarget::DoFindDependencies(const vector& docs) - { - pkg_deps = filter_matching_docs(docs, this); - - if ( pkg_deps.empty() ) - reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", - Name().c_str(), Pattern().c_str()); - - for ( size_t i = 0; i < docs.size(); ++i ) - { - ScriptDocument* script = dynamic_cast(docs[i]); - - if ( ! script ) - continue; - - for ( size_t j = 0; j < pkg_deps.size(); ++j ) - { - if ( strncmp(script->Name().c_str(), pkg_deps[j]->Name().c_str(), - pkg_deps[j]->Name().size())) - continue; - - DBG_LOG(DBG_BROXYGEN, "Script %s associated with package %s", - script->Name().c_str(), pkg_deps[j]->Name().c_str()); - pkg_manifest[pkg_deps[j]].push_back(script); - script_deps.push_back(script); - } - } - } - -void PackageTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), script_deps) && - broxygen_mgr->IsUpToDate(Name(), pkg_deps) ) - return; - - TargetFile file(Name()); - - fprintf(file.f, ":orphan:\n\n"); - - for ( manifest_t::const_iterator it = pkg_manifest.begin(); - it != pkg_manifest.end(); ++it ) - { - string header = fmt("Package: %s", it->first->Name().c_str()); - header += "\n" + string(header.size(), '='); - - fprintf(file.f, "%s\n\n", header.c_str()); - - vector readme = it->first->GetReadme(); - - for ( size_t i = 0; i < readme.size(); ++i ) - fprintf(file.f, "%s\n", readme[i].c_str()); - - fprintf(file.f, "\n"); - - for ( size_t i = 0; i < it->second.size(); ++i ) - { - fprintf(file.f, ":doc:`/scripts/%s`\n", - it->second[i]->Name().c_str()); - - vector cmnts = it->second[i]->GetComments(); - - for ( size_t j = 0; j < cmnts.size(); ++j ) - fprintf(file.f, " %s\n", cmnts[j].c_str()); - - fprintf(file.f, "\n"); - } - } - } - -void PackageIndexTarget::DoFindDependencies(const vector& docs) - { - pkg_deps = filter_matching_docs(docs, this); - - if ( pkg_deps.empty() ) - reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", - Name().c_str(), Pattern().c_str()); - } - -void PackageIndexTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), pkg_deps) ) - return; - - TargetFile file(Name()); - - for ( size_t i = 0; i < pkg_deps.size(); ++i ) - fprintf(file.f, "%s\n", pkg_deps[i]->ReStructuredText().c_str()); - } - -void ScriptTarget::DoFindDependencies(const vector& docs) - { - script_deps = filter_matching_docs(docs, this); - - if ( script_deps.empty() ) - reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", - Name().c_str(), Pattern().c_str()); - - if ( ! IsDir() ) - return; - - for ( size_t i = 0; i < script_deps.size(); ++i ) - { - if ( SafeBasename(script_deps[i]->Name()).result == PACKAGE_LOADER ) - { - string pkg_dir = SafeDirname(script_deps[i]->Name()).result; - string target_file = Name() + pkg_dir + "/index.rst"; - Target* t = PackageTarget::Instantiate(target_file, pkg_dir); - t->FindDependencies(docs); - pkg_deps.push_back(t); - } - } - } - -vector dir_contents_recursive(string dir) - { - vector rval; - struct stat st; - - if ( stat(dir.c_str(), &st) < 0 && errno == ENOENT ) - return rval; - - while ( dir[dir.size() - 1] == '/' ) - dir.erase(dir.size() - 1, 1); - - char* dir_copy = copy_string(dir.c_str()); - char** scan_path = new char*[2]; - scan_path[0] = dir_copy; - scan_path[1] = 0; - - FTS* fts = fts_open(scan_path, FTS_NOCHDIR, 0); - - if ( ! fts ) - { - reporter->Error("fts_open failure: %s", strerror(errno)); - delete [] scan_path; - delete [] dir_copy; - return rval; - } - - FTSENT* n; - - while ( (n = fts_read(fts)) ) - { - if ( n->fts_info & FTS_F ) - rval.push_back(n->fts_path); - } - - if ( errno ) - reporter->Error("fts_read failure: %s", strerror(errno)); - - if ( fts_close(fts) < 0 ) - reporter->Error("fts_close failure: %s", strerror(errno)); - - delete [] scan_path; - delete [] dir_copy; - return rval; - } - -void ScriptTarget::DoGenerate() const - { - if ( IsDir() ) - { - // Target name is a dir, matching scripts are written within that dir - // with a dir tree that parallels the script's BROPATH location. - - set targets; - vector dir_contents = dir_contents_recursive(Name()); - - for ( size_t i = 0; i < script_deps.size(); ++i ) - { - string target_filename = Name() + script_deps[i]->Name() + ".rst"; - targets.insert(target_filename); - vector dep; - dep.push_back(script_deps[i]); - - if ( broxygen_mgr->IsUpToDate(target_filename, dep) ) - continue; - - TargetFile file(target_filename); - - fprintf(file.f, "%s\n", script_deps[i]->ReStructuredText().c_str()); - } - - for ( size_t i = 0; i < pkg_deps.size(); ++i ) - { - targets.insert(pkg_deps[i]->Name()); - pkg_deps[i]->Generate(); - } - - for ( size_t i = 0; i < dir_contents.size(); ++i ) - { - string f = dir_contents[i]; - - if ( targets.find(f) != targets.end() ) - continue; - - if ( unlink(f.c_str()) < 0 ) - reporter->Warning("Failed to unlink %s: %s", f.c_str(), - strerror(errno)); - - DBG_LOG(DBG_BROXYGEN, "Delete stale script file %s", f.c_str()); - } - - return; - } - - // Target is a single file, all matching scripts get written there. - - if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) - return; - - TargetFile file(Name()); - - for ( size_t i = 0; i < script_deps.size(); ++i ) - fprintf(file.f, "%s\n", script_deps[i]->ReStructuredText().c_str()); - } - -void ScriptSummaryTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) - return; - - TargetFile file(Name()); - - for ( size_t i = 0; i < script_deps.size(); ++i ) - { - ScriptDocument* d = dynamic_cast(script_deps[i]); - - if ( ! d ) - continue; - - fprintf(file.f, ":doc:`/scripts/%s`\n", d->Name().c_str()); - - vector cmnts = d->GetComments(); - - for ( size_t i = 0; i < cmnts.size(); ++i ) - fprintf(file.f, " %s\n", cmnts[i].c_str()); - - fprintf(file.f, "\n"); - } - } - -void ScriptIndexTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) - return; - - TargetFile file(Name()); - - fprintf(file.f, ".. toctree::\n"); - fprintf(file.f, " :maxdepth: 1\n\n"); - - for ( size_t i = 0; i < script_deps.size(); ++i ) - { - ScriptDocument* d = dynamic_cast(script_deps[i]); - - if ( ! d ) - continue; - - fprintf(file.f, " %s \n", d->Name().c_str(), - d->Name().c_str()); - } - } - -void IdentifierTarget::DoFindDependencies(const vector& docs) - { - id_deps = filter_matching_docs(docs, this); - - if ( id_deps.empty() ) - reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", - Name().c_str(), Pattern().c_str()); - } - -void IdentifierTarget::DoGenerate() const - { - if ( broxygen_mgr->IsUpToDate(Name(), id_deps) ) - return; - - TargetFile file(Name()); - - for ( size_t i = 0; i < id_deps.size(); ++i ) - fprintf(file.f, "%s\n\n", id_deps[i]->ReStructuredText().c_str()); - } - Config::Config(const string& arg_file, const string& delim) - : file(arg_file), targets() + : file(arg_file), targets(), target_factory(create_target_factory()) { if ( file.empty() ) return; @@ -450,14 +59,13 @@ Config::Config(const string& arg_file, const string& delim) reporter->FatalError("malformed Broxygen target in %s:%u: %s", file.c_str(), line_number, line.c_str()); - target_factory_map::const_iterator it = - target_instantiators.find(tokens[0]); + Target* target = target_factory.Create(tokens[0], tokens[2], tokens[1]); - if ( it == target_instantiators.end() ) + if ( ! target ) reporter->FatalError("unkown Broxygen target type: %s", tokens[0].c_str()); - targets.push_back(it->second(tokens[2], tokens[1])); + targets.push_back(target); } if ( f.bad() ) @@ -471,10 +79,10 @@ Config::~Config() delete targets[i]; } -void Config::FindDependencies(const vector& docs) +void Config::FindDependencies(const vector& infos) { for ( size_t i = 0; i < targets.size(); ++i ) - targets[i]->FindDependencies(docs); + targets[i]->FindDependencies(infos); } void Config::GenerateDocs() const @@ -485,11 +93,8 @@ void Config::GenerateDocs() const time_t Config::GetModificationTime() const { - struct stat s; + if ( file.empty() ) + return 0; - if ( stat(file.c_str(), &s) < 0 ) - reporter->InternalError("Broxygen can't stat config file %s: %s", - file.c_str(), strerror(errno)); - - return s.st_mtime; + return broxygen::get_mtime(file); } diff --git a/src/broxygen/Configuration.h b/src/broxygen/Configuration.h index 6ea9f640be..0a2f98bcda 100644 --- a/src/broxygen/Configuration.h +++ b/src/broxygen/Configuration.h @@ -1,246 +1,61 @@ #ifndef BROXYGEN_CONFIGURATION_H #define BROXYGEN_CONFIGURATION_H -#include "Document.h" -#include "BroDoc.h" +#include "Info.h" +#include "Target.h" #include #include -#include namespace broxygen { -// TODO: documentation... - -class Target { -public: - - typedef Target* (*factory_fn)(const std::string&, const std::string&); - - virtual ~Target() - { } - - void FindDependencies(const std::vector& docs) - { DoFindDependencies(docs); } - - void Generate() const - { DoGenerate(); } - - bool MatchesPattern(Document* doc) const; - - std::string Name() const - { return name; } - - std::string Pattern() const - { return pattern; } - -protected: - - Target(const std::string& arg_name, const std::string& arg_pattern); - -private: - - virtual void DoFindDependencies(const std::vector& docs) = 0; - - virtual void DoGenerate() const = 0; - - std::string name; - std::string pattern; - std::string prefix; -}; - -class AnalyzerTarget : public Target { -protected: - - typedef void (*doc_creator_fn)(FILE*); - - AnalyzerTarget(const std::string& name, const std::string& pattern, - doc_creator_fn cb) - : Target(name, pattern), doc_creator_callback(cb) - { } - -private: - - void DoFindDependencies(const std::vector& docs); - - void DoGenerate() const; - - doc_creator_fn doc_creator_callback; -}; - -class ProtoAnalyzerTarget : public AnalyzerTarget { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new ProtoAnalyzerTarget(name, pattern); } - -private: - - ProtoAnalyzerTarget(const std::string& name, const std::string& pattern) - : AnalyzerTarget(name, pattern, &CreateProtoAnalyzerDoc) - { } -}; - -class FileAnalyzerTarget : public AnalyzerTarget { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new FileAnalyzerTarget(name, pattern); } - -private: - - FileAnalyzerTarget(const std::string& name, const std::string& pattern) - : AnalyzerTarget(name, pattern, &CreateFileAnalyzerDoc) - { } -}; - -class PackageTarget : public Target { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new PackageTarget(name, pattern); } - -private: - - PackageTarget(const std::string& name, const std::string& pattern) - : Target(name, pattern), pkg_deps(), script_deps(), pkg_manifest() - { } - - void DoFindDependencies(const std::vector& docs); - - void DoGenerate() const; - - std::vector pkg_deps; - std::vector script_deps; - typedef std::map > manifest_t; - manifest_t pkg_manifest; -}; - -class PackageIndexTarget : public Target { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new PackageIndexTarget(name, pattern); } - -private: - - PackageIndexTarget(const std::string& name, const std::string& pattern) - : Target(name, pattern), pkg_deps() - { } - - void DoFindDependencies(const std::vector& docs); - - void DoGenerate() const; - - std::vector pkg_deps; -}; - -class ScriptTarget : public Target { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new ScriptTarget(name, pattern); } - - ~ScriptTarget() - { for ( size_t i = 0; i < pkg_deps.size(); ++i ) delete pkg_deps[i]; } - -protected: - - ScriptTarget(const std::string& name, const std::string& pattern) - : Target(name, pattern), script_deps() - { } - - std::vector script_deps; - -private: - - void DoFindDependencies(const std::vector& docs); - - void DoGenerate() const; - - bool IsDir() const - { return Name()[Name().size() - 1] == '/'; } - - std::vector pkg_deps; -}; - -class ScriptSummaryTarget : public ScriptTarget { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new ScriptSummaryTarget(name, pattern); } - -private: - - ScriptSummaryTarget(const std::string& name, const std::string& pattern) - : ScriptTarget(name, pattern) - { } - - void DoGenerate() const /* override */; -}; - -class ScriptIndexTarget : public ScriptTarget { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new ScriptIndexTarget(name, pattern); } - -private: - - ScriptIndexTarget(const std::string& name, const std::string& pattern) - : ScriptTarget(name, pattern) - { } - - void DoGenerate() const /* override */; -}; - -class IdentifierTarget : public Target { -public: - - static Target* Instantiate(const std::string& name, - const std::string& pattern) - { return new IdentifierTarget(name, pattern); } - -private: - - IdentifierTarget(const std::string& name, const std::string& pattern) - : Target(name, pattern), id_deps() - { } - - void DoFindDependencies(const std::vector& docs); - - void DoGenerate() const; - - std::vector id_deps; -}; - +/** + * Manages the generation of reStructuredText documents corresponding to + * particular targets that are specified in a config file. The config file + * is a simple list of one target per line, with the target format being + * a tab-delimited list of target-type, target-pattern, and target-output-file. + */ class Config { + public: + /** + * Read a Broxygen configuration file, parsing all targets in it. + * @param file The file containing a list of Broxygen targets. If it's + * an empty string most methods are a no-op. + * @param delim The delimiter between target fields. + */ Config(const std::string& file, const std::string& delim = "\t"); + /** + * Destructor, cleans up targets created when parsing config file. + */ ~Config(); - void FindDependencies(const std::vector& docs); + /** + * Resolves dependency information for each target. + * @param infos All known information objects for documentable things. + */ + void FindDependencies(const std::vector& infos); + /** + * Build each Broxygen target (i.e. write out the reST documents to disk). + */ void GenerateDocs() const; + /** + * @return The modification time of the config file, or 0 if config + * file was specified by an empty string. + */ time_t GetModificationTime() const; private: std::string file; std::vector targets; + TargetFactory target_factory; }; - } // namespace broxygen #endif diff --git a/src/broxygen/Document.cc b/src/broxygen/Document.cc deleted file mode 100644 index bb64f90555..0000000000 --- a/src/broxygen/Document.cc +++ /dev/null @@ -1,660 +0,0 @@ -#include "Document.h" -#include "Manager.h" -#include "utils.h" - -#include "util.h" -#include "Val.h" -#include "Desc.h" -#include "Reporter.h" - -#include -#include - -using namespace broxygen; -using namespace std; - -static bool is_public_api(const ID* id) - { - return (id->Scope() == SCOPE_GLOBAL) || - (id->Scope() == SCOPE_MODULE && id->IsExport()); - } - -static string make_heading(const string& heading, char underline) - { - return heading + "\n" + string(heading.size(), underline) + "\n"; - } - -PackageDocument::PackageDocument(const string& arg_name) - : Document(), - pkg_name(arg_name), readme() - { - string readme_file = find_file(pkg_name + "/README", bro_path()); - - if ( readme_file.empty() ) - return; - - ifstream f(readme_file.c_str()); - - if ( ! f.is_open() ) - reporter->InternalWarning("Broxygen failed to open '%s': %s", - readme_file.c_str(), strerror(errno)); - - string line; - - while ( getline(f, line) ) - readme.push_back(line); - - if ( f.bad() ) - reporter->InternalWarning("Broxygen error reading '%s': %s", - readme_file.c_str(), strerror(errno)); - } - -string PackageDocument::DoReStructuredText(bool roles_only) const - { - string rval = fmt(":doc:`%s `\n\n", pkg_name.c_str(), - pkg_name.c_str()); - - for ( size_t i = 0; i < readme.size(); ++i ) - rval += " " + readme[i] + "\n"; - - return rval; - } - -IdentifierDocument::IdentifierDocument(ID* arg_id, ScriptDocument* script) - : Document(), - comments(), id(arg_id), initial_val_desc(), redefs(), fields(), - last_field_seen(), declaring_script(script) - { - Ref(id); - - if ( id->ID_Val() ) - { - ODesc d; - id->ID_Val()->Describe(&d); - initial_val_desc = d.Description(); - } - } - -IdentifierDocument::~IdentifierDocument() - { - Unref(id); - - for ( redef_list::const_iterator it = redefs.begin(); it != redefs.end(); - ++it ) - delete *it; - - for ( record_field_map::const_iterator it = fields.begin(); - it != fields.end(); ++it ) - delete it->second; - } - -void IdentifierDocument::AddRedef(const string& script, - const vector& comments) - { - Redefinition* redef = new Redefinition(); - redef->from_script = script; - - if ( id->ID_Val() ) - { - ODesc d; - id->ID_Val()->Describe(&d); - redef->new_val_desc = d.Description(); - } - - redef->comments = comments; - redefs.push_back(redef); - } - -void IdentifierDocument::AddRecordField(const TypeDecl* field, - const string& script, - vector& comments) - { - RecordField* rf = new RecordField(); - rf->field = new TypeDecl(*field); - rf->from_script = script; - rf->comments = comments; - fields[rf->field->id] = rf; - last_field_seen = rf; - } - -vector IdentifierDocument::GetComments() const - { - return comments; - } - -vector IdentifierDocument::GetFieldComments(const string& field) const - { - record_field_map::const_iterator it = fields.find(field); - - if ( it == fields.end() ) - return vector(); - - return it->second->comments; - } - -list -IdentifierDocument::GetRedefs(const string& from_script) const - { - list rval; - - for ( redef_list::const_iterator it = redefs.begin(); it != redefs.end(); - ++it ) - { - if ( from_script == (*it)->from_script ) - rval.push_back(*(*it)); - } - - return rval; - } - -string IdentifierDocument::GetDeclaringScriptForField(const string& field) const - { - record_field_map::const_iterator it = fields.find(field); - - if ( it == fields.end() ) - return ""; - - return it->second->from_script; - } - -string IdentifierDocument::DoReStructuredText(bool roles_only) const - { - ODesc d; - d.SetIndentSpaces(3); - d.SetQuotes(true); - id->DescribeReST(&d, roles_only); - - if ( comments.empty() ) - return d.Description(); - - d.ClearIndentLevel(); - d.PushIndent(); - - for ( size_t i = 0; i < comments.size(); ++i ) - { - if ( i > 0 ) - d.NL(); - - if ( IsFunc(id->Type()->Tag()) ) - { - string s = comments[i]; - - if ( broxygen::prettify_params(s) ) - d.NL(); - - d.Add(s.c_str()); - } - else - d.Add(comments[i].c_str()); - } - - return d.Description(); - } - -ScriptDocument::ScriptDocument(const string& arg_name, const string& arg_path) - : Document(), - name(arg_name), path(arg_path), - is_pkg_loader(SafeBasename(name).result == PACKAGE_LOADER), - dependencies(), module_usages(), comments(), identifier_docs(), - options(), constants(), state_vars(), types(), events(), hooks(), - functions(), redefs() - { - } - -void ScriptDocument::AddIdentifierDoc(IdentifierDocument* doc) - { - identifier_docs[doc->Name()] = doc; - } - -void ScriptDocument::DoInitPostScript() - { - for ( id_doc_map::const_iterator it = identifier_docs.begin(); - it != identifier_docs.end(); ++it ) - { - IdentifierDocument* doc = it->second; - ID* id = doc->GetID(); - - if ( ! is_public_api(id) ) - continue; - - if ( id->AsType() ) - { - types.push_back(doc); - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a type", - id->Name(), name.c_str()); - continue; - } - - if ( IsFunc(id->Type()->Tag()) ) - { - switch ( id->Type()->AsFuncType()->Flavor() ) { - case FUNC_FLAVOR_HOOK: - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a hook", - id->Name(), name.c_str()); - hooks.push_back(doc); - break; - case FUNC_FLAVOR_EVENT: - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a event", - id->Name(), name.c_str()); - events.push_back(doc); - break; - case FUNC_FLAVOR_FUNCTION: - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a function", - id->Name(), name.c_str()); - functions.push_back(doc); - break; - default: - reporter->InternalError("Invalid function flavor"); - break; - } - - continue; - } - - if ( id->IsConst() ) - { - if ( id->FindAttr(ATTR_REDEF) ) - { - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as an option", - id->Name(), name.c_str()); - options.push_back(doc); - } - else - { - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a constant", - id->Name(), name.c_str()); - constants.push_back(doc); - } - - continue; - } - - if ( id->Type()->Tag() == TYPE_ENUM ) - // Enums are always referenced/documented from the type's - // documentation. - continue; - - DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a state variable", - id->Name(), name.c_str()); - state_vars.push_back(doc); - } - } - -vector ScriptDocument::GetComments() const - { - return comments; - } - -static size_t end_of_first_sentence(const string& s) - { - size_t rval = 0; - - while ( (rval = s.find_first_of('.', rval)) != string::npos ) - { - if ( rval == s.size() - 1 ) - // Period is at end of string. - return rval; - - if ( isspace(s[rval + 1]) ) - // Period has a space after it. - return rval; - - // Period has some non-space character after it, keep looking. - ++rval; - } - - return rval; - } - -static bool is_all_whitespace(const string& s) - { - for ( size_t i = 0; i < s.size(); ++i ) - if ( ! isspace(s[i]) ) - return false; - - return true; - } - -static vector summary_comment(const vector& cmnts) - { - vector rval; - - for ( size_t i = 0; i < cmnts.size(); ++i ) - { - size_t end = end_of_first_sentence(cmnts[i]); - - if ( end == string::npos ) - { - if ( is_all_whitespace(cmnts[i]) ) - break; - - rval.push_back(cmnts[i]); - } - else - { - rval.push_back(cmnts[i].substr(0, end + 1)); - break; - } - } - - return rval; - } - -class ReStructuredTextTable { -public: - - ReStructuredTextTable(size_t arg_num_cols) - : num_cols(arg_num_cols), rows(), longest_row_in_column() - { - for ( size_t i = 0; i < num_cols; ++i ) - longest_row_in_column.push_back(1); - } - - void AddRow(const vector& new_row) - { - assert(new_row.size() == num_cols); - rows.push_back(new_row); - - for ( size_t i = 0; i < new_row.size(); ++i ) - if ( new_row[i].size() > longest_row_in_column[i] ) - longest_row_in_column[i] = new_row[i].size(); - } - - static string MakeBorder(const vector col_sizes, char border) - { - string rval; - - for ( size_t i = 0; i < col_sizes.size(); ++i ) - { - if ( i > 0 ) - rval += " "; - - rval += string(col_sizes[i], border); - } - - rval += "\n"; - return rval; - } - - string AsString(char border) const - { - string rval = MakeBorder(longest_row_in_column, border); - - for ( size_t row = 0; row < rows.size(); ++row ) - { - for ( size_t col = 0; col < num_cols; ++col ) - { - if ( col > 0 ) - { - size_t last = rows[row][col - 1].size(); - size_t longest = longest_row_in_column[col - 1]; - size_t whitespace = longest - last + 1; - rval += string(whitespace, ' '); - } - - rval += rows[row][col]; - } - - rval += "\n"; - } - - rval += MakeBorder(longest_row_in_column, border); - return rval; - } - - -private: - - size_t num_cols; - vector > rows; - vector longest_row_in_column; -}; - -static void add_summary_rows(const ODesc& id_desc, const vector& cmnts, - ReStructuredTextTable* table) - { - vector row; - row.push_back(id_desc.Description()); - - if ( cmnts.empty() ) - { - row.push_back(""); - table->AddRow(row); - return; - } - - row.push_back(cmnts[0]); - table->AddRow(row); - - for ( size_t i = 1; i < cmnts.size(); ++i ) - { - row.clear(); - row.push_back(""); - row.push_back(cmnts[i]); - table->AddRow(row); - } - } - -static string make_summary(const string& heading, char underline, char border, - const list& id_list) - { - if ( id_list.empty() ) - return ""; - - ReStructuredTextTable table(2); - - for ( list::const_iterator it = id_list.begin(); - it != id_list.end(); ++it ) - { - ID* id = (*it)->GetID(); - ODesc d; - d.SetQuotes(1); - id->DescribeReSTShort(&d); - add_summary_rows(d, summary_comment((*it)->GetComments()), &table); - } - - return make_heading(heading, underline) + table.AsString(border) + "\n"; - } - -static string make_redef_summary(const string& heading, char underline, - char border, const string& from_script, - const id_doc_set& id_set) - { - if ( id_set.empty() ) - return ""; - - ReStructuredTextTable table(2); - - for ( id_doc_set::const_iterator it = id_set.begin(); it != id_set.end(); - ++it ) - { - ID* id = (*it)->GetID(); - ODesc d; - d.SetQuotes(1); - id->DescribeReSTShort(&d); - - typedef list redef_list; - redef_list redefs = (*it)->GetRedefs(from_script); - - for ( redef_list::const_iterator iit = redefs.begin(); - iit != redefs.end(); ++iit ) - add_summary_rows(d, summary_comment(iit->comments), &table); - } - - return make_heading(heading, underline) + table.AsString(border) + "\n"; - } - -static string make_details(const string& heading, char underline, - const list& id_list) - { - if ( id_list.empty() ) - return ""; - - string rval = make_heading(heading, underline); - - for ( list::const_iterator it = id_list.begin(); - it != id_list.end(); ++it ) - { - rval += (*it)->ReStructuredText(); - rval += "\n\n"; - } - - return rval; - } - -static string make_redef_details(const string& heading, char underline, - const id_doc_set& id_set) - { - if ( id_set.empty() ) - return ""; - - string rval = make_heading(heading, underline); - - for ( id_doc_set::const_iterator it = id_set.begin(); - it != id_set.end(); ++it ) - { - rval += (*it)->ReStructuredText(true); - rval += "\n\n"; - } - - return rval; - } - -string ScriptDocument::DoReStructuredText(bool roles_only) const - { - string rval; - - rval += ":tocdepth: 3\n\n"; - rval += make_heading(name, '='); - - for ( string_set::const_iterator it = module_usages.begin(); - it != module_usages.end(); ++it ) - rval += ".. bro:namespace:: " + *it + "\n"; - - rval += "\n"; - - for ( size_t i = 0; i < comments.size(); ++i ) - rval += comments[i] + "\n"; - - rval += "\n"; - - if ( ! module_usages.empty() ) - { - rval += module_usages.size() > 1 ? ":Namespaces: " : ":Namespace: "; - - for ( string_set::const_iterator it = module_usages.begin(); - it != module_usages.end(); ++it ) - { - if ( it != module_usages.begin() ) - rval += ", "; - - rval += *it; - } - - rval += "\n"; - } - - if ( ! dependencies.empty() ) - { - rval += ":Imports: "; - - for ( string_set::const_iterator it = dependencies.begin(); - it != dependencies.end(); ++it ) - { - if ( it != dependencies.begin() ) - rval += ", "; - - string path = find_file(*it, bro_path(), "bro"); - string doc = *it; - - if ( ! path.empty() && is_dir(path.c_str()) ) - // Reference the package. - doc += "/index"; - - rval += fmt(":doc:`%s `", it->c_str(), doc.c_str()); - } - - rval += "\n"; - } - - rval += fmt(":Source File: :download:`/scripts/%s`\n", name.c_str()); - rval += "\n"; - rval += make_heading("Summary", '~'); - rval += make_summary("Options", '#', '=', options); - rval += make_summary("Constants", '#', '=', constants); - rval += make_summary("State Variables", '#', '=', state_vars); - rval += make_summary("Types", '#', '=', types); - rval += make_redef_summary("Redefinitions", '#', '=', name, redefs); - rval += make_summary("Events", '#', '=', events); - rval += make_summary("Hooks", '#', '=', hooks); - rval += make_summary("Functions", '#', '=', functions); - rval += "\n"; - rval += make_heading("Detailed Interface", '~'); - rval += make_details("Options", '#', options); - rval += make_details("Constants", '#', constants); - rval += make_details("State Variables", '#', state_vars); - rval += make_details("Types", '#', types); - //rval += make_redef_details("Redefinitions", '#', redefs); - rval += make_details("Events", '#', events); - rval += make_details("Hooks", '#', hooks); - rval += make_details("Functions", '#', functions); - - return rval; - } - -static time_t get_mtime(const string& filename) - { - struct stat s; - - if ( stat(filename.c_str(), &s) < 0 ) - reporter->InternalError("Broxygen failed to stat file '%s': %s", - filename.c_str(), strerror(errno)); - - return s.st_mtime; - } - -time_t IdentifierDocument::DoGetModificationTime() const - { - // Could probably get away with just checking the set of scripts that - // contributed to the ID declaration/redefinitions, but this is easier... - return declaring_script->GetModificationTime(); - } - -time_t ScriptDocument::DoGetModificationTime() const - { - time_t most_recent = get_mtime(path); - - for ( string_set::const_iterator it = dependencies.begin(); - it != dependencies.end(); ++it ) - { - Document* doc = broxygen_mgr->GetScriptDoc(*it); - - if ( ! doc ) - { - string pkg_name = *it + "/" + PACKAGE_LOADER; - doc = broxygen_mgr->GetScriptDoc(pkg_name); - - if ( ! doc ) - reporter->InternalWarning("Broxygen failed to get mtime of %s", - it->c_str()); - continue; - } - - time_t dep_mtime = doc->GetModificationTime(); - - if ( dep_mtime > most_recent ) - most_recent = dep_mtime; - } - - return most_recent; - } - -time_t PackageDocument::DoGetModificationTime() const - { - string readme_file = find_file(pkg_name + "/README", bro_path()); - - if ( readme_file.empty() ) - return 0; - - return get_mtime(readme_file); - } diff --git a/src/broxygen/Document.h b/src/broxygen/Document.h deleted file mode 100644 index 10155ab0eb..0000000000 --- a/src/broxygen/Document.h +++ /dev/null @@ -1,215 +0,0 @@ -#ifndef BROXYGEN_DOCUMENT_H -#define BROXYGEN_DOCUMENT_H - -#include -#include -#include -#include -#include -#include -#include - -#include "ID.h" -#include "Type.h" - -namespace broxygen { - -// TODO: documentation... - -class Document { - -public: - - Document() - { } - - virtual ~Document() - { } - - time_t GetModificationTime() const - { return DoGetModificationTime(); } - - std::string Name() const - { return DoName(); } - - std::string ReStructuredText(bool roles_only = false) const - { return DoReStructuredText(roles_only); } - - void InitPostScript() - { return DoInitPostScript(); } - -private: - - virtual time_t DoGetModificationTime() const = 0; - virtual std::string DoName() const = 0; - virtual std::string DoReStructuredText(bool roles_only) const = 0; - virtual void DoInitPostScript() - { } -}; - -class PackageDocument : public Document { - -public: - - PackageDocument(const std::string& name); - - std::vector GetReadme() const - { return readme; } - -private: - - time_t DoGetModificationTime() const; - - std::string DoName() const - { return pkg_name; } - - std::string DoReStructuredText(bool roles_only) const; - - std::string pkg_name; - std::vector readme; -}; - - -class ScriptDocument; - -class IdentifierDocument : public Document { -public: - - IdentifierDocument(ID* id, ScriptDocument* script); - - ~IdentifierDocument(); - - void AddComment(const std::string& comment) - { last_field_seen ? last_field_seen->comments.push_back(comment) - : comments.push_back(comment); } - - void AddComments(const std::vector& cmtns) - { comments.insert(comments.end(), cmtns.begin(), cmtns.end()); } - - void AddRedef(const std::string& from_script, - const std::vector& comments); - - void AddRecordField(const TypeDecl* field, const std::string& script, - std::vector& comments); - - void CompletedTypeDecl() - { last_field_seen = 0; } - - ID* GetID() const - { return id; } - - ScriptDocument* GetDeclaringScript() const - { return declaring_script; } - - std::string GetDeclaringScriptForField(const std::string& field) const; - - std::vector GetComments() const; - - std::vector GetFieldComments(const std::string& field) const; - - struct Redefinition { - std::string from_script; - std::string new_val_desc; - std::vector comments; - }; - - std::list GetRedefs(const std::string& from_script) const; - -private: - - time_t DoGetModificationTime() const; - - std::string DoName() const - { return id->Name(); } - - std::string DoReStructuredText(bool roles_only) const; - - struct RecordField { - ~RecordField() - { delete field; } - - TypeDecl* field; - std::string from_script; - std::vector comments; - }; - - typedef std::list redef_list; - typedef std::map record_field_map; - - std::vector comments; - ID* id; - std::string initial_val_desc; - redef_list redefs; - record_field_map fields; - RecordField* last_field_seen; - ScriptDocument* declaring_script; -}; - -struct IdDocComp { - bool operator() (IdentifierDocument* lhs, IdentifierDocument* rhs) const - { return lhs->Name() < rhs->Name(); } -}; - -typedef std::set id_doc_set; - -class ScriptDocument : public Document { - -public: - - ScriptDocument(const std::string& name, const std::string& path); - - void AddComment(const std::string& comment) - { comments.push_back(comment); } - - void AddDependency(const std::string& name) - { dependencies.insert(name); } - - void AddModule(const std::string& name) - { module_usages.insert(name); } - - void AddIdentifierDoc(IdentifierDocument* doc); - - void AddRedef(IdentifierDocument* doc) - { redefs.insert(doc); } - - bool IsPkgLoader() const - { return is_pkg_loader; } - - std::vector GetComments() const; - -private: - - typedef std::map id_doc_map; - typedef std::list id_doc_list; - typedef std::set string_set; - - time_t DoGetModificationTime() const; - - std::string DoName() const - { return name; } - - std::string DoReStructuredText(bool roles_only) const; - - void DoInitPostScript() /* override */; - - std::string name; - std::string path; - bool is_pkg_loader; - string_set dependencies; - string_set module_usages; - std::vector comments; - id_doc_map identifier_docs; - id_doc_list options; - id_doc_list constants; - id_doc_list state_vars; - id_doc_list types; - id_doc_list events; - id_doc_list hooks; - id_doc_list functions; - id_doc_set redefs; -}; - - -} // namespace broxygen - -#endif diff --git a/src/broxygen/IdentifierInfo.cc b/src/broxygen/IdentifierInfo.cc new file mode 100644 index 0000000000..870f128171 --- /dev/null +++ b/src/broxygen/IdentifierInfo.cc @@ -0,0 +1,146 @@ +#include "IdentifierInfo.h" +#include "utils.h" + +#include "Desc.h" +#include "Val.h" + +using namespace std; +using namespace broxygen; + +IdentifierInfo::IdentifierInfo(ID* arg_id, ScriptInfo* script) + : Info(), + comments(), id(arg_id), initial_val_desc(), redefs(), fields(), + last_field_seen(), declaring_script(script) + { + Ref(id); + + if ( id->ID_Val() ) + { + ODesc d; + id->ID_Val()->Describe(&d); + initial_val_desc = d.Description(); + } + } + +IdentifierInfo::~IdentifierInfo() + { + Unref(id); + + for ( redef_list::const_iterator it = redefs.begin(); it != redefs.end(); + ++it ) + delete *it; + + for ( record_field_map::const_iterator it = fields.begin(); + it != fields.end(); ++it ) + delete it->second; + } + +void IdentifierInfo::AddRedef(const string& script, + const vector& comments) + { + Redefinition* redef = new Redefinition(); + redef->from_script = script; + + if ( id->ID_Val() ) + { + ODesc d; + id->ID_Val()->Describe(&d); + redef->new_val_desc = d.Description(); + } + + redef->comments = comments; + redefs.push_back(redef); + } + +void IdentifierInfo::AddRecordField(const TypeDecl* field, + const string& script, + vector& comments) + { + RecordField* rf = new RecordField(); + rf->field = new TypeDecl(*field); + rf->from_script = script; + rf->comments = comments; + fields[rf->field->id] = rf; + last_field_seen = rf; + } + +vector IdentifierInfo::GetComments() const + { + return comments; + } + +vector IdentifierInfo::GetFieldComments(const string& field) const + { + record_field_map::const_iterator it = fields.find(field); + + if ( it == fields.end() ) + return vector(); + + return it->second->comments; + } + +list +IdentifierInfo::GetRedefs(const string& from_script) const + { + list rval; + + for ( redef_list::const_iterator it = redefs.begin(); it != redefs.end(); + ++it ) + { + if ( from_script == (*it)->from_script ) + rval.push_back(*(*it)); + } + + return rval; + } + +string IdentifierInfo::GetDeclaringScriptForField(const string& field) const + { + record_field_map::const_iterator it = fields.find(field); + + if ( it == fields.end() ) + return ""; + + return it->second->from_script; + } + +string IdentifierInfo::DoReStructuredText(bool roles_only) const + { + ODesc d; + d.SetIndentSpaces(3); + d.SetQuotes(true); + id->DescribeReST(&d, roles_only); + + if ( comments.empty() ) + return d.Description(); + + d.ClearIndentLevel(); + d.PushIndent(); + + for ( size_t i = 0; i < comments.size(); ++i ) + { + if ( i > 0 ) + d.NL(); + + if ( IsFunc(id->Type()->Tag()) ) + { + string s = comments[i]; + + if ( broxygen::prettify_params(s) ) + d.NL(); + + d.Add(s.c_str()); + } + else + d.Add(comments[i].c_str()); + } + + return d.Description(); + } + +time_t IdentifierInfo::DoGetModificationTime() const + { + // Could probably get away with just checking the set of scripts that + // contributed to the ID declaration/redefinitions, but this is easier... + return declaring_script->GetModificationTime(); + } diff --git a/src/broxygen/IdentifierInfo.h b/src/broxygen/IdentifierInfo.h new file mode 100644 index 0000000000..322f776ee6 --- /dev/null +++ b/src/broxygen/IdentifierInfo.h @@ -0,0 +1,162 @@ +#ifndef BROXYGEN_IDENTIFIERINFO_H +#define BROXYGEN_IDENTIFIERINFO_H + +#include "Info.h" +#include "ScriptInfo.h" + +#include "ID.h" +#include "Type.h" + +#include +#include +#include +#include + +namespace broxygen { + +class ScriptInfo; + +/** + * Information regarding a script-level identifier and its documentation. + */ +class IdentifierInfo : public Info { + +public: + + /** + * Create a new identifier info object. + * @param id The script-level identifier. + * @param script The info object associated with the script in which \a id + * is declared. + */ + IdentifierInfo(ID* id, ScriptInfo* script); + + /** + * Dtor. Releases any references to script-level objects. + */ + ~IdentifierInfo(); + + /** + * Add a comment associated with the identifier. If the identifier is a + * record type and it's in the middle of parsing fields, the comment is + * associated with the last field that was parsed. + * @param comment A string extracted from Broxygen-style comment. + */ + void AddComment(const std::string& comment) + { last_field_seen ? last_field_seen->comments.push_back(comment) + : comments.push_back(comment); } + + /** + * Associate several comments with the identifier. They will be appended + * to the end of the list of any current comments. + * @param cmtns A vector of comments to associate. + */ + void AddComments(const std::vector& cmtns) + { comments.insert(comments.end(), cmtns.begin(), cmtns.end()); } + + /** + * Register a redefinition of the identifier. + * @param from_script The script in which the redef occurred. + * @param comments Comments associated with the redef statement. + */ + void AddRedef(const std::string& from_script, + const std::vector& comments); + + /** + * Register a record field associated with the identifier + * (which is implicitly a record type). + * @param field The name/type information of the field. + * @param script The script in which the field was declared. This may + * differ from the script in which a record type is declared due to redefs. + * @param comments Comments associated with the record field. + */ + void AddRecordField(const TypeDecl* field, const std::string& script, + std::vector& comments); + + /** + * Signals that a record type has been completely parsed. This resets + * internal tracking of the last record field seen so that "##<"-style + * comments are correctly associated. + */ + void CompletedTypeDecl() + { last_field_seen = 0; } + + /** + * @return the script-level ID tracked by this info object. + */ + ID* GetID() const + { return id; } + + /** + * @return The script which declared the script-level identifier. + */ + ScriptInfo* GetDeclaringScript() const + { return declaring_script; } + + /** + * @param field A record field name. + * @return The script which declared the record field name. + */ + std::string GetDeclaringScriptForField(const std::string& field) const; + + /** + * @return All Broxygen comments associated with the identifier. + */ + std::vector GetComments() const; + + /** + * @param field A record field name. + * @return All Broxygen comments associated with the record field. + */ + std::vector GetFieldComments(const std::string& field) const; + + /** + * Tracks useful information related to a redef. + */ + struct Redefinition { + std::string from_script; /**< Name of script doing the redef. */ + std::string new_val_desc; /**< Description of new value bound to ID. */ + std::vector comments; /**< Broxygen comments on redef. */ + }; + + /** + * Get a list of information about redefinitions of the identifier within + * a particular script. + * @param from_script The name of a script in which to look for redefs. + * @return A list of redefs that occurred in \a from_script. + */ + std::list GetRedefs(const std::string& from_script) const; + +private: + + time_t DoGetModificationTime() const; + + std::string DoName() const + { return id->Name(); } + + std::string DoReStructuredText(bool roles_only) const; + + struct RecordField { + ~RecordField() + { delete field; } + + TypeDecl* field; + std::string from_script; + std::vector comments; + }; + + typedef std::list redef_list; + typedef std::map record_field_map; + + std::vector comments; + ID* id; + std::string initial_val_desc; + redef_list redefs; + record_field_map fields; + RecordField* last_field_seen; + ScriptInfo* declaring_script; +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/Info.h b/src/broxygen/Info.h new file mode 100644 index 0000000000..cd7282a293 --- /dev/null +++ b/src/broxygen/Info.h @@ -0,0 +1,71 @@ +#ifndef BROXYGEN_INFO_H +#define BROXYGEN_INFO_H + +#include +#include + +namespace broxygen { + +/** + * Abstract base class for any thing that Broxygen can document. + */ +class Info { + +public: + + /** + * Ctor. + */ + Info() + { } + + /** + * Dtor. + */ + virtual ~Info() + { } + + /** + * @return The time any information related to the object was last modified. + */ + time_t GetModificationTime() const + { return DoGetModificationTime(); } + + /** + * @return A unique name for the documentable object. + */ + std::string Name() const + { return DoName(); } + + /** + * Get a reST representation of the object and any associated documentation. + * @param roles_only True if the reST should only use cross-referencing role + * syntax to refer itself instead of using a directive (which declares this + * reST the authoritative "anchor" for cross-references). + * @return A reST representation of the object and associated documentation. + */ + std::string ReStructuredText(bool roles_only = false) const + { return DoReStructuredText(roles_only); } + + /** + * Perform any remaining info gathering/initialization that can only be done + * after all script parsing is complete. + */ + void InitPostScript() + { DoInitPostScript(); } + +private: + + virtual time_t DoGetModificationTime() const = 0; + + virtual std::string DoName() const = 0; + + virtual std::string DoReStructuredText(bool roles_only) const = 0; + + virtual void DoInitPostScript() + { } +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/Manager.cc b/src/broxygen/Manager.cc index 68e4c07b98..b923efe61f 100644 --- a/src/broxygen/Manager.cc +++ b/src/broxygen/Manager.cc @@ -27,7 +27,7 @@ static string RemoveLeadingSpace(const string& s) Manager::Manager(const string& arg_config, const string& bro_command) : disabled(), comment_buffer(), comment_buffer_map(), packages(), scripts(), - identifiers(), all_docs(), last_identifier_seen(), incomplete_type(), + identifiers(), all_info(), last_identifier_seen(), incomplete_type(), enum_mappings(), config(arg_config), bro_mtime() { if ( getenv("BRO_DISABLE_BROXYGEN") ) @@ -45,8 +45,8 @@ Manager::Manager(const string& arg_config, const string& bro_command) Manager::~Manager() { - for ( size_t i = 0; i < all_docs.size(); ++i ) - delete all_docs[i]; + for ( size_t i = 0; i < all_info.size(); ++i ) + delete all_info[i]; } void Manager::InitPreScript() @@ -60,10 +60,10 @@ void Manager::InitPostScript() if ( disabled ) return; - for ( size_t i = 0; i < all_docs.size(); ++i ) - all_docs[i]->InitPostScript(); + for ( size_t i = 0; i < all_info.size(); ++i ) + all_info[i]->InitPostScript(); - config.FindDependencies(all_docs); + config.FindDependencies(all_info); } void Manager::GenerateDocs() const @@ -74,39 +74,39 @@ void Manager::GenerateDocs() const config.GenerateDocs(); } -void Manager::File(const string& path) +void Manager::Script(const string& path) { if ( disabled ) return; string name = without_bropath_component(path); - if ( scripts.GetDocument(name) ) + if ( scripts.GetInfo(name) ) { DbgAndWarn(fmt("Duplicate script documentation: %s", name.c_str())); return; } - ScriptDocument* doc = new ScriptDocument(name, path); - scripts.map[name] = doc; - all_docs.push_back(doc); - DBG_LOG(DBG_BROXYGEN, "Made ScriptDocument %s", name.c_str()); + ScriptInfo* info = new ScriptInfo(name, path); + scripts.map[name] = info; + all_info.push_back(info); + DBG_LOG(DBG_BROXYGEN, "Made ScriptInfo %s", name.c_str()); - if ( ! doc->IsPkgLoader() ) + if ( ! info->IsPkgLoader() ) return; name = SafeDirname(name).result; - if ( packages.GetDocument(name) ) + if ( packages.GetInfo(name) ) { DbgAndWarn(fmt("Duplicate package documentation: %s", name.c_str())); return; } - PackageDocument* pkgdoc = new PackageDocument(name); - packages.map[name] = pkgdoc; - all_docs.push_back(pkgdoc); - DBG_LOG(DBG_BROXYGEN, "Made PackageDocument %s", name.c_str()); + PackageInfo* pkginfo = new PackageInfo(name); + packages.map[name] = pkginfo; + all_info.push_back(pkginfo); + DBG_LOG(DBG_BROXYGEN, "Made PackageInfo %s", name.c_str()); } void Manager::ScriptDependency(const string& path, const string& dep) @@ -122,16 +122,16 @@ void Manager::ScriptDependency(const string& path, const string& dep) string name = without_bropath_component(path); string depname = without_bropath_component(dep); - ScriptDocument* script_doc = scripts.GetDocument(name); + ScriptInfo* script_info = scripts.GetInfo(name); - if ( ! script_doc ) + if ( ! script_info ) { DbgAndWarn(fmt("Failed to add script doc dependency %s for %s", depname.c_str(), name.c_str())); return; } - script_doc->AddDependency(depname); + script_info->AddDependency(depname); DBG_LOG(DBG_BROXYGEN, "Added script dependency %s for %s", depname.c_str(), name.c_str()); @@ -146,23 +146,23 @@ void Manager::ModuleUsage(const string& path, const string& module) return; string name = without_bropath_component(path); - ScriptDocument* script_doc = scripts.GetDocument(name); + ScriptInfo* script_info = scripts.GetInfo(name); - if ( ! script_doc ) + if ( ! script_info ) { DbgAndWarn(fmt("Failed to add module usage %s in %s", module.c_str(), name.c_str())); return; } - script_doc->AddModule(module); + script_info->AddModule(module); DBG_LOG(DBG_BROXYGEN, "Added module usage %s in %s", module.c_str(), name.c_str()); } -IdentifierDocument* Manager::CreateIdentifierDoc(ID* id, ScriptDocument* script) +IdentifierInfo* Manager::CreateIdentifierInfo(ID* id, ScriptInfo* script) { - IdentifierDocument* rval = new IdentifierDocument(id, script); + IdentifierInfo* rval = new IdentifierInfo(id, script); rval->AddComments(comment_buffer); comment_buffer.clear(); @@ -176,12 +176,12 @@ IdentifierDocument* Manager::CreateIdentifierDoc(ID* id, ScriptDocument* script) comment_buffer_map.erase(it); } - all_docs.push_back(rval); + all_info.push_back(rval); identifiers.map[id->Name()] = rval; last_identifier_seen = rval; if ( script ) - script->AddIdentifierDoc(rval); + script->AddIdentifierInfo(rval); return rval; } @@ -198,17 +198,17 @@ void Manager::StartType(ID* id) } string script = without_bropath_component(id->GetLocationInfo()->filename); - ScriptDocument* script_doc = scripts.GetDocument(script); + ScriptInfo* script_info = scripts.GetInfo(script); - if ( ! script_doc ) + if ( ! script_info ) { DbgAndWarn(fmt("Can't document identifier %s, lookup of %s failed", id->Name(), script.c_str())); return; } - incomplete_type = CreateIdentifierDoc(id, script_doc); - DBG_LOG(DBG_BROXYGEN, "Made IdentifierDocument (incomplete) %s, in %s", + incomplete_type = CreateIdentifierInfo(id, script_info); + DBG_LOG(DBG_BROXYGEN, "Made IdentifierInfo (incomplete) %s, in %s", id->Name(), script.c_str()); } @@ -236,14 +236,14 @@ void Manager::Identifier(ID* id) enum_mappings[id->Name()] = incomplete_type->GetID()->Name(); } - IdentifierDocument* id_doc = identifiers.GetDocument(id->Name()); + IdentifierInfo* id_info = identifiers.GetInfo(id->Name()); - if ( id_doc ) + if ( id_info ) { - if ( IsFunc(id_doc->GetID()->Type()->Tag()) ) + if ( IsFunc(id_info->GetID()->Type()->Tag()) ) { // Function may already been seen (declaration versus body). - id_doc->AddComments(comment_buffer); + id_info->AddComments(comment_buffer); comment_buffer.clear(); return; } @@ -256,24 +256,24 @@ void Manager::Identifier(ID* id) { // Internally-created identifier (e.g. file/proto analyzer enum tags). // Handled specially since they don't have a script location. - DBG_LOG(DBG_BROXYGEN, "Made internal IdentifierDocument %s", + DBG_LOG(DBG_BROXYGEN, "Made internal IdentifierInfo %s", id->Name()); - CreateIdentifierDoc(id, 0); + CreateIdentifierInfo(id, 0); return; } string script = without_bropath_component(id->GetLocationInfo()->filename); - ScriptDocument* script_doc = scripts.GetDocument(script); + ScriptInfo* script_info = scripts.GetInfo(script); - if ( ! script_doc ) + if ( ! script_info ) { DbgAndWarn(fmt("Can't document identifier %s, lookup of %s failed", id->Name(), script.c_str())); return; } - CreateIdentifierDoc(id, script_doc); - DBG_LOG(DBG_BROXYGEN, "Made IdentifierDocument %s, in script %s", + CreateIdentifierInfo(id, script_info); + DBG_LOG(DBG_BROXYGEN, "Made IdentifierInfo %s, in script %s", id->Name(), script.c_str()); } @@ -283,7 +283,7 @@ void Manager::RecordField(const ID* id, const TypeDecl* field, if ( disabled ) return; - IdentifierDocument* idd = identifiers.GetDocument(id->Name()); + IdentifierInfo* idd = identifiers.GetInfo(id->Name()); if ( ! idd ) { @@ -308,9 +308,9 @@ void Manager::Redef(const ID* id, const string& path) // This is a redef defined on the command line. return; - IdentifierDocument* id_doc = identifiers.GetDocument(id->Name()); + IdentifierInfo* id_info = identifiers.GetInfo(id->Name()); - if ( ! id_doc ) + if ( ! id_info ) { DbgAndWarn(fmt("Can't document redef of %s, identifier lookup failed", id->Name())); @@ -318,19 +318,19 @@ void Manager::Redef(const ID* id, const string& path) } string from_script = without_bropath_component(path); - ScriptDocument* script_doc = scripts.GetDocument(from_script); + ScriptInfo* script_info = scripts.GetInfo(from_script); - if ( ! script_doc ) + if ( ! script_info ) { DbgAndWarn(fmt("Can't document redef of %s, lookup of %s failed", id->Name(), from_script.c_str())); return; } - id_doc->AddRedef(from_script, comment_buffer); - script_doc->AddRedef(id_doc); + id_info->AddRedef(from_script, comment_buffer); + script_info->AddRedef(id_info); comment_buffer.clear(); - last_identifier_seen = id_doc; + last_identifier_seen = id_info; DBG_LOG(DBG_BROXYGEN, "Added redef of %s from %s", id->Name(), from_script.c_str()); } @@ -341,10 +341,10 @@ void Manager::SummaryComment(const string& script, const string& comment) return; string name = without_bropath_component(script); - ScriptDocument* doc = scripts.GetDocument(name); + ScriptInfo* info = scripts.GetInfo(name); - if ( doc ) - doc->AddComment(RemoveLeadingSpace(comment)); + if ( info ) + info->AddComment(RemoveLeadingSpace(comment)); else DbgAndWarn(fmt("Lookup of script %s failed for summary comment %s", name.c_str(), comment.c_str())); diff --git a/src/broxygen/Manager.h b/src/broxygen/Manager.h index 42b9ea828a..bb67cf35cf 100644 --- a/src/broxygen/Manager.h +++ b/src/broxygen/Manager.h @@ -2,7 +2,10 @@ #define BROXYGEN_MANAGER_H #include "Configuration.h" -#include "Document.h" +#include "Info.h" +#include "PackageInfo.h" +#include "ScriptInfo.h" +#include "IdentifierInfo.h" #include "Reporter.h" #include "ID.h" @@ -17,13 +20,20 @@ namespace broxygen { -// TODO: documentation... - +/** + * Map of info objects. Just a wrapper around std::map to improve code + * readability (less typedefs for specific map types and not having to use + * iterators directly to find a particular info object). + */ template -struct DocumentMap { +struct InfoMap { typedef std::map map_type; - T* GetDocument(const std::string& name) const + /** + * @param name Name of an info object to retrieve. + * @return The info object associated with \a name. + */ + T* GetInfo(const std::string& name) const { typename map_type::const_iterator it = map.find(name); return it == map.end() ? 0 : it->second; @@ -32,53 +42,165 @@ struct DocumentMap { map_type map; }; +/** + * Manages all documentation tracking and generation. + */ class Manager { public: + /** + * Ctor. + * @param config Path to a Broxygen config file if documentation is to be + * written to disk. + * @param bro_command The command used to invoke the bro process. + * It's used when checking for out-of-date targets. If the bro binary is + * newer then a target, it needs to be rebuilt. + */ Manager(const std::string& config, const std::string& bro_command); + /** + * Dtor. + */ ~Manager(); + /** + * Do initialization that needs to happen before scripts are parsed. + * Currently nothing outside of what's done in ctor is needed. + */ void InitPreScript(); + /** + * Do initialization that needs to happen after scripts are parsed. + * This is primarly dependency resolution/filtering. + */ void InitPostScript(); + /** + * Builds all Broxygen targets specified by config file and write out + * documentation to disk. + */ void GenerateDocs() const; - void File(const std::string& path); + /** + * Register Bro script for which information/documentation will be gathered. + * @param path Absolute path to Bro script. + */ + void Script(const std::string& path); + /** + * Register Bro script dependency ("@load"). + * @param path Absolute path to a Bro script. + * @param dep Absolute path to a Bro script being "@load"d from script given + * by \a path. + */ void ScriptDependency(const std::string& path, const std::string& dep); + /** + * Register a module usage (script may export identifiers in to the + * module namespace). + * @param path Absolute path to a Bro script. + * @param module The module which script given by \a path is using. + */ void ModuleUsage(const std::string& path, const std::string& module); + /** + * Signal that a record or enum type is now being parsed. + * @param id The record or enum type identifier. + */ void StartType(ID* id); + /** + * Register a script-level identifier for which information/documentation + * will be gathered. + * @param id The script-level identifier. + */ void Identifier(ID* id); + /** + * Register a record-field for which information/documentation will be + * gathered. + * @param id The identifier of the record type which has the field. + * @param field The field name/type information. + * @param path Absolute path to a Bro script in which this field is + * declared. This can be different from the place where the record type + * is declared due to redefs. + */ void RecordField(const ID* id, const TypeDecl* field, const std::string& path); + /** + * Register a redefinition of a particular identifier. + * @param id The identifier being redef'd. + * @param path Absolute path to a Bro script doing the redef. + */ void Redef(const ID* id, const std::string& path); + /** + * Register Broxygen script summary content. + * @param path Absolute path to a Bro script. + * @param comment Broxygen-style summary comment ("##!") to associate with + * script given by \a path. + */ void SummaryComment(const std::string& path, const std::string& comment); + /** + * Register a Broxygen comment ("##") for an upcoming identifier (i.e. + * this content is buffered and consumed by next identifier/field + * declaration. + * @param comment Content of the Broxygen comment. + */ void PreComment(const std::string& comment); + /** + * Register a Broxygen comment ("##<") for the last identifier seen. + * @param comment Content of the Broxygen comment. + * @param identifier_hint Expected name of identifier with which to + * associate \a comment. + */ void PostComment(const std::string& comment, const std::string& identifier_hint = ""); + /** + * @param id Name of script-level enum identifier. + * @return The name of the enum's type. + */ std::string GetEnumTypeName(const std::string& id) const; - IdentifierDocument* GetIdentifierDoc(const std::string& name) const - { return identifiers.GetDocument(name); } + /** + * @param name Name of a script-level identifier. + * @return an identifier info object associated with \a name or a null + * pointer if it's not a known identifier. + */ + IdentifierInfo* GetIdentifierInfo(const std::string& name) const + { return identifiers.GetInfo(name); } - ScriptDocument* GetScriptDoc(const std::string& name) const - { return scripts.GetDocument(name); } + /** + * @param name Name of a Bro script ("normalized" to be a path relative + * to a component within BROPATH). + * @return a script info object associated with \a name or a null pointer + * if it's not a known script name. + */ + ScriptInfo* GetScriptInfo(const std::string& name) const + { return scripts.GetInfo(name); } - PackageDocument* GetPackageDoc(const std::string& name) const - { return packages.GetDocument(name); } + /** + * @param name Nmae of a Bro script package ("normalized" to be a path + * relative to a component within BROPATH). + * @return a package info object assocated with \a name or a null pointer + * if it's not a known package name. + */ + PackageInfo* GetPackageInfo(const std::string& name) const + { return packages.GetInfo(name); } + /** + * Check if a Broxygen target is up-to-date. + * @param target_file output file of a Broxygen target. + * @param dependencies all dependencies of the target. + * @return true if modification time of \a target_file is newer than + * modification time of Bro binary, Broxygen config file, and all + * dependencies, else false. + */ template bool IsUpToDate(const std::string& target_file, const std::vector& dependencies) const; @@ -88,18 +210,18 @@ private: typedef std::vector comment_buffer_t; typedef std::map comment_buffer_map_t; - IdentifierDocument* CreateIdentifierDoc(ID* id, ScriptDocument* script); + IdentifierInfo* CreateIdentifierInfo(ID* id, ScriptInfo* script); bool disabled; - comment_buffer_t comment_buffer; // For whatever next identifier that comes in. + comment_buffer_t comment_buffer; // For whatever next identifier comes in. comment_buffer_map_t comment_buffer_map; // For a particular identifier. - DocumentMap packages; - DocumentMap scripts; - DocumentMap identifiers; - std::vector all_docs; - IdentifierDocument* last_identifier_seen; - IdentifierDocument* incomplete_type; - std::map enum_mappings; + InfoMap packages; + InfoMap scripts; + InfoMap identifiers; + std::vector all_info; + IdentifierInfo* last_identifier_seen; + IdentifierInfo* incomplete_type; + std::map enum_mappings; // enum id -> enum type id Config config; time_t bro_mtime; }; diff --git a/src/broxygen/PackageInfo.cc b/src/broxygen/PackageInfo.cc new file mode 100644 index 0000000000..c01182dfeb --- /dev/null +++ b/src/broxygen/PackageInfo.cc @@ -0,0 +1,55 @@ +#include "PackageInfo.h" +#include "utils.h" + +#include "Reporter.h" + +#include + +using namespace std; +using namespace broxygen; + +PackageInfo::PackageInfo(const string& arg_name) + : Info(), + pkg_name(arg_name), readme() + { + string readme_file = find_file(pkg_name + "/README", bro_path()); + + if ( readme_file.empty() ) + return; + + ifstream f(readme_file.c_str()); + + if ( ! f.is_open() ) + reporter->InternalWarning("Broxygen failed to open '%s': %s", + readme_file.c_str(), strerror(errno)); + + string line; + + while ( getline(f, line) ) + readme.push_back(line); + + if ( f.bad() ) + reporter->InternalWarning("Broxygen error reading '%s': %s", + readme_file.c_str(), strerror(errno)); + } + +string PackageInfo::DoReStructuredText(bool roles_only) const + { + string rval = fmt(":doc:`%s `\n\n", pkg_name.c_str(), + pkg_name.c_str()); + + for ( size_t i = 0; i < readme.size(); ++i ) + rval += " " + readme[i] + "\n"; + + return rval; + } + +time_t PackageInfo::DoGetModificationTime() const + { + string readme_file = find_file(pkg_name + "/README", bro_path()); + + if ( readme_file.empty() ) + return 0; + + return broxygen::get_mtime(readme_file); + } diff --git a/src/broxygen/PackageInfo.h b/src/broxygen/PackageInfo.h new file mode 100644 index 0000000000..fe0e3c29a7 --- /dev/null +++ b/src/broxygen/PackageInfo.h @@ -0,0 +1,48 @@ +#ifndef BROXYGEN_PACKAGEINFO_H +#define BROXYGEN_PACKAGEINFO_H + +#include "Info.h" + +#include +#include + +namespace broxygen { + +/** + * Information about a Bro script package. + */ +class PackageInfo : public Info { + +public: + + /** + * Ctor. + * @param name The name of the Bro script package (relative path from a + * component within BROPATH. + */ + PackageInfo(const std::string& name); + + /** + * @return The content of the package's README file, each line being + * an element in the returned vector. If the package has no README, the + * vector is empty. + */ + std::vector GetReadme() const + { return readme; } + +private: + + time_t DoGetModificationTime() const; + + std::string DoName() const + { return pkg_name; } + + std::string DoReStructuredText(bool roles_only) const; + + std::string pkg_name; + std::vector readme; +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/ReStructuredTextTable.cc b/src/broxygen/ReStructuredTextTable.cc new file mode 100644 index 0000000000..c0e3b1920a --- /dev/null +++ b/src/broxygen/ReStructuredTextTable.cc @@ -0,0 +1,66 @@ +#include "ReStructuredTextTable.h" + +#include + +using namespace std; +using namespace broxygen; + +ReStructuredTextTable::ReStructuredTextTable(size_t arg_num_cols) + : num_cols(arg_num_cols), rows(), longest_row_in_column() + { + for ( size_t i = 0; i < num_cols; ++i ) + longest_row_in_column.push_back(1); + } + +void ReStructuredTextTable::AddRow(const vector& new_row) + { + assert(new_row.size() == num_cols); + rows.push_back(new_row); + + for ( size_t i = 0; i < new_row.size(); ++i ) + if ( new_row[i].size() > longest_row_in_column[i] ) + longest_row_in_column[i] = new_row[i].size(); + } + +string ReStructuredTextTable::MakeBorder(const vector col_sizes, + char border) + { + string rval; + + for ( size_t i = 0; i < col_sizes.size(); ++i ) + { + if ( i > 0 ) + rval += " "; + + rval += string(col_sizes[i], border); + } + + rval += "\n"; + return rval; + } + +string ReStructuredTextTable::AsString(char border) const + { + string rval = MakeBorder(longest_row_in_column, border); + + for ( size_t row = 0; row < rows.size(); ++row ) + { + for ( size_t col = 0; col < num_cols; ++col ) + { + if ( col > 0 ) + { + size_t last = rows[row][col - 1].size(); + size_t longest = longest_row_in_column[col - 1]; + size_t whitespace = longest - last + 1; + rval += string(whitespace, ' '); + } + + rval += rows[row][col]; + } + + rval += "\n"; + } + + rval += MakeBorder(longest_row_in_column, border); + return rval; + } diff --git a/src/broxygen/ReStructuredTextTable.h b/src/broxygen/ReStructuredTextTable.h new file mode 100644 index 0000000000..c00c2191fe --- /dev/null +++ b/src/broxygen/ReStructuredTextTable.h @@ -0,0 +1,51 @@ +#ifndef BROXYGEN_RESTTABLE_H +#define BROXYGEN_RESTTABLE_H + +#include +#include + +namespace broxygen { + +/** + * A reST table with arbitrary number of columns. + */ +class ReStructuredTextTable { +public: + + /** + * Create the reST table object. + * @param arg_num_cols The number of columns in the table. + */ + ReStructuredTextTable(size_t arg_num_cols); + + /** + * Add a new content row to the table. + * @param new_row A vector with one element for each column in the table. + */ + void AddRow(const std::vector& new_row); + + /** + * @param col_sizes Vector of column sizes (width in number of characters). + * @param border Character to use for the border. + * @return A border sized appropriated for the table with columns of sizes + * denoted by \a col_sizes. + */ + static std::string MakeBorder(const std::vector col_sizes, + char border); + + /** + * @param border Character to use for the border. + * @return the reST representation of the table and its content. + */ + std::string AsString(char border) const; + +private: + + size_t num_cols; + std::vector > rows; + std::vector longest_row_in_column; +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/ScriptInfo.cc b/src/broxygen/ScriptInfo.cc new file mode 100644 index 0000000000..c49edff288 --- /dev/null +++ b/src/broxygen/ScriptInfo.cc @@ -0,0 +1,361 @@ +#include "ScriptInfo.h" +#include "IdentifierInfo.h" +#include "ReStructuredTextTable.h" +#include "utils.h" +#include "Manager.h" + +#include "Reporter.h" +#include "Desc.h" + +using namespace std; +using namespace broxygen; + +bool IdInfoComp::operator ()(const IdentifierInfo* lhs, + const IdentifierInfo* rhs) const + { + return lhs->Name() < rhs->Name(); + } + +static vector summary_comment(const vector& cmnts) + { + vector rval; + + for ( size_t i = 0; i < cmnts.size(); ++i ) + { + size_t end = broxygen::end_of_first_sentence(cmnts[i]); + + if ( end == string::npos ) + { + if ( broxygen::is_all_whitespace(cmnts[i]) ) + break; + + rval.push_back(cmnts[i]); + } + else + { + rval.push_back(cmnts[i].substr(0, end + 1)); + break; + } + } + + return rval; + } + +static void add_summary_rows(const ODesc& id_desc, const vector& cmnts, + ReStructuredTextTable* table) + { + vector row; + row.push_back(id_desc.Description()); + + if ( cmnts.empty() ) + { + row.push_back(""); + table->AddRow(row); + return; + } + + row.push_back(cmnts[0]); + table->AddRow(row); + + for ( size_t i = 1; i < cmnts.size(); ++i ) + { + row.clear(); + row.push_back(""); + row.push_back(cmnts[i]); + table->AddRow(row); + } + } + +static string make_summary(const string& heading, char underline, char border, + const id_info_list& id_list) + { + if ( id_list.empty() ) + return ""; + + ReStructuredTextTable table(2); + + for ( id_info_list::const_iterator it = id_list.begin(); + it != id_list.end(); ++it ) + { + ID* id = (*it)->GetID(); + ODesc d; + d.SetQuotes(1); + id->DescribeReSTShort(&d); + add_summary_rows(d, summary_comment((*it)->GetComments()), &table); + } + + return broxygen::make_heading(heading, underline) + table.AsString(border) + + "\n"; + } + +static string make_redef_summary(const string& heading, char underline, + char border, const string& from_script, + const id_info_set& id_set) + { + if ( id_set.empty() ) + return ""; + + ReStructuredTextTable table(2); + + for ( id_info_set::const_iterator it = id_set.begin(); it != id_set.end(); + ++it ) + { + ID* id = (*it)->GetID(); + ODesc d; + d.SetQuotes(1); + id->DescribeReSTShort(&d); + + typedef list redef_list; + redef_list redefs = (*it)->GetRedefs(from_script); + + for ( redef_list::const_iterator iit = redefs.begin(); + iit != redefs.end(); ++iit ) + add_summary_rows(d, summary_comment(iit->comments), &table); + } + + return broxygen::make_heading(heading, underline) + table.AsString(border) + + "\n"; + } + +static string make_details(const string& heading, char underline, + const id_info_list& id_list) + { + if ( id_list.empty() ) + return ""; + + string rval = broxygen::make_heading(heading, underline); + + for ( id_info_list::const_iterator it = id_list.begin(); + it != id_list.end(); ++it ) + { + rval += (*it)->ReStructuredText(); + rval += "\n\n"; + } + + return rval; + } + +static string make_redef_details(const string& heading, char underline, + const id_info_set& id_set) + { + if ( id_set.empty() ) + return ""; + + string rval = broxygen::make_heading(heading, underline); + + for ( id_info_set::const_iterator it = id_set.begin(); + it != id_set.end(); ++it ) + { + rval += (*it)->ReStructuredText(true); + rval += "\n\n"; + } + + return rval; + } + +ScriptInfo::ScriptInfo(const string& arg_name, const string& arg_path) + : Info(), + name(arg_name), path(arg_path), + is_pkg_loader(SafeBasename(name).result == PACKAGE_LOADER), + dependencies(), module_usages(), comments(), id_info(), + options(), constants(), state_vars(), types(), events(), hooks(), + functions(), redefs() + { + } + +void ScriptInfo::AddIdentifierInfo(IdentifierInfo* info) + { + id_info[info->Name()] = info; + } + +void ScriptInfo::DoInitPostScript() + { + for ( id_info_map::const_iterator it = id_info.begin(); + it != id_info.end(); ++it ) + { + IdentifierInfo* info = it->second; + ID* id = info->GetID(); + + if ( ! broxygen::is_public_api(id) ) + continue; + + if ( id->AsType() ) + { + types.push_back(info); + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a type", + id->Name(), name.c_str()); + continue; + } + + if ( IsFunc(id->Type()->Tag()) ) + { + switch ( id->Type()->AsFuncType()->Flavor() ) { + case FUNC_FLAVOR_HOOK: + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a hook", + id->Name(), name.c_str()); + hooks.push_back(info); + break; + case FUNC_FLAVOR_EVENT: + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a event", + id->Name(), name.c_str()); + events.push_back(info); + break; + case FUNC_FLAVOR_FUNCTION: + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a function", + id->Name(), name.c_str()); + functions.push_back(info); + break; + default: + reporter->InternalError("Invalid function flavor"); + break; + } + + continue; + } + + if ( id->IsConst() ) + { + if ( id->FindAttr(ATTR_REDEF) ) + { + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as an option", + id->Name(), name.c_str()); + options.push_back(info); + } + else + { + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a constant", + id->Name(), name.c_str()); + constants.push_back(info); + } + + continue; + } + + if ( id->Type()->Tag() == TYPE_ENUM ) + // Enums are always referenced/documented from the type's + // documentation. + continue; + + DBG_LOG(DBG_BROXYGEN, "Filter id '%s' in '%s' as a state variable", + id->Name(), name.c_str()); + state_vars.push_back(info); + } + } + +vector ScriptInfo::GetComments() const + { + return comments; + } + +string ScriptInfo::DoReStructuredText(bool roles_only) const + { + string rval; + + rval += ":tocdepth: 3\n\n"; + rval += broxygen::make_heading(name, '='); + + for ( string_set::const_iterator it = module_usages.begin(); + it != module_usages.end(); ++it ) + rval += ".. bro:namespace:: " + *it + "\n"; + + rval += "\n"; + + for ( size_t i = 0; i < comments.size(); ++i ) + rval += comments[i] + "\n"; + + rval += "\n"; + + if ( ! module_usages.empty() ) + { + rval += module_usages.size() > 1 ? ":Namespaces: " : ":Namespace: "; + + for ( string_set::const_iterator it = module_usages.begin(); + it != module_usages.end(); ++it ) + { + if ( it != module_usages.begin() ) + rval += ", "; + + rval += *it; + } + + rval += "\n"; + } + + if ( ! dependencies.empty() ) + { + rval += ":Imports: "; + + for ( string_set::const_iterator it = dependencies.begin(); + it != dependencies.end(); ++it ) + { + if ( it != dependencies.begin() ) + rval += ", "; + + string path = find_file(*it, bro_path(), "bro"); + string doc = *it; + + if ( ! path.empty() && is_dir(path.c_str()) ) + // Reference the package. + doc += "/index"; + + rval += fmt(":doc:`%s `", it->c_str(), doc.c_str()); + } + + rval += "\n"; + } + + rval += fmt(":Source File: :download:`/scripts/%s`\n", name.c_str()); + rval += "\n"; + rval += broxygen::make_heading("Summary", '~'); + rval += make_summary("Options", '#', '=', options); + rval += make_summary("Constants", '#', '=', constants); + rval += make_summary("State Variables", '#', '=', state_vars); + rval += make_summary("Types", '#', '=', types); + rval += make_redef_summary("Redefinitions", '#', '=', name, redefs); + rval += make_summary("Events", '#', '=', events); + rval += make_summary("Hooks", '#', '=', hooks); + rval += make_summary("Functions", '#', '=', functions); + rval += "\n"; + rval += broxygen::make_heading("Detailed Interface", '~'); + rval += make_details("Options", '#', options); + rval += make_details("Constants", '#', constants); + rval += make_details("State Variables", '#', state_vars); + rval += make_details("Types", '#', types); + //rval += make_redef_details("Redefinitions", '#', redefs); + rval += make_details("Events", '#', events); + rval += make_details("Hooks", '#', hooks); + rval += make_details("Functions", '#', functions); + + return rval; + } + +time_t ScriptInfo::DoGetModificationTime() const + { + time_t most_recent = broxygen::get_mtime(path); + + for ( string_set::const_iterator it = dependencies.begin(); + it != dependencies.end(); ++it ) + { + Info* info = broxygen_mgr->GetScriptInfo(*it); + + if ( ! info ) + { + string pkg_name = *it + "/" + PACKAGE_LOADER; + info = broxygen_mgr->GetScriptInfo(pkg_name); + + if ( ! info ) + reporter->InternalWarning("Broxygen failed to get mtime of %s", + it->c_str()); + continue; + } + + time_t dep_mtime = info->GetModificationTime(); + + if ( dep_mtime > most_recent ) + most_recent = dep_mtime; + } + + return most_recent; + } + + diff --git a/src/broxygen/ScriptInfo.h b/src/broxygen/ScriptInfo.h new file mode 100644 index 0000000000..3b5cc553de --- /dev/null +++ b/src/broxygen/ScriptInfo.h @@ -0,0 +1,121 @@ +#ifndef BROXYGEN_SCRIPTINFO_H +#define BROXYGEN_SCRIPTINFO_H + +#include "Info.h" +#include "IdentifierInfo.h" + +#include +#include +#include +#include +#include + +namespace broxygen { + +class IdentifierInfo; + +struct IdInfoComp { + bool operator() (const IdentifierInfo* lhs, + const IdentifierInfo* rhs) const; +}; + +typedef std::set id_info_set; +typedef std::list id_info_list; + +/** + * Information about a Bro script. + */ +class ScriptInfo : public Info { + +public: + + /** + * Ctor. + * @param name Name of script: a path relative to a component in BROPATH. + * @param path Absolute path to the script. + */ + ScriptInfo(const std::string& name, const std::string& path); + + /** + * Associate a Broxygen summary comment ("##!") with the script. + * @param comment String extracted from the comment. + */ + void AddComment(const std::string& comment) + { comments.push_back(comment); } + + /** + * Register a dependency on another script. + * @param name Name of a script with this one @loads. This is the + * "normalized" name (a path relative to a component in BROPATH). + */ + void AddDependency(const std::string& name) + { dependencies.insert(name); } + + /** + * Register a module usage (signifying the script may export identifiers + * into that modules namespace). + * @param name The name of the module. + */ + void AddModule(const std::string& name) + { module_usages.insert(name); } + + /** + * Register an identifier declared by this script. + * @param info The identifier info object associated with a script-level + * identifier declared by the script. + */ + void AddIdentifierInfo(IdentifierInfo* info); + + /** + * Register a redef of an identifier done by this script. + * @param info The identifier info object associated with the script-level + * identifier redef'd by the script. + */ + void AddRedef(IdentifierInfo* info) + { redefs.insert(info); } + + /** + * @return Whether the script is a package loader (i.e. "__load__.bro"). + */ + bool IsPkgLoader() const + { return is_pkg_loader; } + + /** + * @return All the scripts Broxygen summary comments. + */ + std::vector GetComments() const; + +private: + + typedef std::map id_info_map; + typedef std::set string_set; + + time_t DoGetModificationTime() const; + + std::string DoName() const + { return name; } + + std::string DoReStructuredText(bool roles_only) const; + + void DoInitPostScript() /* override */; + + std::string name; + std::string path; + bool is_pkg_loader; + string_set dependencies; + string_set module_usages; + std::vector comments; + id_info_map id_info; + id_info_list options; + id_info_list constants; + id_info_list state_vars; + id_info_list types; + id_info_list events; + id_info_list hooks; + id_info_list functions; + id_info_set redefs; +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/Target.cc b/src/broxygen/Target.cc new file mode 100644 index 0000000000..0bbc372724 --- /dev/null +++ b/src/broxygen/Target.cc @@ -0,0 +1,595 @@ +#include "Target.h" +#include "Manager.h" + +#include "util.h" +#include "Reporter.h" +#include "plugin/Manager.h" +#include "analyzer/Manager.h" +#include "analyzer/Component.h" +#include "file_analysis/Manager.h" + +#include +#include +#include +#include + +using namespace std; +using namespace broxygen; + +static void write_plugin_section_heading(FILE* f, const plugin::Plugin* p) + { + string name = p->Name(); + + fprintf(f, "%s\n", name.c_str()); + for ( size_t i = 0; i < name.size(); ++i ) + fprintf(f, "-"); + fprintf(f, "\n\n"); + + fprintf(f, "%s\n\n", p->Description()); + } + +static void write_analyzer_component(FILE* f, const analyzer::Component* c) + { + EnumType* atag = analyzer_mgr->GetTagEnumType(); + string tag = fmt("ANALYZER_%s", c->CanonicalName()); + + if ( atag->Lookup("Analyzer", tag.c_str()) < 0 ) + reporter->InternalError("missing analyzer tag for %s", tag.c_str()); + + fprintf(f, ":bro:enum:`Analyzer::%s`\n\n", tag.c_str()); + } + +static void write_analyzer_component(FILE* f, const file_analysis::Component* c) + { + EnumType* atag = file_mgr->GetTagEnumType(); + string tag = fmt("ANALYZER_%s", c->CanonicalName()); + + if ( atag->Lookup("Files", tag.c_str()) < 0 ) + reporter->InternalError("missing analyzer tag for %s", tag.c_str()); + + fprintf(f, ":bro:enum:`Files::%s`\n\n", tag.c_str()); + } + +static void write_plugin_components(FILE* f, const plugin::Plugin* p) + { + plugin::Plugin::component_list components = p->Components(); + plugin::Plugin::component_list::const_iterator it; + + fprintf(f, "Components\n"); + fprintf(f, "++++++++++\n\n"); + + for ( it = components.begin(); it != components.end(); ++it ) + { + switch ( (*it)->Type() ) { + case plugin::component::ANALYZER: + { + const analyzer::Component* c = + dynamic_cast(*it); + + if ( c ) + write_analyzer_component(f, c); + else + reporter->InternalError("component type mismatch"); + } + break; + + case plugin::component::FILE_ANALYZER: + { + const file_analysis::Component* c = + dynamic_cast(*it); + + if ( c ) + write_analyzer_component(f, c); + else + reporter->InternalError("component type mismatch"); + } + break; + + case plugin::component::READER: + reporter->InternalError("docs for READER component unimplemented"); + + case plugin::component::WRITER: + reporter->InternalError("docs for WRITER component unimplemented"); + + default: + reporter->InternalError("docs for unknown component unimplemented"); + } + } + } + +static void write_plugin_bif_items(FILE* f, const plugin::Plugin* p, + plugin::BifItem::Type t, const string& heading) + { + plugin::Plugin::bif_item_list bifitems = p->BifItems(); + plugin::Plugin::bif_item_list::iterator it = bifitems.begin(); + + while ( it != bifitems.end() ) + { + if ( it->GetType() != t ) + it = bifitems.erase(it); + else + ++it; + } + + if ( bifitems.empty() ) + return; + + fprintf(f, "%s\n", heading.c_str()); + for ( size_t i = 0; i < heading.size(); ++i ) + fprintf(f, "+"); + fprintf(f, "\n\n"); + + for ( it = bifitems.begin(); it != bifitems.end(); ++it ) + { + broxygen::IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo( + it->GetID()); + + if ( doc ) + fprintf(f, "%s\n\n", doc->ReStructuredText().c_str()); + else + reporter->InternalWarning("Broxygen ID lookup failed: %s\n", + it->GetID()); + } + } + +static void WriteAnalyzerTagDefn(FILE* f, const string& module) + { + string tag_id = module + "::Tag"; + + broxygen::IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo(tag_id); + + if ( ! doc ) + reporter->InternalError("Broxygen failed analyzer tag lookup: %s", + tag_id.c_str()); + + fprintf(f, "%s\n", doc->ReStructuredText().c_str()); + } + +static bool ComponentsMatch(const plugin::Plugin* p, plugin::component::Type t, + bool match_empty = false) + { + plugin::Plugin::component_list components = p->Components(); + plugin::Plugin::component_list::const_iterator it; + + if ( components.empty() ) + return match_empty; + + for ( it = components.begin(); it != components.end(); ++it ) + if ( (*it)->Type() != t ) + return false; + + return true; + } + +template +static vector filter_matches(const vector& from, Target* t) + { + vector rval; + + for ( size_t i = 0; i < from.size(); ++i ) + { + T* d = dynamic_cast(from[i]); + + if ( ! d ) + continue; + + if ( t->MatchesPattern(d) ) + { + DBG_LOG(DBG_BROXYGEN, "'%s' matched pattern for target '%s'", + d->Name().c_str(), t->Name().c_str()); + rval.push_back(d); + } + } + + return rval; + } + +TargetFile::TargetFile(const string& arg_name) + : name(arg_name), f() + { + if ( name.find('/') != string::npos ) + { + string dir = SafeDirname(name).result; + + if ( ! ensure_intermediate_dirs(dir.c_str()) ) + reporter->FatalError("Broxygen failed to make dir %s", + dir.c_str()); + } + + f = fopen(name.c_str(), "w"); + + if ( ! f ) + reporter->FatalError("Broxygen failed to open '%s' for writing: %s", + name.c_str(), strerror(errno)); + } + +TargetFile::~TargetFile() + { + if ( f ) + fclose(f); + + DBG_LOG(DBG_BROXYGEN, "Wrote out-of-date target '%s'", name.c_str()); + } + + +Target::Target(const string& arg_name, const string& arg_pattern) + : name(arg_name), pattern(arg_pattern), prefix() + { + size_t pos = pattern.find('*'); + + if ( pos == 0 || pos == string::npos ) + return; + + prefix = pattern.substr(0, pos); + } + +bool Target::MatchesPattern(Info* info) const + { + if ( pattern == "*" ) + return true; + + if ( prefix.empty() ) + return info->Name() == pattern; + + return ! strncmp(info->Name().c_str(), prefix.c_str(), prefix.size()); + } + +void AnalyzerTarget::DoFindDependencies(const std::vector& infos) + { + // TODO: really should add to dependency list the tag type's ID and + // all bif items for matching analyzer plugins, but that's all dependent + // on the bro binary itself, so I'm cheating. + } + +void AnalyzerTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), vector()) ) + return; + + if ( Pattern() != "*" ) + reporter->InternalWarning("Broxygen only implements analyzer target" + " pattern '*'"); + + TargetFile file(Name()); + CreateAnalyzerDoc(file.f); + } + +void ProtoAnalyzerTarget::DoCreateAnalyzerDoc(FILE* f) const + { + fprintf(f, "Protocol Analyzers\n"); + fprintf(f, "==================\n\n"); + fprintf(f, ".. contents::\n"); + fprintf(f, " :depth: 2\n\n"); + + WriteAnalyzerTagDefn(f, "Analyzer"); + + plugin::Manager::plugin_list plugins = plugin_mgr->Plugins(); + plugin::Manager::plugin_list::const_iterator it; + + for ( it = plugins.begin(); it != plugins.end(); ++it ) + { + if ( ! ComponentsMatch(*it, plugin::component::ANALYZER, true) ) + continue; + + write_plugin_section_heading(f, *it); + write_plugin_components(f, *it); + write_plugin_bif_items(f, *it, plugin::BifItem::CONSTANT, + "Options/Constants"); + write_plugin_bif_items(f, *it, plugin::BifItem::GLOBAL, "Globals"); + write_plugin_bif_items(f, *it, plugin::BifItem::TYPE, "Types"); + write_plugin_bif_items(f, *it, plugin::BifItem::EVENT, "Events"); + write_plugin_bif_items(f, *it, plugin::BifItem::FUNCTION, "Functions"); + } + } + +void FileAnalyzerTarget::DoCreateAnalyzerDoc(FILE* f) const + { + fprintf(f, "File Analyzers\n"); + fprintf(f, "==============\n\n"); + fprintf(f, ".. contents::\n"); + fprintf(f, " :depth: 2\n\n"); + + WriteAnalyzerTagDefn(f, "Files"); + + plugin::Manager::plugin_list plugins = plugin_mgr->Plugins(); + plugin::Manager::plugin_list::const_iterator it; + + for ( it = plugins.begin(); it != plugins.end(); ++it ) + { + if ( ! ComponentsMatch(*it, plugin::component::FILE_ANALYZER) ) + continue; + + write_plugin_section_heading(f, *it); + write_plugin_components(f, *it); + write_plugin_bif_items(f, *it, plugin::BifItem::CONSTANT, + "Options/Constants"); + write_plugin_bif_items(f, *it, plugin::BifItem::GLOBAL, "Globals"); + write_plugin_bif_items(f, *it, plugin::BifItem::TYPE, "Types"); + write_plugin_bif_items(f, *it, plugin::BifItem::EVENT, "Events"); + write_plugin_bif_items(f, *it, plugin::BifItem::FUNCTION, "Functions"); + } + } + +void PackageTarget::DoFindDependencies(const vector& infos) + { + pkg_deps = filter_matches(infos, this); + + if ( pkg_deps.empty() ) + reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", + Name().c_str(), Pattern().c_str()); + + for ( size_t i = 0; i < infos.size(); ++i ) + { + ScriptInfo* script = dynamic_cast(infos[i]); + + if ( ! script ) + continue; + + for ( size_t j = 0; j < pkg_deps.size(); ++j ) + { + if ( strncmp(script->Name().c_str(), pkg_deps[j]->Name().c_str(), + pkg_deps[j]->Name().size())) + continue; + + DBG_LOG(DBG_BROXYGEN, "Script %s associated with package %s", + script->Name().c_str(), pkg_deps[j]->Name().c_str()); + pkg_manifest[pkg_deps[j]].push_back(script); + script_deps.push_back(script); + } + } + } + +void PackageTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), script_deps) && + broxygen_mgr->IsUpToDate(Name(), pkg_deps) ) + return; + + TargetFile file(Name()); + + fprintf(file.f, ":orphan:\n\n"); + + for ( manifest_t::const_iterator it = pkg_manifest.begin(); + it != pkg_manifest.end(); ++it ) + { + string header = fmt("Package: %s", it->first->Name().c_str()); + header += "\n" + string(header.size(), '='); + + fprintf(file.f, "%s\n\n", header.c_str()); + + vector readme = it->first->GetReadme(); + + for ( size_t i = 0; i < readme.size(); ++i ) + fprintf(file.f, "%s\n", readme[i].c_str()); + + fprintf(file.f, "\n"); + + for ( size_t i = 0; i < it->second.size(); ++i ) + { + fprintf(file.f, ":doc:`/scripts/%s`\n", + it->second[i]->Name().c_str()); + + vector cmnts = it->second[i]->GetComments(); + + for ( size_t j = 0; j < cmnts.size(); ++j ) + fprintf(file.f, " %s\n", cmnts[j].c_str()); + + fprintf(file.f, "\n"); + } + } + } + +void PackageIndexTarget::DoFindDependencies(const vector& infos) + { + pkg_deps = filter_matches(infos, this); + + if ( pkg_deps.empty() ) + reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", + Name().c_str(), Pattern().c_str()); + } + +void PackageIndexTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), pkg_deps) ) + return; + + TargetFile file(Name()); + + for ( size_t i = 0; i < pkg_deps.size(); ++i ) + fprintf(file.f, "%s\n", pkg_deps[i]->ReStructuredText().c_str()); + } + +void ScriptTarget::DoFindDependencies(const vector& infos) + { + script_deps = filter_matches(infos, this); + + if ( script_deps.empty() ) + reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", + Name().c_str(), Pattern().c_str()); + + if ( ! IsDir() ) + return; + + for ( size_t i = 0; i < script_deps.size(); ++i ) + { + if ( SafeBasename(script_deps[i]->Name()).result == PACKAGE_LOADER ) + { + string pkg_dir = SafeDirname(script_deps[i]->Name()).result; + string target_file = Name() + pkg_dir + "/index.rst"; + Target* t = new PackageTarget(target_file, pkg_dir); + t->FindDependencies(infos); + pkg_deps.push_back(t); + } + } + } + +vector dir_contents_recursive(string dir) + { + vector rval; + struct stat st; + + if ( stat(dir.c_str(), &st) < 0 && errno == ENOENT ) + return rval; + + while ( dir[dir.size() - 1] == '/' ) + dir.erase(dir.size() - 1, 1); + + char* dir_copy = copy_string(dir.c_str()); + char** scan_path = new char*[2]; + scan_path[0] = dir_copy; + scan_path[1] = 0; + + FTS* fts = fts_open(scan_path, FTS_NOCHDIR, 0); + + if ( ! fts ) + { + reporter->Error("fts_open failure: %s", strerror(errno)); + delete [] scan_path; + delete [] dir_copy; + return rval; + } + + FTSENT* n; + + while ( (n = fts_read(fts)) ) + { + if ( n->fts_info & FTS_F ) + rval.push_back(n->fts_path); + } + + if ( errno ) + reporter->Error("fts_read failure: %s", strerror(errno)); + + if ( fts_close(fts) < 0 ) + reporter->Error("fts_close failure: %s", strerror(errno)); + + delete [] scan_path; + delete [] dir_copy; + return rval; + } + +void ScriptTarget::DoGenerate() const + { + if ( IsDir() ) + { + // Target name is a dir, matching scripts are written within that dir + // with a dir tree that parallels the script's BROPATH location. + + set targets; + vector dir_contents = dir_contents_recursive(Name()); + + for ( size_t i = 0; i < script_deps.size(); ++i ) + { + string target_filename = Name() + script_deps[i]->Name() + ".rst"; + targets.insert(target_filename); + vector dep; + dep.push_back(script_deps[i]); + + if ( broxygen_mgr->IsUpToDate(target_filename, dep) ) + continue; + + TargetFile file(target_filename); + + fprintf(file.f, "%s\n", script_deps[i]->ReStructuredText().c_str()); + } + + for ( size_t i = 0; i < pkg_deps.size(); ++i ) + { + targets.insert(pkg_deps[i]->Name()); + pkg_deps[i]->Generate(); + } + + for ( size_t i = 0; i < dir_contents.size(); ++i ) + { + string f = dir_contents[i]; + + if ( targets.find(f) != targets.end() ) + continue; + + if ( unlink(f.c_str()) < 0 ) + reporter->Warning("Failed to unlink %s: %s", f.c_str(), + strerror(errno)); + + DBG_LOG(DBG_BROXYGEN, "Delete stale script file %s", f.c_str()); + } + + return; + } + + // Target is a single file, all matching scripts get written there. + + if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) + return; + + TargetFile file(Name()); + + for ( size_t i = 0; i < script_deps.size(); ++i ) + fprintf(file.f, "%s\n", script_deps[i]->ReStructuredText().c_str()); + } + +void ScriptSummaryTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) + return; + + TargetFile file(Name()); + + for ( size_t i = 0; i < script_deps.size(); ++i ) + { + ScriptInfo* d = dynamic_cast(script_deps[i]); + + if ( ! d ) + continue; + + fprintf(file.f, ":doc:`/scripts/%s`\n", d->Name().c_str()); + + vector cmnts = d->GetComments(); + + for ( size_t i = 0; i < cmnts.size(); ++i ) + fprintf(file.f, " %s\n", cmnts[i].c_str()); + + fprintf(file.f, "\n"); + } + } + +void ScriptIndexTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), script_deps) ) + return; + + TargetFile file(Name()); + + fprintf(file.f, ".. toctree::\n"); + fprintf(file.f, " :maxdepth: 1\n\n"); + + for ( size_t i = 0; i < script_deps.size(); ++i ) + { + ScriptInfo* d = dynamic_cast(script_deps[i]); + + if ( ! d ) + continue; + + fprintf(file.f, " %s \n", d->Name().c_str(), + d->Name().c_str()); + } + } + +void IdentifierTarget::DoFindDependencies(const vector& infos) + { + id_deps = filter_matches(infos, this); + + if ( id_deps.empty() ) + reporter->FatalError("No match for Broxygen target '%s' pattern '%s'", + Name().c_str(), Pattern().c_str()); + } + +void IdentifierTarget::DoGenerate() const + { + if ( broxygen_mgr->IsUpToDate(Name(), id_deps) ) + return; + + TargetFile file(Name()); + + for ( size_t i = 0; i < id_deps.size(); ++i ) + fprintf(file.f, "%s\n\n", id_deps[i]->ReStructuredText().c_str()); + } diff --git a/src/broxygen/Target.h b/src/broxygen/Target.h new file mode 100644 index 0000000000..8fe9d0337e --- /dev/null +++ b/src/broxygen/Target.h @@ -0,0 +1,387 @@ +#ifndef BROXYGEN_TARGET_H +#define BROXYGEN_TARGET_H + +#include "Info.h" +#include "PackageInfo.h" +#include "ScriptInfo.h" +#include "IdentifierInfo.h" + +#include +#include +#include +#include + +namespace broxygen { + +/** + * Helper class to create files in arbitrary file paths and automatically + * close it on destruction. + */ +struct TargetFile { + /** + * Open a file. + * @param arg_name Path to a file to create. It's a fatal error if + * creating it fails. Creating it will also create any intermediate + * directories that don't already exist. + * + */ + TargetFile(const std::string& arg_name); + + /** + * Close the file. + */ + ~TargetFile(); + + std::string name; /**< File name. */ + FILE* f; /**< File stream. */ +}; + +/** + * A Broxygen target abstract base class. A target is generally any portion of + * documentation that Bro can build. It's identified by a type (e.g. script, + * identifier, package), a pattern (e.g. "example.bro", "HTTP::Info"), and + * a path to an output file. + */ +class Target { + +public: + + /** + * Ctor. + * @param arg_name output file name of the target. + * @param arg_pattern pattern of info objects the target depends upon. Only + * exact string and simple prefix matching is currently allowed. + */ + Target(const std::string& arg_name, const std::string& arg_pattern); + + /** + * Dtor. + */ + virtual ~Target() + { } + + /** + * Filter out any dependency information from a set of all known info. + * @param infos All known info objects. + */ + void FindDependencies(const std::vector& infos) + { DoFindDependencies(infos); } + + /** + * Build the target by generating its output file. Implementations may + * not always write to the output file if they determine an existing + * version is already up-to-date. + */ + void Generate() const + { DoGenerate(); } + + /** + * Check if a particular info object matches the target pattern. + * Currently only exact string and simple prefix matching patterns are + * used. E.g. for prefix matching "HTTP::*" or "base/protocols/http*". + * @param info An info object for some thing that is documentable. + * @return true if it matches, else false. + */ + bool MatchesPattern(Info* info) const; + + /** + * @return The output file name of the target. + */ + std::string Name() const + { return name; } + + /** + * @return The pattern string of the target. + */ + std::string Pattern() const + { return pattern; } + +private: + + virtual void DoFindDependencies(const std::vector& infos) = 0; + + virtual void DoGenerate() const = 0; + + std::string name; + std::string pattern; + std::string prefix; +}; + +template +static Target* create_target(const std::string& name, + const std::string& pattern) + { + return new T(name, pattern); + } + +/** + * Factory for creating Target instances. + */ +class TargetFactory { + +public: + + /** + * Register a new target type. + * @param type_name The target type name as it will appear in Broxygen + * config files. + */ + template + void Register(const std::string& type_name) + { + target_creators[type_name] = &create_target; + } + + /** + * Instantiate a target. + * @param type_name The target type name as it appears in Broxygen config + * files. + * @param name The output file name of the target. + * @param pattern The dependency pattern of the target. + * @return A new target instance or a pointer if \a type_name is not + * registered. + */ + Target* Create(const std::string& type_name, + const std::string& name, const std::string& pattern) + { + target_creator_map::const_iterator it = target_creators.find(type_name); + + if ( it == target_creators.end() ) + return 0; + + return it->second(name, pattern); + } + +private: + + typedef Target* (*TargetFactoryFn)(const std::string& name, + const std::string& pattern); + typedef std::map target_creator_map; + target_creator_map target_creators; +}; + +/** + * Target to build analyzer documentation. + */ +class AnalyzerTarget : public Target { +public: + + /** + * Writes out plugin index documentation for all analyzer plugins. + * @param f an open file stream to write docs into. + */ + void CreateAnalyzerDoc(FILE* f) const + { return DoCreateAnalyzerDoc(f); } + +protected: + + typedef void (*doc_creator_fn)(FILE*); + + AnalyzerTarget(const std::string& name, const std::string& pattern) + : Target(name, pattern) + { } + +private: + + void DoFindDependencies(const std::vector& infos); + + void DoGenerate() const; + + virtual void DoCreateAnalyzerDoc(FILE* f) const = 0; +}; + +/** + * Target to build protocol analyzer documentation. + */ +class ProtoAnalyzerTarget : public AnalyzerTarget { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + ProtoAnalyzerTarget(const std::string& name, const std::string& pattern) + : AnalyzerTarget(name, pattern) + { } + +private: + + void DoCreateAnalyzerDoc(FILE* f) const; +}; + +/** + * Target to build file analyzer documentation. + */ +class FileAnalyzerTarget : public AnalyzerTarget { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + FileAnalyzerTarget(const std::string& name, const std::string& pattern) + : AnalyzerTarget(name, pattern) + { } + +private: + + void DoCreateAnalyzerDoc(FILE* f) const; +}; + +/** + * Target to build package documentation. + */ +class PackageTarget : public Target { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + PackageTarget(const std::string& name, const std::string& pattern) + : Target(name, pattern), pkg_deps(), script_deps(), pkg_manifest() + { } + +private: + + void DoFindDependencies(const std::vector& infos); + + void DoGenerate() const; + + std::vector pkg_deps; + std::vector script_deps; + typedef std::map > manifest_t; + manifest_t pkg_manifest; +}; + +/** + * Target to build package index documentation. + */ +class PackageIndexTarget : public Target { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + PackageIndexTarget(const std::string& name, const std::string& pattern) + : Target(name, pattern), pkg_deps() + { } + +private: + + void DoFindDependencies(const std::vector& infos); + + void DoGenerate() const; + + std::vector pkg_deps; +}; + +/** + * Target to build script documentation. + */ +class ScriptTarget : public Target { +public: + + /** + * Ctor. + * @param name Output file name or directory. If it's a directory, + * then one document for each script that matches the pattern is written to + * the directory in a directory structure which mirrors the script's path + * relative to a component in BROPATH. + * @param pattern Dependency pattern. + */ + ScriptTarget(const std::string& name, const std::string& pattern) + : Target(name, pattern), script_deps() + { } + + ~ScriptTarget() + { for ( size_t i = 0; i < pkg_deps.size(); ++i ) delete pkg_deps[i]; } + +protected: + + std::vector script_deps; + +private: + + void DoFindDependencies(const std::vector& infos); + + void DoGenerate() const; + + bool IsDir() const + { return Name()[Name().size() - 1] == '/'; } + + std::vector pkg_deps; +}; + +/** + * Target to build script summary documentation. + */ +class ScriptSummaryTarget : public ScriptTarget { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + ScriptSummaryTarget(const std::string& name, const std::string& pattern) + : ScriptTarget(name, pattern) + { } + +private: + + void DoGenerate() const /* override */; +}; + +/** + * Target to build script index documentation. + */ +class ScriptIndexTarget : public ScriptTarget { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + ScriptIndexTarget(const std::string& name, const std::string& pattern) + : ScriptTarget(name, pattern) + { } + +private: + + void DoGenerate() const /* override */; +}; + +/** + * Target to build identifier documentation. + */ +class IdentifierTarget : public Target { +public: + + /** + * Ctor. + * @param name Output file name. + * @param pattern Dependency pattern. + */ + IdentifierTarget(const std::string& name, const std::string& pattern) + : Target(name, pattern), id_deps() + { } + +private: + + void DoFindDependencies(const std::vector& infos); + + void DoGenerate() const; + + std::vector id_deps; +}; + +} // namespace broxygen + +#endif diff --git a/src/broxygen/broxygen.bif b/src/broxygen/broxygen.bif index 0d346411d0..1229e0317c 100644 --- a/src/broxygen/broxygen.bif +++ b/src/broxygen/broxygen.bif @@ -10,12 +10,17 @@ static StringVal* comments_to_val(const vector& comments) } %%} -# TODO: documentation - +## Retrieve the Broxygen-style comments (``##``) associated with an identifier +## (e.g. a variable or type). +## +## name: a script-level identifier for which to retrieve comments. +## +## Returns: comments associated with *name*. If *name* is not a known +## identifier, an empty string is returned. function get_identifier_comments%(name: string%): string %{ using namespace broxygen; - IdentifierDocument* d = broxygen_mgr->GetIdentifierDoc(name->CheckString()); + IdentifierInfo* d = broxygen_mgr->GetIdentifierInfo(name->CheckString()); if ( ! d ) return new StringVal(""); @@ -23,10 +28,19 @@ function get_identifier_comments%(name: string%): string return comments_to_val(d->GetComments()); %} +## Retrieve the Broxygen-style summary comments (``##!``) associated with +## a Bro script. +## +## name: the name of a Bro script. It must be a relative path to where +## it is located within a particular component of BROPATH and use +## the same file name extension/suffix as the actual file (e.g. ".bro"). +## +## Returns: summary comments associated with script with *name*. If +## *name* is not a known script, an empty string is returned. function get_script_comments%(name: string%): string %{ using namespace broxygen; - ScriptDocument* d = broxygen_mgr->GetScriptDoc(name->CheckString()); + ScriptInfo* d = broxygen_mgr->GetScriptInfo(name->CheckString()); if ( ! d ) return new StringVal(""); @@ -34,10 +48,17 @@ function get_script_comments%(name: string%): string return comments_to_val(d->GetComments()); %} +## Retrieve the contents of a Bro script package's README file. +## +## name: the name of a Bro script package. It must be a relative path +## to where it is located within a particular component of BROPATH. +## +## Returns: contents of the package's README file. If *name* is not a known +## package, an empty string is returned. function get_package_readme%(name: string%): string %{ using namespace broxygen; - PackageDocument* d = broxygen_mgr->GetPackageDoc(name->CheckString()); + PackageInfo* d = broxygen_mgr->GetPackageInfo(name->CheckString()); if ( ! d ) return new StringVal(""); @@ -45,6 +66,14 @@ function get_package_readme%(name: string%): string return comments_to_val(d->GetReadme()); %} +## Retrieve the Broxygen-style comments (``##``) associated with a record field. +## +## name: the name of a record type and a field within it formatted like +## a typical record field access: "$". +## +## Returns: comments associated with the record field. If *name* does +## not point to a known record type or a known field within a record +## type, an empty string is returned. function get_record_field_comments%(name: string%): string %{ using namespace broxygen; @@ -56,7 +85,7 @@ function get_record_field_comments%(name: string%): string string id = accessor.substr(0, i); - IdentifierDocument* d = broxygen_mgr->GetIdentifierDoc(id); + IdentifierInfo* d = broxygen_mgr->GetIdentifierInfo(id); if ( ! d ) return new StringVal(""); diff --git a/src/broxygen/utils.cc b/src/broxygen/utils.cc index ed27219144..5d0b3be91b 100644 --- a/src/broxygen/utils.cc +++ b/src/broxygen/utils.cc @@ -1,5 +1,9 @@ #include "utils.h" +#include "Reporter.h" + +#include + using namespace broxygen; using namespace std; @@ -68,3 +72,55 @@ bool broxygen::prettify_params(string& s) return false; } + +bool broxygen::is_public_api(const ID* id) + { + return (id->Scope() == SCOPE_GLOBAL) || + (id->Scope() == SCOPE_MODULE && id->IsExport()); + } + +time_t broxygen::get_mtime(const string& filename) + { + struct stat s; + + if ( stat(filename.c_str(), &s) < 0 ) + reporter->InternalError("Broxygen failed to stat file '%s': %s", + filename.c_str(), strerror(errno)); + + return s.st_mtime; + } + +string broxygen::make_heading(const string& heading, char underline) + { + return heading + "\n" + string(heading.size(), underline) + "\n"; + } + +size_t broxygen::end_of_first_sentence(const string& s) + { + size_t rval = 0; + + while ( (rval = s.find_first_of('.', rval)) != string::npos ) + { + if ( rval == s.size() - 1 ) + // Period is at end of string. + return rval; + + if ( isspace(s[rval + 1]) ) + // Period has a space after it. + return rval; + + // Period has some non-space character after it, keep looking. + ++rval; + } + + return rval; + } + +bool broxygen::is_all_whitespace(const string& s) + { + for ( size_t i = 0; i < s.size(); ++i ) + if ( ! isspace(s[i]) ) + return false; + + return true; + } diff --git a/src/broxygen/utils.h b/src/broxygen/utils.h index a8ba239802..a11113715a 100644 --- a/src/broxygen/utils.h +++ b/src/broxygen/utils.h @@ -1,12 +1,59 @@ #ifndef BROXYGEN_UTILS_H #define BROXYGEN_UTILS_H +#include "ID.h" + #include namespace broxygen { +/** + * Transform content of a Broxygen comment which may contain function + * parameter or return value documentation to a prettier reST format. + * @param s Content from a Broxygen comment to transform. "id: ..." and + * "Returns: ..." change to ":param id: ..." and ":returns: ...". + * @return Whether any content in \a s was transformed. + */ bool prettify_params(std::string& s); +/** + * Check whether an identifier is part of the "public" interface. + * @param id A script-level identifier. + * @return true if the ID is in the global scope or if it's exported in to + * any modules namespace. + */ +bool is_public_api(const ID* id); + +/** + * Get the modification time of a file or abort if there's an error. + * @param filename Path to a file. + * @return The modification time of \a filename via stat(2). + */ +time_t get_mtime(const std::string& filename); + +/** + * Make a reST-style heading. + * @param heading Content of the heading. + * @param underline Character in which to underline heading content. + * @return underlined heading string. + */ +std::string make_heading(const std::string& heading, char underline); + +/** + * Get the position of the end of the first sentence in a string. + * @param s Any string. + * @return The position which looks like the end of the first sentence in + * \a s or 0 if no such position is found. + */ +size_t end_of_first_sentence(const std::string& s); + +/** + * Check if a string is entirely white space. + * @param s Any string. + * @return True if \a s is nothing but white space, else false. + */ +bool is_all_whitespace(const std::string& s); + } // namespace broxygen #endif diff --git a/src/main.cc b/src/main.cc index cac1acd7b1..d48a931b24 100644 --- a/src/main.cc +++ b/src/main.cc @@ -50,7 +50,6 @@ extern "C" void OPENSSL_add_all_algorithms_conf(void); #include "PersistenceSerializer.h" #include "EventRegistry.h" #include "Stats.h" -#include "BroDoc.h" #include "Brofiler.h" #include "threading/Manager.h" diff --git a/src/scan.l b/src/scan.l index 5b80350111..8cb84b6de3 100644 --- a/src/scan.l +++ b/src/scan.l @@ -564,7 +564,7 @@ static int load_files(const char* orig_file) else file_stack.append(new FileInfo); - broxygen_mgr->File(file_path); + broxygen_mgr->Script(file_path); // "orig_file", could be an alias for yytext, which is ephemeral // and will be zapped after the yy_switch_to_buffer() below.