Internal Broxygen organization/documentation/polish.

This commit is contained in:
Jon Siwek 2013-11-25 14:36:05 -06:00
parent 27138b893a
commit e58865af22
29 changed files with 2461 additions and 3024 deletions

View file

@ -1,667 +0,0 @@
#include <cstdio>
#include <cstdarg>
#include <string>
#include <list>
#include <algorithm>
#include <libgen.h>
#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<std::string>::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 </scripts/%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<std::string>& l)
{
if ( l.empty() )
{
WriteToDoc(f, "\n");
return;
}
std::list<std::string>::const_iterator it;
std::list<std::string>::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<const analyzer::Component*>(*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<const file_analysis::Component*>(*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);
}

View file

@ -1,422 +0,0 @@
#ifndef brodoc_h
#define brodoc_h
#include <cstdio>
#include <cstdarg>
#include <string>
#include <list>
#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<const BroDocObj*> BroDocObjList;
typedef std::map<std::string, BroDocObj*> 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<std::string>& l);
/**
* @see WriteStringList(FILE* f, const char*, const char*,
* const std::list<std::string>&>)
*/
static void WriteStringList(FILE* f, const char* format,
const std::list<std::string>& 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<std::string> modules;
std::list<std::string> summary;
std::list<std::string> imports;
std::list<std::string> 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

View file

@ -1,195 +0,0 @@
#include <cstdio>
#include <string>
#include <list>
#include "ID.h"
#include "BroDocObj.h"
map<string, BroDocObj*> doc_ids = map<string, BroDocObj*>();
BroDocObj* BroDocObj::last = 0;
BroDocObj::BroDocObj(const ID* id, std::list<std::string>*& 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<std::string>::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<std::string>::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<std::string>::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<std::string>::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<std::string>();
reST_doc_strings->splice(reST_doc_strings->end(),
*(o->reST_doc_strings));
}
delete o;
FormulateShortDesc();
}

View file

@ -1,143 +0,0 @@
#ifndef brodocobj_h
#define brodocobj_h
#include <cstdio>
#include <string>
#include <list>
#include <map>
#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<std::string>*& 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<std::string>();
reST_doc_strings->push_back(s);
FormulateShortDesc();
}
static BroDocObj* last;
protected:
std::list<std::string>* reST_doc_strings;
std::list<std::string> 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<string, BroDocObj*> doc_ids;
#endif

View file

@ -250,8 +250,6 @@ set(bro_SRCS
Attr.cc Attr.cc
Base64.cc Base64.cc
BPF_Program.cc BPF_Program.cc
BroDoc.cc
BroDocObj.cc
Brofiler.cc Brofiler.cc
BroString.cc BroString.cc
CCL.cc CCL.cc

View file

@ -1146,8 +1146,8 @@ void RecordType::DescribeFieldsReST(ODesc* d, bool func_args) const
if ( func_args ) if ( func_args )
continue; continue;
using broxygen::IdentifierDocument; using broxygen::IdentifierInfo;
IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc(GetName()); IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo(GetName());
if ( ! doc ) if ( ! doc )
{ {
@ -1498,8 +1498,8 @@ void EnumType::DescribeReST(ODesc* d, bool roles_only) const
else else
d->Add(fmt(".. bro:enum:: %s %s", it->second, GetName().c_str())); d->Add(fmt(".. bro:enum:: %s %s", it->second, GetName().c_str()));
using broxygen::IdentifierDocument; using broxygen::IdentifierInfo;
IdentifierDocument* doc = broxygen_mgr->GetIdentifierDoc(it->second); IdentifierInfo* doc = broxygen_mgr->GetIdentifierInfo(it->second);
if ( ! doc ) if ( ! doc )
{ {
@ -1514,7 +1514,7 @@ void EnumType::DescribeReST(ODesc* d, bool roles_only) const
if ( doc->GetDeclaringScript() ) if ( doc->GetDeclaringScript() )
enum_from_script = doc->GetDeclaringScript()->Name(); 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() ) if ( type_doc && type_doc->GetDeclaringScript() )
type_from_script = type_doc->GetDeclaringScript()->Name(); type_from_script = type_doc->GetDeclaringScript()->Name();

View file

@ -6,9 +6,14 @@ include_directories(BEFORE
) )
set(broxygen_SRCS set(broxygen_SRCS
Configuration.cc
Document.cc
Manager.cc Manager.cc
Info.h
PackageInfo.cc
ScriptInfo.cc
IdentifierInfo.cc
Target.cc
Configuration.cc
ReStructuredTextTable.cc
utils.cc utils.cc
) )

View file

@ -1,5 +1,5 @@
#include "Configuration.h" #include "Configuration.h"
#include "Manager.h" #include "utils.h"
#include "util.h" #include "util.h"
#include "Reporter.h" #include "Reporter.h"
@ -7,417 +7,26 @@
#include <fstream> #include <fstream>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <map>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fts.h>
#include <unistd.h>
using namespace broxygen; using namespace broxygen;
using namespace std; using namespace std;
typedef map<string, Target::factory_fn> target_factory_map; static TargetFactory create_target_factory()
static target_factory_map create_target_factory_map()
{ {
target_factory_map rval; TargetFactory rval;
rval["package_index"] = &PackageIndexTarget::Instantiate; rval.Register<PackageIndexTarget>("package_index");
rval["package"] = &PackageTarget::Instantiate; rval.Register<PackageTarget>("package");
rval["proto_analyzer"] = &ProtoAnalyzerTarget::Instantiate; rval.Register<ProtoAnalyzerTarget>("proto_analyzer");
rval["file_analyzer"] = &FileAnalyzerTarget::Instantiate; rval.Register<FileAnalyzerTarget>("file_analyzer");
rval["script_summary"] = &ScriptSummaryTarget::Instantiate; rval.Register<ScriptSummaryTarget>("script_summary");
rval["script_index"] = &ScriptIndexTarget::Instantiate; rval.Register<ScriptIndexTarget>("script_index");
rval["script"] = &ScriptTarget::Instantiate; rval.Register<ScriptTarget>("script");
rval["identifier"] = &IdentifierTarget::Instantiate; rval.Register<IdentifierTarget>("identifier");
return rval; 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<class T>
static vector<T*> filter_matching_docs(const vector<Document*>& from, Target* t)
{
vector<T*> rval;
for ( size_t i = 0; i < from.size(); ++i )
{
T* d = dynamic_cast<T*>(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<Document *>& 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<Document*>()) )
return;
if ( Pattern() != "*" )
reporter->InternalWarning("Broxygen only implements analyzer target"
" pattern '*'");
TargetFile file(Name());
doc_creator_callback(file.f);
}
void PackageTarget::DoFindDependencies(const vector<Document*>& docs)
{
pkg_deps = filter_matching_docs<PackageDocument>(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<ScriptDocument*>(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<string> 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<string> 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<Document*>& docs)
{
pkg_deps = filter_matching_docs<PackageDocument>(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<Document*>& docs)
{
script_deps = filter_matching_docs<ScriptDocument>(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<string> dir_contents_recursive(string dir)
{
vector<string> 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<string> targets;
vector<string> 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<ScriptDocument*> 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<ScriptDocument*>(script_deps[i]);
if ( ! d )
continue;
fprintf(file.f, ":doc:`/scripts/%s`\n", d->Name().c_str());
vector<string> 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<ScriptDocument*>(script_deps[i]);
if ( ! d )
continue;
fprintf(file.f, " %s </scripts/%s>\n", d->Name().c_str(),
d->Name().c_str());
}
}
void IdentifierTarget::DoFindDependencies(const vector<Document*>& docs)
{
id_deps = filter_matching_docs<IdentifierDocument>(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) 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() ) if ( file.empty() )
return; return;
@ -450,14 +59,13 @@ Config::Config(const string& arg_file, const string& delim)
reporter->FatalError("malformed Broxygen target in %s:%u: %s", reporter->FatalError("malformed Broxygen target in %s:%u: %s",
file.c_str(), line_number, line.c_str()); file.c_str(), line_number, line.c_str());
target_factory_map::const_iterator it = Target* target = target_factory.Create(tokens[0], tokens[2], tokens[1]);
target_instantiators.find(tokens[0]);
if ( it == target_instantiators.end() ) if ( ! target )
reporter->FatalError("unkown Broxygen target type: %s", reporter->FatalError("unkown Broxygen target type: %s",
tokens[0].c_str()); tokens[0].c_str());
targets.push_back(it->second(tokens[2], tokens[1])); targets.push_back(target);
} }
if ( f.bad() ) if ( f.bad() )
@ -471,10 +79,10 @@ Config::~Config()
delete targets[i]; delete targets[i];
} }
void Config::FindDependencies(const vector<Document*>& docs) void Config::FindDependencies(const vector<Info*>& infos)
{ {
for ( size_t i = 0; i < targets.size(); ++i ) for ( size_t i = 0; i < targets.size(); ++i )
targets[i]->FindDependencies(docs); targets[i]->FindDependencies(infos);
} }
void Config::GenerateDocs() const void Config::GenerateDocs() const
@ -485,11 +93,8 @@ void Config::GenerateDocs() const
time_t Config::GetModificationTime() const time_t Config::GetModificationTime() const
{ {
struct stat s; if ( file.empty() )
return 0;
if ( stat(file.c_str(), &s) < 0 ) return broxygen::get_mtime(file);
reporter->InternalError("Broxygen can't stat config file %s: %s",
file.c_str(), strerror(errno));
return s.st_mtime;
} }

View file

@ -1,246 +1,61 @@
#ifndef BROXYGEN_CONFIGURATION_H #ifndef BROXYGEN_CONFIGURATION_H
#define BROXYGEN_CONFIGURATION_H #define BROXYGEN_CONFIGURATION_H
#include "Document.h" #include "Info.h"
#include "BroDoc.h" #include "Target.h"
#include <string> #include <string>
#include <vector> #include <vector>
#include <map>
namespace broxygen { namespace broxygen {
// TODO: documentation... /**
* Manages the generation of reStructuredText documents corresponding to
class Target { * particular targets that are specified in a config file. The config file
public: * 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.
typedef Target* (*factory_fn)(const std::string&, const std::string&); */
virtual ~Target()
{ }
void FindDependencies(const std::vector<Document*>& 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<Document*>& 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<Document*>& 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<Document*>& docs);
void DoGenerate() const;
std::vector<PackageDocument*> pkg_deps;
std::vector<ScriptDocument*> script_deps;
typedef std::map<PackageDocument*,std::vector<ScriptDocument*> > 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<Document*>& docs);
void DoGenerate() const;
std::vector<PackageDocument*> 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<ScriptDocument*> script_deps;
private:
void DoFindDependencies(const std::vector<Document*>& docs);
void DoGenerate() const;
bool IsDir() const
{ return Name()[Name().size() - 1] == '/'; }
std::vector<Target*> 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<Document*>& docs);
void DoGenerate() const;
std::vector<IdentifierDocument*> id_deps;
};
class Config { class Config {
public: 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"); Config(const std::string& file, const std::string& delim = "\t");
/**
* Destructor, cleans up targets created when parsing config file.
*/
~Config(); ~Config();
void FindDependencies(const std::vector<Document*>& docs); /**
* Resolves dependency information for each target.
* @param infos All known information objects for documentable things.
*/
void FindDependencies(const std::vector<Info*>& infos);
/**
* Build each Broxygen target (i.e. write out the reST documents to disk).
*/
void GenerateDocs() const; 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; time_t GetModificationTime() const;
private: private:
std::string file; std::string file;
std::vector<Target*> targets; std::vector<Target*> targets;
TargetFactory target_factory;
}; };
} // namespace broxygen } // namespace broxygen
#endif #endif

View file

@ -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 <fstream>
#include <sys/stat.h>
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 </scripts/%s/index>`\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<string>& 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<string>& 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<string> IdentifierDocument::GetComments() const
{
return comments;
}
vector<string> IdentifierDocument::GetFieldComments(const string& field) const
{
record_field_map::const_iterator it = fields.find(field);
if ( it == fields.end() )
return vector<string>();
return it->second->comments;
}
list<IdentifierDocument::Redefinition>
IdentifierDocument::GetRedefs(const string& from_script) const
{
list<Redefinition> 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<string> 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<string> summary_comment(const vector<string>& cmnts)
{
vector<string> 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<string>& 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<size_t> 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<vector<string> > rows;
vector<size_t> longest_row_in_column;
};
static void add_summary_rows(const ODesc& id_desc, const vector<string>& cmnts,
ReStructuredTextTable* table)
{
vector<string> 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<IdentifierDocument*>& id_list)
{
if ( id_list.empty() )
return "";
ReStructuredTextTable table(2);
for ( list<IdentifierDocument*>::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<IdentifierDocument::Redefinition> 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<IdentifierDocument*>& id_list)
{
if ( id_list.empty() )
return "";
string rval = make_heading(heading, underline);
for ( list<IdentifierDocument*>::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 </scripts/%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);
}

View file

@ -1,215 +0,0 @@
#ifndef BROXYGEN_DOCUMENT_H
#define BROXYGEN_DOCUMENT_H
#include <utility>
#include <string>
#include <list>
#include <vector>
#include <set>
#include <map>
#include <ctime>
#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<std::string> 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<std::string> 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<std::string>& cmtns)
{ comments.insert(comments.end(), cmtns.begin(), cmtns.end()); }
void AddRedef(const std::string& from_script,
const std::vector<std::string>& comments);
void AddRecordField(const TypeDecl* field, const std::string& script,
std::vector<std::string>& 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<std::string> GetComments() const;
std::vector<std::string> GetFieldComments(const std::string& field) const;
struct Redefinition {
std::string from_script;
std::string new_val_desc;
std::vector<std::string> comments;
};
std::list<Redefinition> 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<std::string> comments;
};
typedef std::list<Redefinition*> redef_list;
typedef std::map<std::string, RecordField*> record_field_map;
std::vector<std::string> 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<IdentifierDocument*, IdDocComp> 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<std::string> GetComments() const;
private:
typedef std::map<std::string, IdentifierDocument*> id_doc_map;
typedef std::list<IdentifierDocument*> id_doc_list;
typedef std::set<std::string> 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<std::string> 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

View file

@ -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<string>& 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<string>& 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<string> IdentifierInfo::GetComments() const
{
return comments;
}
vector<string> IdentifierInfo::GetFieldComments(const string& field) const
{
record_field_map::const_iterator it = fields.find(field);
if ( it == fields.end() )
return vector<string>();
return it->second->comments;
}
list<IdentifierInfo::Redefinition>
IdentifierInfo::GetRedefs(const string& from_script) const
{
list<Redefinition> 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();
}

View file

@ -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 <string>
#include <vector>
#include <list>
#include <map>
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<std::string>& 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<std::string>& 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<std::string>& 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<std::string> GetComments() const;
/**
* @param field A record field name.
* @return All Broxygen comments associated with the record field.
*/
std::vector<std::string> 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<std::string> 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<Redefinition> 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<std::string> comments;
};
typedef std::list<Redefinition*> redef_list;
typedef std::map<std::string, RecordField*> record_field_map;
std::vector<std::string> 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

71
src/broxygen/Info.h Normal file
View file

@ -0,0 +1,71 @@
#ifndef BROXYGEN_INFO_H
#define BROXYGEN_INFO_H
#include <string>
#include <ctime>
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

View file

@ -27,7 +27,7 @@ static string RemoveLeadingSpace(const string& s)
Manager::Manager(const string& arg_config, const string& bro_command) Manager::Manager(const string& arg_config, const string& bro_command)
: disabled(), comment_buffer(), comment_buffer_map(), packages(), scripts(), : 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() enum_mappings(), config(arg_config), bro_mtime()
{ {
if ( getenv("BRO_DISABLE_BROXYGEN") ) if ( getenv("BRO_DISABLE_BROXYGEN") )
@ -45,8 +45,8 @@ Manager::Manager(const string& arg_config, const string& bro_command)
Manager::~Manager() Manager::~Manager()
{ {
for ( size_t i = 0; i < all_docs.size(); ++i ) for ( size_t i = 0; i < all_info.size(); ++i )
delete all_docs[i]; delete all_info[i];
} }
void Manager::InitPreScript() void Manager::InitPreScript()
@ -60,10 +60,10 @@ void Manager::InitPostScript()
if ( disabled ) if ( disabled )
return; return;
for ( size_t i = 0; i < all_docs.size(); ++i ) for ( size_t i = 0; i < all_info.size(); ++i )
all_docs[i]->InitPostScript(); all_info[i]->InitPostScript();
config.FindDependencies(all_docs); config.FindDependencies(all_info);
} }
void Manager::GenerateDocs() const void Manager::GenerateDocs() const
@ -74,39 +74,39 @@ void Manager::GenerateDocs() const
config.GenerateDocs(); config.GenerateDocs();
} }
void Manager::File(const string& path) void Manager::Script(const string& path)
{ {
if ( disabled ) if ( disabled )
return; return;
string name = without_bropath_component(path); string name = without_bropath_component(path);
if ( scripts.GetDocument(name) ) if ( scripts.GetInfo(name) )
{ {
DbgAndWarn(fmt("Duplicate script documentation: %s", name.c_str())); DbgAndWarn(fmt("Duplicate script documentation: %s", name.c_str()));
return; return;
} }
ScriptDocument* doc = new ScriptDocument(name, path); ScriptInfo* info = new ScriptInfo(name, path);
scripts.map[name] = doc; scripts.map[name] = info;
all_docs.push_back(doc); all_info.push_back(info);
DBG_LOG(DBG_BROXYGEN, "Made ScriptDocument %s", name.c_str()); DBG_LOG(DBG_BROXYGEN, "Made ScriptInfo %s", name.c_str());
if ( ! doc->IsPkgLoader() ) if ( ! info->IsPkgLoader() )
return; return;
name = SafeDirname(name).result; name = SafeDirname(name).result;
if ( packages.GetDocument(name) ) if ( packages.GetInfo(name) )
{ {
DbgAndWarn(fmt("Duplicate package documentation: %s", name.c_str())); DbgAndWarn(fmt("Duplicate package documentation: %s", name.c_str()));
return; return;
} }
PackageDocument* pkgdoc = new PackageDocument(name); PackageInfo* pkginfo = new PackageInfo(name);
packages.map[name] = pkgdoc; packages.map[name] = pkginfo;
all_docs.push_back(pkgdoc); all_info.push_back(pkginfo);
DBG_LOG(DBG_BROXYGEN, "Made PackageDocument %s", name.c_str()); DBG_LOG(DBG_BROXYGEN, "Made PackageInfo %s", name.c_str());
} }
void Manager::ScriptDependency(const string& path, const string& dep) 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 name = without_bropath_component(path);
string depname = without_bropath_component(dep); 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", DbgAndWarn(fmt("Failed to add script doc dependency %s for %s",
depname.c_str(), name.c_str())); depname.c_str(), name.c_str()));
return; return;
} }
script_doc->AddDependency(depname); script_info->AddDependency(depname);
DBG_LOG(DBG_BROXYGEN, "Added script dependency %s for %s", DBG_LOG(DBG_BROXYGEN, "Added script dependency %s for %s",
depname.c_str(), name.c_str()); depname.c_str(), name.c_str());
@ -146,23 +146,23 @@ void Manager::ModuleUsage(const string& path, const string& module)
return; return;
string name = without_bropath_component(path); 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", DbgAndWarn(fmt("Failed to add module usage %s in %s",
module.c_str(), name.c_str())); module.c_str(), name.c_str()));
return; return;
} }
script_doc->AddModule(module); script_info->AddModule(module);
DBG_LOG(DBG_BROXYGEN, "Added module usage %s in %s", DBG_LOG(DBG_BROXYGEN, "Added module usage %s in %s",
module.c_str(), name.c_str()); 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); rval->AddComments(comment_buffer);
comment_buffer.clear(); comment_buffer.clear();
@ -176,12 +176,12 @@ IdentifierDocument* Manager::CreateIdentifierDoc(ID* id, ScriptDocument* script)
comment_buffer_map.erase(it); comment_buffer_map.erase(it);
} }
all_docs.push_back(rval); all_info.push_back(rval);
identifiers.map[id->Name()] = rval; identifiers.map[id->Name()] = rval;
last_identifier_seen = rval; last_identifier_seen = rval;
if ( script ) if ( script )
script->AddIdentifierDoc(rval); script->AddIdentifierInfo(rval);
return rval; return rval;
} }
@ -198,17 +198,17 @@ void Manager::StartType(ID* id)
} }
string script = without_bropath_component(id->GetLocationInfo()->filename); 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", DbgAndWarn(fmt("Can't document identifier %s, lookup of %s failed",
id->Name(), script.c_str())); id->Name(), script.c_str()));
return; return;
} }
incomplete_type = CreateIdentifierDoc(id, script_doc); incomplete_type = CreateIdentifierInfo(id, script_info);
DBG_LOG(DBG_BROXYGEN, "Made IdentifierDocument (incomplete) %s, in %s", DBG_LOG(DBG_BROXYGEN, "Made IdentifierInfo (incomplete) %s, in %s",
id->Name(), script.c_str()); id->Name(), script.c_str());
} }
@ -236,14 +236,14 @@ void Manager::Identifier(ID* id)
enum_mappings[id->Name()] = incomplete_type->GetID()->Name(); 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). // Function may already been seen (declaration versus body).
id_doc->AddComments(comment_buffer); id_info->AddComments(comment_buffer);
comment_buffer.clear(); comment_buffer.clear();
return; return;
} }
@ -256,24 +256,24 @@ void Manager::Identifier(ID* id)
{ {
// Internally-created identifier (e.g. file/proto analyzer enum tags). // Internally-created identifier (e.g. file/proto analyzer enum tags).
// Handled specially since they don't have a script location. // 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()); id->Name());
CreateIdentifierDoc(id, 0); CreateIdentifierInfo(id, 0);
return; return;
} }
string script = without_bropath_component(id->GetLocationInfo()->filename); 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", DbgAndWarn(fmt("Can't document identifier %s, lookup of %s failed",
id->Name(), script.c_str())); id->Name(), script.c_str()));
return; return;
} }
CreateIdentifierDoc(id, script_doc); CreateIdentifierInfo(id, script_info);
DBG_LOG(DBG_BROXYGEN, "Made IdentifierDocument %s, in script %s", DBG_LOG(DBG_BROXYGEN, "Made IdentifierInfo %s, in script %s",
id->Name(), script.c_str()); id->Name(), script.c_str());
} }
@ -283,7 +283,7 @@ void Manager::RecordField(const ID* id, const TypeDecl* field,
if ( disabled ) if ( disabled )
return; return;
IdentifierDocument* idd = identifiers.GetDocument(id->Name()); IdentifierInfo* idd = identifiers.GetInfo(id->Name());
if ( ! idd ) if ( ! idd )
{ {
@ -308,9 +308,9 @@ void Manager::Redef(const ID* id, const string& path)
// This is a redef defined on the command line. // This is a redef defined on the command line.
return; 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", DbgAndWarn(fmt("Can't document redef of %s, identifier lookup failed",
id->Name())); id->Name()));
@ -318,19 +318,19 @@ void Manager::Redef(const ID* id, const string& path)
} }
string from_script = without_bropath_component(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", DbgAndWarn(fmt("Can't document redef of %s, lookup of %s failed",
id->Name(), from_script.c_str())); id->Name(), from_script.c_str()));
return; return;
} }
id_doc->AddRedef(from_script, comment_buffer); id_info->AddRedef(from_script, comment_buffer);
script_doc->AddRedef(id_doc); script_info->AddRedef(id_info);
comment_buffer.clear(); comment_buffer.clear();
last_identifier_seen = id_doc; last_identifier_seen = id_info;
DBG_LOG(DBG_BROXYGEN, "Added redef of %s from %s", DBG_LOG(DBG_BROXYGEN, "Added redef of %s from %s",
id->Name(), from_script.c_str()); id->Name(), from_script.c_str());
} }
@ -341,10 +341,10 @@ void Manager::SummaryComment(const string& script, const string& comment)
return; return;
string name = without_bropath_component(script); string name = without_bropath_component(script);
ScriptDocument* doc = scripts.GetDocument(name); ScriptInfo* info = scripts.GetInfo(name);
if ( doc ) if ( info )
doc->AddComment(RemoveLeadingSpace(comment)); info->AddComment(RemoveLeadingSpace(comment));
else else
DbgAndWarn(fmt("Lookup of script %s failed for summary comment %s", DbgAndWarn(fmt("Lookup of script %s failed for summary comment %s",
name.c_str(), comment.c_str())); name.c_str(), comment.c_str()));

View file

@ -2,7 +2,10 @@
#define BROXYGEN_MANAGER_H #define BROXYGEN_MANAGER_H
#include "Configuration.h" #include "Configuration.h"
#include "Document.h" #include "Info.h"
#include "PackageInfo.h"
#include "ScriptInfo.h"
#include "IdentifierInfo.h"
#include "Reporter.h" #include "Reporter.h"
#include "ID.h" #include "ID.h"
@ -17,13 +20,20 @@
namespace broxygen { 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<class T> template<class T>
struct DocumentMap { struct InfoMap {
typedef std::map<std::string, T*> map_type; typedef std::map<std::string, T*> 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); typename map_type::const_iterator it = map.find(name);
return it == map.end() ? 0 : it->second; return it == map.end() ? 0 : it->second;
@ -32,53 +42,165 @@ struct DocumentMap {
map_type map; map_type map;
}; };
/**
* Manages all documentation tracking and generation.
*/
class Manager { class Manager {
public: 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); Manager(const std::string& config, const std::string& bro_command);
/**
* Dtor.
*/
~Manager(); ~Manager();
/**
* Do initialization that needs to happen before scripts are parsed.
* Currently nothing outside of what's done in ctor is needed.
*/
void InitPreScript(); void InitPreScript();
/**
* Do initialization that needs to happen after scripts are parsed.
* This is primarly dependency resolution/filtering.
*/
void InitPostScript(); void InitPostScript();
/**
* Builds all Broxygen targets specified by config file and write out
* documentation to disk.
*/
void GenerateDocs() const; 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); 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); 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); 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); 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, void RecordField(const ID* id, const TypeDecl* field,
const std::string& path); 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); 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); 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); 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, void PostComment(const std::string& comment,
const std::string& identifier_hint = ""); 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; 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 <class T> template <class T>
bool IsUpToDate(const std::string& target_file, bool IsUpToDate(const std::string& target_file,
const std::vector<T*>& dependencies) const; const std::vector<T*>& dependencies) const;
@ -88,18 +210,18 @@ private:
typedef std::vector<std::string> comment_buffer_t; typedef std::vector<std::string> comment_buffer_t;
typedef std::map<std::string, comment_buffer_t> comment_buffer_map_t; typedef std::map<std::string, comment_buffer_t> comment_buffer_map_t;
IdentifierDocument* CreateIdentifierDoc(ID* id, ScriptDocument* script); IdentifierInfo* CreateIdentifierInfo(ID* id, ScriptInfo* script);
bool disabled; 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. comment_buffer_map_t comment_buffer_map; // For a particular identifier.
DocumentMap<PackageDocument> packages; InfoMap<PackageInfo> packages;
DocumentMap<ScriptDocument> scripts; InfoMap<ScriptInfo> scripts;
DocumentMap<IdentifierDocument> identifiers; InfoMap<IdentifierInfo> identifiers;
std::vector<Document*> all_docs; std::vector<Info*> all_info;
IdentifierDocument* last_identifier_seen; IdentifierInfo* last_identifier_seen;
IdentifierDocument* incomplete_type; IdentifierInfo* incomplete_type;
std::map<std::string, std::string> enum_mappings; std::map<std::string, std::string> enum_mappings; // enum id -> enum type id
Config config; Config config;
time_t bro_mtime; time_t bro_mtime;
}; };

View file

@ -0,0 +1,55 @@
#include "PackageInfo.h"
#include "utils.h"
#include "Reporter.h"
#include <fstream>
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 </scripts/%s/index>`\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);
}

View file

@ -0,0 +1,48 @@
#ifndef BROXYGEN_PACKAGEINFO_H
#define BROXYGEN_PACKAGEINFO_H
#include "Info.h"
#include <string>
#include <vector>
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<std::string> 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<std::string> readme;
};
} // namespace broxygen
#endif

View file

@ -0,0 +1,66 @@
#include "ReStructuredTextTable.h"
#include <assert.h>
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<string>& 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<size_t> 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;
}

View file

@ -0,0 +1,51 @@
#ifndef BROXYGEN_RESTTABLE_H
#define BROXYGEN_RESTTABLE_H
#include <vector>
#include <string>
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<std::string>& 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<size_t> 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<std::vector<std::string> > rows;
std::vector<size_t> longest_row_in_column;
};
} // namespace broxygen
#endif

361
src/broxygen/ScriptInfo.cc Normal file
View file

@ -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<string> summary_comment(const vector<string>& cmnts)
{
vector<string> 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<string>& cmnts,
ReStructuredTextTable* table)
{
vector<string> 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<IdentifierInfo::Redefinition> 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<string> 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 </scripts/%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;
}

121
src/broxygen/ScriptInfo.h Normal file
View file

@ -0,0 +1,121 @@
#ifndef BROXYGEN_SCRIPTINFO_H
#define BROXYGEN_SCRIPTINFO_H
#include "Info.h"
#include "IdentifierInfo.h"
#include <set>
#include <list>
#include <string>
#include <vector>
#include <map>
namespace broxygen {
class IdentifierInfo;
struct IdInfoComp {
bool operator() (const IdentifierInfo* lhs,
const IdentifierInfo* rhs) const;
};
typedef std::set<IdentifierInfo*, IdInfoComp> id_info_set;
typedef std::list<IdentifierInfo*> 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<std::string> GetComments() const;
private:
typedef std::map<std::string, IdentifierInfo*> id_info_map;
typedef std::set<std::string> 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<std::string> 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

595
src/broxygen/Target.cc Normal file
View file

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <fts.h>
#include <unistd.h>
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<const analyzer::Component*>(*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<const file_analysis::Component*>(*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<class T>
static vector<T*> filter_matches(const vector<Info*>& from, Target* t)
{
vector<T*> rval;
for ( size_t i = 0; i < from.size(); ++i )
{
T* d = dynamic_cast<T*>(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<Info *>& 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<Info*>()) )
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<Info*>& infos)
{
pkg_deps = filter_matches<PackageInfo>(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<ScriptInfo*>(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<string> 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<string> 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<Info*>& infos)
{
pkg_deps = filter_matches<PackageInfo>(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<Info*>& infos)
{
script_deps = filter_matches<ScriptInfo>(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<string> dir_contents_recursive(string dir)
{
vector<string> 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<string> targets;
vector<string> 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<ScriptInfo*> 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<ScriptInfo*>(script_deps[i]);
if ( ! d )
continue;
fprintf(file.f, ":doc:`/scripts/%s`\n", d->Name().c_str());
vector<string> 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<ScriptInfo*>(script_deps[i]);
if ( ! d )
continue;
fprintf(file.f, " %s </scripts/%s>\n", d->Name().c_str(),
d->Name().c_str());
}
}
void IdentifierTarget::DoFindDependencies(const vector<Info*>& infos)
{
id_deps = filter_matches<IdentifierInfo>(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());
}

387
src/broxygen/Target.h Normal file
View file

@ -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 <map>
#include <string>
#include <vector>
#include <cstdio>
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<Info*>& 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<Info*>& infos) = 0;
virtual void DoGenerate() const = 0;
std::string name;
std::string pattern;
std::string prefix;
};
template<class T>
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<class T>
void Register(const std::string& type_name)
{
target_creators[type_name] = &create_target<T>;
}
/**
* 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<std::string, TargetFactoryFn> 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<Info*>& 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<Info*>& infos);
void DoGenerate() const;
std::vector<PackageInfo*> pkg_deps;
std::vector<ScriptInfo*> script_deps;
typedef std::map<PackageInfo*,std::vector<ScriptInfo*> > 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<Info*>& infos);
void DoGenerate() const;
std::vector<PackageInfo*> 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<ScriptInfo*> script_deps;
private:
void DoFindDependencies(const std::vector<Info*>& infos);
void DoGenerate() const;
bool IsDir() const
{ return Name()[Name().size() - 1] == '/'; }
std::vector<Target*> 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<Info*>& infos);
void DoGenerate() const;
std::vector<IdentifierInfo*> id_deps;
};
} // namespace broxygen
#endif

View file

@ -10,12 +10,17 @@ static StringVal* comments_to_val(const vector<string>& 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 function get_identifier_comments%(name: string%): string
%{ %{
using namespace broxygen; using namespace broxygen;
IdentifierDocument* d = broxygen_mgr->GetIdentifierDoc(name->CheckString()); IdentifierInfo* d = broxygen_mgr->GetIdentifierInfo(name->CheckString());
if ( ! d ) if ( ! d )
return new StringVal(""); return new StringVal("");
@ -23,10 +28,19 @@ function get_identifier_comments%(name: string%): string
return comments_to_val(d->GetComments()); 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 function get_script_comments%(name: string%): string
%{ %{
using namespace broxygen; using namespace broxygen;
ScriptDocument* d = broxygen_mgr->GetScriptDoc(name->CheckString()); ScriptInfo* d = broxygen_mgr->GetScriptInfo(name->CheckString());
if ( ! d ) if ( ! d )
return new StringVal(""); return new StringVal("");
@ -34,10 +48,17 @@ function get_script_comments%(name: string%): string
return comments_to_val(d->GetComments()); 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 function get_package_readme%(name: string%): string
%{ %{
using namespace broxygen; using namespace broxygen;
PackageDocument* d = broxygen_mgr->GetPackageDoc(name->CheckString()); PackageInfo* d = broxygen_mgr->GetPackageInfo(name->CheckString());
if ( ! d ) if ( ! d )
return new StringVal(""); return new StringVal("");
@ -45,6 +66,14 @@ function get_package_readme%(name: string%): string
return comments_to_val(d->GetReadme()); 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: "<record_type>$<field>".
##
## 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 function get_record_field_comments%(name: string%): string
%{ %{
using namespace broxygen; using namespace broxygen;
@ -56,7 +85,7 @@ function get_record_field_comments%(name: string%): string
string id = accessor.substr(0, i); string id = accessor.substr(0, i);
IdentifierDocument* d = broxygen_mgr->GetIdentifierDoc(id); IdentifierInfo* d = broxygen_mgr->GetIdentifierInfo(id);
if ( ! d ) if ( ! d )
return new StringVal(""); return new StringVal("");

View file

@ -1,5 +1,9 @@
#include "utils.h" #include "utils.h"
#include "Reporter.h"
#include <sys/stat.h>
using namespace broxygen; using namespace broxygen;
using namespace std; using namespace std;
@ -68,3 +72,55 @@ bool broxygen::prettify_params(string& s)
return false; 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;
}

View file

@ -1,12 +1,59 @@
#ifndef BROXYGEN_UTILS_H #ifndef BROXYGEN_UTILS_H
#define BROXYGEN_UTILS_H #define BROXYGEN_UTILS_H
#include "ID.h"
#include <string> #include <string>
namespace broxygen { 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); 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 } // namespace broxygen
#endif #endif

View file

@ -50,7 +50,6 @@ extern "C" void OPENSSL_add_all_algorithms_conf(void);
#include "PersistenceSerializer.h" #include "PersistenceSerializer.h"
#include "EventRegistry.h" #include "EventRegistry.h"
#include "Stats.h" #include "Stats.h"
#include "BroDoc.h"
#include "Brofiler.h" #include "Brofiler.h"
#include "threading/Manager.h" #include "threading/Manager.h"

View file

@ -564,7 +564,7 @@ static int load_files(const char* orig_file)
else else
file_stack.append(new FileInfo); 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 // "orig_file", could be an alias for yytext, which is ephemeral
// and will be zapped after the yy_switch_to_buffer() below. // and will be zapped after the yy_switch_to_buffer() below.