diff --git a/CHANGES b/CHANGES
index 4c1f089921..8ea82df983 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,25 @@
+1.6-dev.80 Mon Apr 18 14:50:54 PDT 2011
+
+- New framework for generating documentation from Bro scripts. (Jon
+ Siwek)
+
+ This includes:
+
+ - Changes to Bro's scanner/parser to facilitate automatic
+ generation of Bro policy script documentation in
+ reStructuredText format.
+
+ - New command line flags -Z/--doc-scripts to enable the new doc
+ generation mode.
+
+ - Changes to bifcl to pass comments starting with "##" through
+ into the generated .bro script.
+
+ - A "doc" build target for the top-level Makefile to first
+ generate reStructuredText for a defined set of Bro policy
+ scripts, and then run that through Sphinx to create HTML
+ documentation.
+
1.6-dev.78 Mon Apr 18 12:52:55 PDT 2011
- Adding files to CMake build targets so they show up in generated IDE
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1face2ce95..11b8454d84 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,6 +51,11 @@ file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/bro-path-dev.csh
"setenv BROPATH `${CMAKE_CURRENT_BINARY_DIR}/bro-path-dev`\n")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" VERSION LIMIT_COUNT 1)
+string(REPLACE "." " " version_numbers ${VERSION})
+separate_arguments(version_numbers)
+list(GET version_numbers 0 VERSION_MAJOR)
+list(GET version_numbers 1 VERSION_MINOR)
+set(VERSION_MAJ_MIN "${VERSION_MAJOR}.${VERSION_MINOR}")
set(EXTRA_COMPILE_FLAGS "-Wall -Wno-unused")
@@ -170,8 +175,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_subdirectory(src)
add_subdirectory(policy)
-#add_subdirectory(scripts)
-#add_subdirectory(doc)
+add_subdirectory(doc)
include(CheckOptionalBuildSources)
diff --git a/Makefile b/Makefile
index 8bf528dd26..d2620b422c 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,13 @@ install: configured
clean: configured
( cd $(BUILD) && make clean )
+ ( cd $(BUILD) && make doc-clean )
+
+doc: configured
+ ( cd $(BUILD) && make doc )
+
+doc-clean: configured
+ ( cd $(BUILD) && make doc-clean )
dist: cmake_version
# Minimum Bro source package
diff --git a/VERSION b/VERSION
index 2825c394e5..43119b8d6f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.6-dev.78
+1.6-dev.80
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644
index 0000000000..00e0974919
--- /dev/null
+++ b/doc/CMakeLists.txt
@@ -0,0 +1,42 @@
+set(POLICY_SRC_DIR ${PROJECT_SOURCE_DIR}/policy)
+set(DOC_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/out)
+set(DOC_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/source)
+set(DOC_SOURCE_WORKDIR ${CMAKE_CURRENT_BINARY_DIR}/source)
+
+file(GLOB_RECURSE DOC_SOURCES FOLLOW_SYMLINKS "*")
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/conf.py
+ @ONLY)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_reST_docs.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/generate_reST_docs.py
+ @ONLY)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/BroToReST.py.in
+ ${CMAKE_CURRENT_BINARY_DIR}/BroToReST.py
+ @ONLY)
+
+add_custom_target(doc
+ COMMAND "${CMAKE_COMMAND}" -E copy_directory
+ ${DOC_SOURCE_DIR}
+ ${DOC_SOURCE_WORKDIR}
+ COMMAND python generate_reST_docs.py
+ COMMAND sphinx-build
+ -b html
+ -c ${CMAKE_CURRENT_BINARY_DIR}
+ -d ${DOC_OUTPUT_DIR}/doctrees
+ ${DOC_SOURCE_WORKDIR}
+ ${DOC_OUTPUT_DIR}/html
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ COMMENT "[Sphinx] Generating Script Documentation"
+ VERBATIM
+ # SOURCES just adds stuff to IDE projects as a convienience
+ SOURCES ${DOC_SOURCES})
+
+add_dependencies(doc bro doc-clean)
+
+add_custom_target(doc-clean
+ COMMAND "${CMAKE_COMMAND}" -E remove_directory
+ ${CMAKE_CURRENT_BINARY_DIR}/source
+ COMMAND "${CMAKE_COMMAND}" -E remove_directory
+ ${DOC_OUTPUT_DIR}
+ VERBATIM)
diff --git a/doc/README b/doc/README
index 2a34ea6bdf..150260ed09 100644
--- a/doc/README
+++ b/doc/README
@@ -1 +1,44 @@
-TODO.
+This directory contains scripts and templates that can be used to automate
+the generation of Bro script documentation. Two build targets are defined
+by CMake:
+
+``make doc``
+
+ This target depends on a Python interpreter (>=2.5) and
+ `Sphinx `_ being installed. Sphinx can be
+ installed like::
+
+ > sudo easy_install sphinx
+
+ This target will also first build the bro binary if it is not already
+ since the generation of reStructuredText (reST) documentation from
+ Bro scripts is integrated within the parsing process.
+
+ After completion, HTML documentation can be located inside the CMake
+ ``build/`` directory as ``build/doc/out/html``. The generated reST
+ documentation will be located in ``build/doc/source/policy``.
+
+``make doc-clean``
+
+ This target removes Sphinx inputs and outputs from the CMake ``build/`` dir.
+
+To schedule a script to be documented, edit ``scripts/generate_reST_docs.py.in``
+and try adding the name of the script along with an optional script group to
+the ``docs`` dictionary. That python script also shows other, more specialized
+methods for generating documentation for some types of corner-cases.
+
+When adding a new logical grouping for generated scripts, create a new
+reST document in ``source/policy/.rst`` and add some default
+documentation. References to (and summaries of) documents associated with
+the group get appended to this file during the ``make doc`` process.
+
+The Sphinx source tree template in ``source/`` can be modified to add more
+common/general documentation, style sheets, JavaScript, etc. The Sphinx
+config file is produced from ``conf.py.in``, so that can be edited to change
+various Sphinx options, like setting the default HTML rendering theme.
+There is also a custom Sphinx domain implemented in ``source/ext/bro.py``
+which adds some reST directives and roles that aid in generating useful
+index entries and cross-references.
+
+See ``example.bro`` for an example of how to document a Bro script such that
+``make doc`` will be able to produce reST/HTML documentation for it.
diff --git a/doc/conf.py.in b/doc/conf.py.in
new file mode 100644
index 0000000000..67e06abfb3
--- /dev/null
+++ b/doc/conf.py.in
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+#
+# Bro documentation build configuration file, created by sphinx-quickstart
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('source/ext'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['bro']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['source/_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Bro'
+copyright = u'2011, Jon Siwek'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '@VERSION_MAJ_MIN@'
+# The full version, including alpha/beta/rc tags.
+release = '@VERSION@'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+show_authors = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# " v documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['source/_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Brodoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'Bro.tex', u'Bro Documentation',
+ u'Jon Siwek', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+ ('index', 'bro', u'Bro Documentation',
+ [u'Jon Siwek'], 1)
+]
diff --git a/doc/example.bro b/doc/example.bro
new file mode 100644
index 0000000000..6deab87f45
--- /dev/null
+++ b/doc/example.bro
@@ -0,0 +1,178 @@
+##! This is an example script that demonstrates how to document. Comments
+##! of the form ``##!`` are for the script summary. The contents of
+##! these comments are transferred directly into the auto-generated
+##! `reStructuredText `_
+##! (reST) document's summary section.
+##!
+##! .. tip:: You can embed directives and roles within ``##``-stylized comments
+##!
+##! :Author: Jon Siwek
+
+# Comments that use a single pound sign (#) are not significant to
+# a script's auto-generated documentation, but ones that use a
+# double pound sign (##) do matter. In some cases, like record
+# field comments, it's necessary to disambiguate the field with
+# which a comment associates: e.g. "##<" can be used on the same line
+# as a field to signify the comment relates to it and not the
+# following field. "##<" is not meant for general use, just
+# record/enum fields.
+#
+# Generally, the auto-doc comments (##) are associated with the
+# next declaration/identifier found in the script, but the doc framework
+# will track/render identifiers regardless of whether they have any
+# of these special comments associated with them.
+#
+# The first sentence contained within the "##"-stylized comments for
+# a given identifier is special in that it will be used as summary
+# text in a table containing all such identifiers and short summaries.
+# If there are no sentences (text terminated with '.'), then everything
+# in the "##"-stylized comments up until the first empty comment
+# is taken as the summary text for a given identifier.
+
+# @load directives are self-documenting
+@load notice
+
+# "module" statements are self-documenting
+module Example;
+
+# redefinitions of "capture_filters" are self-documenting and
+# go into the generated documentation's "Packet Filter" section
+redef capture_filters += {
+ ["ssl"] = "tcp port 443",
+ ["nntps"] = "tcp port 563",
+};
+
+global example_ports = {
+ 443/tcp, 563/tcp,
+} &redef;
+
+# redefinitions of "dpd_config" are self-documenting and
+# go into the generated doc's "Port Analysis" section
+redef dpd_config += {
+ [ANALYZER_SSL] = [$ports = example_ports]
+};
+
+# redefinitions of "Notice::Type" are self-documenting, but
+# more information can be supplied in two different ways
+redef enum Notice += {
+ ## any number of this type of comment
+ ## will document "Notice_One"
+ Notice_One,
+ Notice_Two, ##< any number of this type of comment
+ ##< will document "Notice_Two"
+ Notice_Three,
+ Notice_Four,
+};
+
+# Anything declared in the export section will show up in the rendered
+# documentation's "public interface" section
+
+export {
+
+ # these headings don't mean anything special to the
+ # doc framework right now, I'm just including them
+ # to make it more clear to the reader how the doc
+ # framework will actually categorize a script's identifiers
+
+ ############## types ################
+
+ # Note that I'm just mixing the "##" and "##<"
+ # types of comments in the following declarations
+ # as a demonstration. Normally, it would be good style
+ # to pick one and be consistent.
+
+ ## documentation for "SimpleEnum"
+ ## goes here.
+ type SimpleEnum: enum {
+ ## and more specific info for "ONE"
+ ## can span multiple lines
+ ONE,
+ TWO, ##< or more info like this for "TWO"
+ ##< can span multiple lines
+ THREE,
+ };
+
+ ## document the "SimpleEnum" redef here
+ redef enum SimpleEnum += {
+ FOUR, ##< and some documentation for "FOUR"
+ ## also "FIVE" for good measure
+ FIVE
+ };
+
+ ## general documentation for a type "SimpleRecord"
+ ## goes here.
+ type SimpleRecord: record {
+ ## counts something
+ field1: count;
+ field2: bool; ##< toggles something
+ };
+
+
+ ## general documentation for a type "ComplexRecord" goes here
+ type ComplexRecord: record {
+ field1: count; ##< counts something
+ field2: bool; ##< toggles something
+ field3: SimpleRecord;
+ msg: string &default="blah"; ##< attributes are self-documenting
+ } &redef;
+
+ ############## options ################
+ # right now, I'm just defining an option as
+ # any const with &redef (something that can
+ # change at parse time, but not at run time.
+
+ ## add documentation for "an_option" here
+ const an_option: set[addr, addr, string] &redef;
+
+ # default initialization will be self-documenting
+ const option_with_init = 0.01 secs &redef;
+
+ ############## state variables ############
+ # right now, I'm defining this as any global
+ # that's not a function/event. doesn't matter
+ # if &redef attribute is present
+
+ ## put some documentation for "a_var" here
+ global a_var: bool;
+
+ # attributes are self-documenting
+ global var_with_attr: count &persistent;
+
+ # it's fine if the type is inferred, that information is self-documenting
+ global var_without_explicit_type = "this works";
+
+ ############## functions/events ############
+
+ ## Summarize purpose of "a_function" here.
+ ## Give more details about "a_function" here.
+ ## Separating the documentation of the params/return values with
+ ## empty comments is optional, but improves readability of script.
+ ##
+ ## tag: function arguments can be described
+ ## like this
+ ## msg: another param
+ ##
+ ## Returns: describe the return type here
+ global a_function: function(tag: string, msg: string): string;
+
+ ## Summarize "an_event" here.
+ ## Give more details about "an_event" here.
+ ## name: describe the argument here
+ global an_event: event(name: string);
+}
+
+# this function is documented in the "private interface" section
+# of generated documentation and any "##"-stylized comments would also
+# be rendered there
+function function_without_proto(tag: string): string
+ {
+ return "blah";
+ }
+
+# this record type is documented in the "private interface" section
+# of generated documentation and any "##"-stylized comments would also
+# be rendered there
+type PrivateRecord: record {
+ field1: bool;
+ field2: count;
+};
diff --git a/doc/scripts/BroToReST.py.in b/doc/scripts/BroToReST.py.in
new file mode 100755
index 0000000000..89a8e1cc57
--- /dev/null
+++ b/doc/scripts/BroToReST.py.in
@@ -0,0 +1,152 @@
+#! /usr/bin/env python
+
+import os
+import subprocess
+import shutil
+import glob
+import string
+import sys
+
+BRO = "@CMAKE_BINARY_DIR@/src/bro"
+BROPATHDEV = "`@CMAKE_BINARY_DIR@/bro-path-dev`"
+BRO_ARGS = "--doc-scripts"
+DOC_DST_DIR = "@DOC_SOURCE_WORKDIR@/policy"
+BROPATH = subprocess.Popen("@CMAKE_BINARY_DIR@/bro-path-dev", shell=True,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.readline()
+
+class BroToReST:
+ """A class to encapsulate the the generation of reST documentation from
+ a given Bro script.
+ """
+
+ bro_src_file = None
+ doc_src_file = None
+ load_via_stdin = False
+ group = None
+
+ def __init__(self, src_file, load_method=False, search_dir=None, group=None):
+ """
+ :param src_file: the file name of a Bro script (not a path)
+ :param load_method: T if script must be loaded by Bro via a stdin
+ redirection of "@load
+ {{ super() }}
+{% endblock %}
diff --git a/doc/source/builtins.rst b/doc/source/builtins.rst
new file mode 100644
index 0000000000..9c71da61bf
--- /dev/null
+++ b/doc/source/builtins.rst
@@ -0,0 +1,121 @@
+Builtin Types and Attributes
+============================
+
+Types
+-----
+
+The Bro scripting language supports the following built-in types.
+
+.. TODO: add documentation
+
+.. bro:type:: void
+
+.. bro:type:: bool
+
+.. bro:type:: int
+
+.. bro:type:: count
+
+.. bro:type:: counter
+
+.. bro:type:: double
+
+.. bro:type:: time
+
+.. bro:type:: interval
+
+.. bro:type:: string
+
+.. bro:type:: pattern
+
+.. bro:type:: enum
+
+.. bro:type:: timer
+
+.. bro:type:: port
+
+.. bro:type:: addr
+
+.. bro:type:: net
+
+.. bro:type:: subnet
+
+.. bro:type:: any
+
+.. bro:type:: table
+
+.. bro:type:: union
+
+.. bro:type:: record
+
+.. bro:type:: types
+
+.. bro:type:: func
+
+.. bro:type:: file
+
+.. bro:type:: vector
+
+.. TODO: below are kind of "special cases" that bro knows about?
+
+.. bro:type:: set
+
+.. bro:type:: function
+
+.. bro:type:: event
+
+.. TODO: Notice will get documented as part of notice.bro, which can eventually
+ be referenced here once that documentation is auto-generated.
+
+.. bro:type:: Notice
+
+Attributes
+----------
+
+The Bro scripting language supports the following built-in attributes.
+
+.. TODO: add documentation
+
+.. bro:attr:: &optional
+
+.. bro:attr:: &default
+
+.. bro:attr:: &redef
+
+.. bro:attr:: &rotate_interval
+
+.. bro:attr:: &rotate_size
+
+.. bro:attr:: &add_func
+
+.. bro:attr:: &delete_func
+
+.. bro:attr:: &expire_func
+
+.. bro:attr:: &read_expire
+
+.. bro:attr:: &write_expire
+
+.. bro:attr:: &create_expire
+
+.. bro:attr:: &persistent
+
+.. bro:attr:: &synchronized
+
+.. bro:attr:: &postprocessor
+
+.. bro:attr:: &encrypt
+
+.. bro:attr:: &match
+
+.. bro:attr:: &disable_print_hook
+
+.. bro:attr:: &raw_output
+
+.. bro:attr:: &mergeable
+
+.. bro:attr:: &priority
+
+.. bro:attr:: &group
+
+.. bro:attr:: (&tracked)
diff --git a/doc/source/common.rst b/doc/source/common.rst
new file mode 100644
index 0000000000..6105585b2c
--- /dev/null
+++ b/doc/source/common.rst
@@ -0,0 +1,19 @@
+Common Documentation
+====================
+
+.. _common_port_analysis_doc:
+
+Port Analysis
+-------------
+
+TODO: add some stuff here
+
+.. _common_packet_filter_doc:
+
+Packet Filter
+-------------
+
+TODO: add some stuff here
+
+.. note:: Filters are only relevant when dynamic protocol detection (DPD)
+ is explicitly turned off (Bro release 1.6 enabled DPD by default).
diff --git a/doc/source/ext/bro.py b/doc/source/ext/bro.py
new file mode 100644
index 0000000000..7ec02f76a8
--- /dev/null
+++ b/doc/source/ext/bro.py
@@ -0,0 +1,167 @@
+"""
+ The Bro domain for Sphinx.
+"""
+
+def setup(Sphinx):
+ Sphinx.add_domain(BroDomain)
+
+from sphinx import addnodes
+from sphinx.domains import Domain, ObjType, Index
+from sphinx.locale import l_, _
+from sphinx.directives import ObjectDescription
+from sphinx.roles import XRefRole
+from sphinx.util.nodes import make_refnode
+import string
+
+from docutils import nodes
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst import directives
+from docutils.parsers.rst.roles import set_classes
+
+class BroGeneric(ObjectDescription):
+ def add_target_and_index(self, name, sig, signode):
+ targetname = self.objtype + '-' + name
+ if targetname not in self.state.document.ids:
+ signode['names'].append(targetname)
+ signode['ids'].append(targetname)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+
+ objects = self.env.domaindata['bro']['objects']
+ key = (self.objtype, name)
+# this is commented out mostly just to avoid having a special directive
+# for events in order to avoid the duplicate warnings in that case
+ """
+ if key in objects:
+ self.env.warn(self.env.docname,
+ 'duplicate description of %s %s, ' %
+ (self.objtype, name) +
+ 'other instance in ' +
+ self.env.doc2path(objects[key]),
+ self.lineno)
+ """
+ objects[key] = self.env.docname
+ indextext = self.get_index_text(self.objtype, name)
+ if indextext:
+ self.indexnode['entries'].append(('single', indextext,
+ targetname, targetname))
+
+ def get_index_text(self, objectname, name):
+ return _('%s (%s)') % (name, self.objtype)
+
+ def handle_signature(self, sig, signode):
+ signode += addnodes.desc_name("", sig)
+ return sig
+
+class BroNamespace(BroGeneric):
+ def add_target_and_index(self, name, sig, signode):
+ targetname = self.objtype + '-' + name
+ if targetname not in self.state.document.ids:
+ signode['names'].append(targetname)
+ signode['ids'].append(targetname)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+
+ objects = self.env.domaindata['bro']['objects']
+ key = (self.objtype, name)
+ objects[key] = self.env.docname
+ indextext = self.get_index_text(self.objtype, name)
+ self.indexnode['entries'].append(('single', indextext,
+ targetname, targetname))
+ self.indexnode['entries'].append(('single',
+ "namespaces; %s" % (sig),
+ targetname, targetname))
+
+ def get_index_text(self, objectname, name):
+ return _('%s (namespace); %s') % (name, self.env.docname)
+
+ def handle_signature(self, sig, signode):
+ signode += addnodes.desc_name("", sig)
+ return sig
+
+class BroEnum(BroGeneric):
+ def add_target_and_index(self, name, sig, signode):
+ targetname = self.objtype + '-' + name
+ if targetname not in self.state.document.ids:
+ signode['names'].append(targetname)
+ signode['ids'].append(targetname)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+
+ objects = self.env.domaindata['bro']['objects']
+ key = (self.objtype, name)
+ objects[key] = self.env.docname
+ indextext = self.get_index_text(self.objtype, name)
+ #self.indexnode['entries'].append(('single', indextext,
+ # targetname, targetname))
+ m = sig.split()
+ self.indexnode['entries'].append(('single',
+ "%s (enum values); %s" % (m[1], m[0]),
+ targetname, targetname))
+
+ def handle_signature(self, sig, signode):
+ m = sig.split()
+ name = m[0]
+ signode += addnodes.desc_name("", name)
+ return name
+
+class BroIdentifier(BroGeneric):
+ def get_index_text(self, objectname, name):
+ return name
+
+class BroAttribute(BroGeneric):
+ def get_index_text(self, objectname, name):
+ return _('%s (attribute)') % (name)
+
+class BroDomain(Domain):
+ """Bro domain."""
+ name = 'bro'
+ label = 'Bro'
+
+ object_types = {
+ 'type': ObjType(l_('type'), 'type'),
+ 'namespace': ObjType(l_('namespace'), 'namespace'),
+ 'id': ObjType(l_('id'), 'id'),
+ 'enum': ObjType(l_('enum'), 'enum'),
+ 'attr': ObjType(l_('attr'), 'attr'),
+ }
+
+ directives = {
+ 'type': BroGeneric,
+ 'namespace': BroNamespace,
+ 'id': BroIdentifier,
+ 'enum': BroEnum,
+ 'attr': BroAttribute,
+ }
+
+ roles = {
+ 'type': XRefRole(),
+ 'namespace': XRefRole(),
+ 'id': XRefRole(),
+ 'enum': XRefRole(),
+ 'attr': XRefRole(),
+ }
+
+ initial_data = {
+ 'objects': {}, # fullname -> docname, objtype
+ }
+
+ def clear_doc(self, docname):
+ for (typ, name), doc in self.data['objects'].items():
+ if doc == docname:
+ del self.data['objects'][typ, name]
+
+ def resolve_xref(self, env, fromdocname, builder, typ, target, node,
+ contnode):
+ objects = self.data['objects']
+ objtypes = self.objtypes_for_role(typ)
+ for objtype in objtypes:
+ if (objtype, target) in objects:
+ return make_refnode(builder, fromdocname,
+ objects[objtype, target],
+ objtype + '-' + target,
+ contnode, target + ' ' + objtype)
+
+ def get_objects(self):
+ for (typ, name), docname in self.data['objects'].iteritems():
+ yield name, name, typ, docname, typ + '-' + name, 1
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000000..5f4b8eb6e0
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,24 @@
+.. Bro documentation master file
+
+Welcome to Bro's documentation!
+===============================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ common
+ builtins
+ policy/default
+ policy/user
+ policy/bifs
+ policy/internal
+ policy/index
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/doc/source/policy/bifs.rst b/doc/source/policy/bifs.rst
new file mode 100644
index 0000000000..0c40404058
--- /dev/null
+++ b/doc/source/policy/bifs.rst
@@ -0,0 +1,4 @@
+Built-In Functions (BIFs)
+=========================
+
+Here's a list of all documentation for BIFs that Bro provides:
diff --git a/doc/source/policy/default.rst b/doc/source/policy/default.rst
new file mode 100644
index 0000000000..6b79f20c79
--- /dev/null
+++ b/doc/source/policy/default.rst
@@ -0,0 +1,3 @@
+Bro Scripts Loaded by Default
+=============================
+
diff --git a/doc/source/policy/index.rst b/doc/source/policy/index.rst
new file mode 100644
index 0000000000..2202eb8cea
--- /dev/null
+++ b/doc/source/policy/index.rst
@@ -0,0 +1,10 @@
+Index of All Policy Script Documentation
+========================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ *
diff --git a/doc/source/policy/internal.rst b/doc/source/policy/internal.rst
new file mode 100644
index 0000000000..864ee75f8a
--- /dev/null
+++ b/doc/source/policy/internal.rst
@@ -0,0 +1,3 @@
+Internal Policy Scripts
+=======================
+
diff --git a/doc/source/policy/user.rst b/doc/source/policy/user.rst
new file mode 100644
index 0000000000..7a3bba29cb
--- /dev/null
+++ b/doc/source/policy/user.rst
@@ -0,0 +1,3 @@
+User-Facing Policy Scripts
+==========================
+
diff --git a/src/Attr.cc b/src/Attr.cc
index 5a83d0501b..cb3f21db92 100644
--- a/src/Attr.cc
+++ b/src/Attr.cc
@@ -49,6 +49,37 @@ void Attr::Describe(ODesc* d) const
}
}
+void Attr::DescribeReST(ODesc* d) const
+ {
+ d->Add(":bro:attr:`");
+ AddTag(d);
+ d->Add("`");
+
+ if ( expr )
+ {
+ d->SP();
+ d->Add("=");
+ d->SP();
+
+ if ( expr->Type()->Tag() == TYPE_FUNC )
+ d->Add(":bro:type:`func`");
+
+ else if ( expr->Type()->Tag() == TYPE_ENUM )
+ {
+ d->Add(":bro:enum:`");
+ expr->Describe(d);
+ d->Add("`");
+ }
+
+ else
+ {
+ d->Add("``");
+ expr->Describe(d);
+ d-> Add("``");
+ }
+ }
+ }
+
void Attr::AddTag(ODesc* d) const
{
if ( d->IsBinary() )
@@ -161,6 +192,17 @@ void Attributes::Describe(ODesc* d) const
}
}
+void Attributes::DescribeReST(ODesc* d) const
+ {
+ loop_over_list(*attrs, i)
+ {
+ if ( i > 0 )
+ d->Add(" ");
+
+ (*attrs)[i]->DescribeReST(d);
+ }
+ }
+
void Attributes::CheckAttr(Attr* a)
{
switch ( a->Tag() ) {
diff --git a/src/Attr.h b/src/Attr.h
index 73fb101841..ccc6b0f5d1 100644
--- a/src/Attr.h
+++ b/src/Attr.h
@@ -51,6 +51,7 @@ public:
{ return tag == ATTR_REDEF || tag == ATTR_OPTIONAL; }
void Describe(ODesc* d) const;
+ void DescribeReST(ODesc* d) const;
protected:
void AddTag(ODesc* d) const;
@@ -73,6 +74,7 @@ public:
void RemoveAttr(attr_tag t);
void Describe(ODesc* d) const;
+ void DescribeReST(ODesc* d) const;
attr_list* Attrs() { return attrs; }
diff --git a/src/BroBifDoc.cc b/src/BroBifDoc.cc
new file mode 100644
index 0000000000..ff16ee8fde
--- /dev/null
+++ b/src/BroBifDoc.cc
@@ -0,0 +1,16 @@
+#include
+#include
+#include
+
+#include "BroDoc.h"
+#include "BroBifDoc.h"
+
+BroBifDoc::BroBifDoc(const std::string& sourcename) : BroDoc(sourcename)
+ {
+ }
+
+// TODO: This needs to do something different than parent class's version.
+void BroBifDoc::WriteDocFile() const
+ {
+ BroDoc::WriteDocFile();
+ }
diff --git a/src/BroBifDoc.h b/src/BroBifDoc.h
new file mode 100644
index 0000000000..724f50c4bd
--- /dev/null
+++ b/src/BroBifDoc.h
@@ -0,0 +1,18 @@
+#ifndef brobifdoc_h
+#define brobifdoc_h
+
+#include
+#include
+#include
+
+#include "BroDoc.h"
+
+class BroBifDoc : public BroDoc {
+public:
+ BroBifDoc(const std::string& sourcename);
+ virtual ~BroBifDoc() { }
+
+ void WriteDocFile() const;
+};
+
+#endif
diff --git a/src/BroDoc.cc b/src/BroDoc.cc
new file mode 100644
index 0000000000..165f0d0237
--- /dev/null
+++ b/src/BroDoc.cc
@@ -0,0 +1,361 @@
+#include
+#include
+#include
+#include
+
+#include "BroDoc.h"
+#include "BroDocObj.h"
+
+BroDoc::BroDoc(const std::string& sourcename)
+ {
+#ifdef DEBUG
+ fprintf(stdout, "Documenting source: %s\n", sourcename.c_str());
+#endif
+ source_filename = sourcename.substr(sourcename.find_last_of('/') + 1);
+
+ size_t ext_pos = source_filename.find_last_of('.');
+ std::string ext = source_filename.substr(ext_pos + 1);
+
+ if ( ext_pos == std::string::npos || ext != "bro" )
+ {
+ if ( source_filename != "bro.init" && source_filename != "" )
+ {
+ fprintf(stderr,
+ "Warning: documenting file without .bro extension: %s\n",
+ sourcename.c_str());
+ }
+ else
+ {
+ // Force the reST documentation file to be "bro.init.rst".
+ ext_pos = std::string::npos;
+ }
+ }
+
+ reST_filename = source_filename.substr(0, ext_pos);
+ reST_filename += ".rst";
+ reST_file = fopen(reST_filename.c_str(), "w");
+
+ if ( ! reST_file )
+ fprintf(stderr, "Failed to open %s", reST_filename.c_str());
+#ifdef DEBUG
+ else
+ fprintf(stdout, "Created reST document: %s\n", reST_filename.c_str());
+#endif
+ }
+
+BroDoc::~BroDoc()
+ {
+ if ( reST_file && fclose( reST_file ) )
+ fprintf(stderr, "Failed to close %s", reST_filename.c_str());
+
+ FreeBroDocObjPtrList(all);
+ }
+
+void BroDoc::AddImport(const std::string& s)
+ {
+ size_t ext_pos = s.find_last_of('.');
+
+ if ( ext_pos == std::string::npos )
+ imports.push_back(s);
+
+ else
+ {
+ if ( s.substr(ext_pos + 1) == "bro" )
+ imports.push_back(s.substr(0, ext_pos));
+ else
+ fprintf(stderr, "Warning: skipped documenting @load of file "
+ "without .bro extension: %s\n", s.c_str());
+ }
+ }
+
+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::AddPortAnalysis(const std::string& analyzer,
+ const std::string& ports)
+ {
+ std::string reST_string = analyzer + "::\n" + ports + "\n\n";
+ port_analysis.push_back(reST_string);
+ }
+
+void BroDoc::WriteDocFile() const
+ {
+ WriteToDoc(".. Automatically generated. Do not edit.\n\n");
+
+ WriteSectionHeading(source_filename.c_str(), '=');
+
+ WriteToDoc("\n:download:`Original Source File <%s>`\n\n",
+ source_filename.c_str());
+
+ WriteSectionHeading("Overview", '-');
+ WriteStringList("%s\n", "%s\n\n", summary);
+
+ if ( ! imports.empty() )
+ {
+ WriteToDoc(":Imports: ");
+ WriteStringList(":doc:`%s`, ", ":doc:`%s`\n", imports);
+ }
+
+ WriteToDoc("\n");
+
+ WriteInterface("Summary", '~', '#', true, true);
+
+ if ( ! modules.empty() )
+ {
+ WriteSectionHeading("Namespaces", '~');
+ WriteStringList(".. bro:namespace:: %s\n", modules);
+ WriteToDoc("\n");
+ }
+
+ if ( ! notices.empty() )
+ WriteBroDocObjList(notices, "Notices", '~');
+
+ WriteInterface("Public Interface", '-', '~', true, false);
+
+ if ( ! port_analysis.empty() )
+ {
+ WriteSectionHeading("Port Analysis", '-');
+ WriteToDoc(":ref:`More Information `\n\n");
+ WriteStringList("%s", port_analysis);
+ }
+
+ if ( ! packet_filter.empty() )
+ {
+ WriteSectionHeading("Packet Filter", '-');
+ WriteToDoc(":ref:`More Information `\n\n");
+ WriteToDoc("Filters added::\n\n");
+ WriteToDoc("%s\n", packet_filter.c_str());
+ }
+
+ 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);
+ }
+
+void BroDoc::WriteInterface(const char* heading, char underline,
+ char sub, bool isPublic, bool isShort) const
+ {
+ WriteSectionHeading(heading, underline);
+ WriteBroDocObjList(options, isPublic, "Options", sub, isShort);
+ WriteBroDocObjList(constants, isPublic, "Constants", sub, isShort);
+ WriteBroDocObjList(state_vars, isPublic, "State Variables", sub, isShort);
+ WriteBroDocObjList(types, isPublic, "Types", sub, isShort);
+ WriteBroDocObjList(events, isPublic, "Events", sub, isShort);
+ WriteBroDocObjList(functions, isPublic, "Functions", sub, isShort);
+ WriteBroDocObjList(redefs, isPublic, "Redefinitions", sub, isShort);
+ }
+
+void BroDoc::WriteStringList(const char* format, const char* last_format,
+ const std::list& l) const
+ {
+ if ( l.empty() )
+ {
+ WriteToDoc("\n");
+ return;
+ }
+
+ std::list::const_iterator it;
+ std::list::const_iterator last = l.end();
+ last--;
+
+ for ( it = l.begin(); it != last; ++it )
+ WriteToDoc(format, it->c_str());
+
+ WriteToDoc(last_format, last->c_str());
+ }
+
+void BroDoc::WriteBroDocObjTable(const BroDocObjList& l) const
+ {
+ 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('=', max_id_col);
+ WriteToDoc(" ");
+
+ if ( max_com_col == 0 )
+ WriteToDoc("=");
+ else
+ WriteRepeatedChar('=', max_com_col);
+
+ WriteToDoc("\n");
+
+ for ( it = l.begin(); it != l.end(); ++it )
+ {
+ if ( it != l.begin() )
+ WriteToDoc("\n\n");
+ (*it)->WriteReSTCompact(reST_file, max_id_col);
+ }
+
+ // End table.
+ WriteToDoc("\n");
+ WriteRepeatedChar('=', max_id_col);
+ WriteToDoc(" ");
+
+ if ( max_com_col == 0 )
+ WriteToDoc("=");
+ else
+ WriteRepeatedChar('=', max_com_col);
+
+ WriteToDoc("\n\n");
+ }
+
+void BroDoc::WriteBroDocObjList(const BroDocObjList& l, bool wantPublic,
+ const char* heading, char underline, bool isShort) const
+ {
+ 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(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(filtered_list);
+ else
+ WriteBroDocObjList(filtered_list);
+ }
+
+void BroDoc::WriteBroDocObjList(const BroDocObjMap& m, bool wantPublic,
+ const char* heading, char underline, bool isShort) const
+ {
+ BroDocObjMap::const_iterator it;
+ BroDocObjList l;
+
+ for ( it = m.begin(); it != m.end(); ++it )
+ l.push_back(it->second);
+
+ WriteBroDocObjList(l, wantPublic, heading, underline, isShort);
+ }
+
+void BroDoc::WriteBroDocObjList(const BroDocObjList& l, const char* heading,
+ char underline) const
+ {
+ WriteSectionHeading(heading, underline);
+ WriteBroDocObjList(l);
+ }
+
+void BroDoc::WriteBroDocObjList(const BroDocObjList& l) const
+ {
+ for ( BroDocObjList::const_iterator it = l.begin(); it != l.end(); ++it )
+ (*it)->WriteReST(reST_file);
+ }
+
+void BroDoc::WriteBroDocObjList(const BroDocObjMap& m, const char* heading,
+ char underline) const
+ {
+ BroDocObjMap::const_iterator it;
+ BroDocObjList l;
+
+ for ( it = m.begin(); it != m.end(); ++it )
+ l.push_back(it->second);
+
+ WriteBroDocObjList(l, heading, underline);
+ }
+
+void BroDoc::WriteToDoc(const char* format, ...) const
+ {
+ va_list argp;
+ va_start(argp, format);
+ vfprintf(reST_file, format, argp);
+ va_end(argp);
+ }
+
+void BroDoc::WriteSectionHeading(const char* heading, char underline) const
+ {
+ WriteToDoc("%s\n", heading);
+ WriteRepeatedChar(underline, strlen(heading));
+ WriteToDoc("\n");
+ }
+
+void BroDoc::WriteRepeatedChar(char c, size_t n) const
+ {
+ for ( size_t i = 0; i < n; ++i )
+ WriteToDoc("%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);
+ }
diff --git a/src/BroDoc.h b/src/BroDoc.h
new file mode 100644
index 0000000000..4538f5616e
--- /dev/null
+++ b/src/BroDoc.h
@@ -0,0 +1,362 @@
+#ifndef brodoc_h
+#define brodoc_h
+
+#include
+#include
+#include
+#include
+
+#include "BroDocObj.h"
+
+/**
+ * This class is used to gather all data relevant to the automatic generation
+ * of a reStructuredText (reST) document from a given Bro script.
+ */
+class BroDoc {
+public:
+ /**
+ * BroDoc constructor
+ * Given a Bro script, opens new file in the current working directory
+ * that will contain reST documentation generated from the parsing
+ * of the Bro script. The new reST file will be named similar to
+ * the filename of the Bro script that generates it, except any
+ * ".bro" file extension is stripped and ".rst" takes it place.
+ * If the filename doesn't end in ".bro", then ".rst" is just appended.
+ * @param sourcename The name of the Bro script for which to generate
+ * documentation. May contain a path.
+ */
+ BroDoc(const std::string& sourcename);
+
+ /**
+ * 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 given set of ports being associated
+ * with a particular analyzer as a result of the current script
+ * being loaded -- the way the "dpd_config" table is changed.
+ * @param analyzer An analyzer that changed the "dpd_config" table.
+ * @param ports The set of ports assigned to the analyzer in table.
+ */
+ void AddPortAnalysis(const std::string& analyzer, const std::string& ports);
+
+ /**
+ * 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 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();
+ }
+
+protected:
+ FILE* reST_file;
+ std::string reST_filename;
+ std::string source_filename;
+ std::string packet_filter;
+
+ std::list modules;
+ std::list summary;
+ std::list imports;
+ std::list port_analysis;
+
+ typedef std::list BroDocObjList;
+ typedef std::map BroDocObjMap;
+
+ BroDocObjList options;
+ BroDocObjList constants;
+ BroDocObjList state_vars;
+ BroDocObjList types;
+ BroDocObjList notices;
+ BroDocObjList events;
+ BroDocObjMap functions;
+ BroDocObjList redefs;
+
+ BroDocObjList all;
+
+ /**
+ * Writes out a list of strings to the reST document.
+ * If the list is empty, prints a newline character.
+ * @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
+ */
+ void WriteStringList(const char* format, const char* last_format,
+ const std::list& l) const;
+
+ /**
+ * @see WriteStringList(const char*, const char*,
+ * const std::list&>)
+ */
+ void WriteStringList(const char* format,
+ const std::list& l) const
+ {
+ WriteStringList(format, format, l);
+ }
+
+
+ /**
+ * Writes out a table of BroDocObj's to the reST document
+ * @param l A list of BroDocObj pointers
+ */
+ void WriteBroDocObjTable(const BroDocObjList& l) const;
+
+ /**
+ * Writes out a list of BroDocObj objects to the reST document
+ * @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)
+ */
+ void WriteBroDocObjList(const BroDocObjList& l, bool wantPublic,
+ const char* heading, char underline,
+ bool isShort) const;
+
+ /**
+ * Wraps the BroDocObjMap into a BroDocObjList and the writes that list
+ * to the reST document
+ * @see WriteBroDocObjList(const BroDocObjList&, bool, const char*, char,
+ bool)
+ */
+ void WriteBroDocObjList(const BroDocObjMap& m, bool wantPublic,
+ const char* heading, char underline,
+ bool isShort) const;
+
+ /**
+ * 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.
+ */
+ void WriteBroDocObjList(const BroDocObjList& l, const char* heading,
+ char underline) const;
+
+ /**
+ * Writes out a list of BroDocObj objects to the reST document
+ * @param l A list of BroDocObj pointers
+ */
+ void WriteBroDocObjList(const BroDocObjList& l) const;
+
+ /**
+ * Wraps the BroDocObjMap into a BroDocObjList and the writes that list
+ * to the reST document
+ * @see WriteBroDocObjList(const BroDocObjList&, const char*, char)
+ */
+ void WriteBroDocObjList(const BroDocObjMap& m, const char* heading,
+ char underline) const;
+
+ /**
+ * A wrapper to fprintf() that always uses the reST document
+ * for the FILE* argument.
+ * @param format A printf style format string.
+ */
+ void WriteToDoc(const char* format, ...) const;
+
+ /**
+ * Writes out a reST section heading
+ * @param heading The title of the heading to create
+ * @param underline The character to use to underline the section title
+ * within the reST document
+ */
+ void WriteSectionHeading(const char* heading, char underline) const;
+
+ /**
+ * Writes out given number of characters to reST document
+ * @param c the character to write
+ * @param n the number of characters to write
+ */
+ void WriteRepeatedChar(char c, size_t n) const;
+
+ /**
+ * 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;
+private:
+
+ /**
+ * 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();
+ }
+};
+
+#endif
diff --git a/src/BroDocObj.cc b/src/BroDocObj.cc
new file mode 100644
index 0000000000..d9fe16632b
--- /dev/null
+++ b/src/BroDocObj.cc
@@ -0,0 +1,168 @@
+#include
+#include
+#include
+#include "ID.h"
+#include "BroDocObj.h"
+
+BroDocObj::BroDocObj(const ID* id, std::list*& reST,
+ bool is_fake)
+ {
+ broID = id;
+ reST_doc_strings = reST;
+ reST = 0;
+ is_fake_id = is_fake;
+ use_role = 0;
+ FormulateShortDesc();
+ }
+
+BroDocObj::~BroDocObj()
+ {
+ if ( reST_doc_strings )
+ delete reST_doc_strings;
+
+ if ( is_fake_id )
+ delete broID;
+ }
+
+void BroDocObj::WriteReSTCompact(FILE* file, int max_col) const
+ {
+ ODesc desc;
+ desc.SetQuotes(1);
+ broID->DescribeReSTShort(&desc);
+
+ fprintf(file, "%s", desc.Description());
+
+ std::list::const_iterator it;
+
+ for ( it = short_desc.begin(); it != short_desc.end(); ++it )
+ {
+ int start_col;
+
+ if ( it == short_desc.begin() )
+ start_col = max_col - desc.Len() + 1;
+ else
+ {
+ start_col = max_col + 1;
+ fprintf(file, "\n");
+ }
+
+ for ( int i = 0; i < start_col; ++i )
+ fprintf(file, " ");
+
+ fprintf(file, "%s", it->c_str());
+ }
+ }
+
+int BroDocObj::LongestShortDescLen() const
+ {
+ size_t max = 0;
+
+ std::list::const_iterator it;
+
+ for ( it = short_desc.begin(); it != short_desc.end(); ++it )
+ {
+ if ( it->size() > max )
+ max = it->size();
+ }
+
+ return max;
+ }
+
+void BroDocObj::FormulateShortDesc()
+ {
+ if ( ! reST_doc_strings )
+ return;
+
+ short_desc.clear();
+ std::list::const_iterator it;
+
+ for ( it = reST_doc_strings->begin();
+ it != reST_doc_strings->end(); ++it )
+ {
+ // The short description stops at the first sentence or the
+ // first empty comment.
+ size_t end = it->find_first_of(".");
+
+ if ( end == string::npos )
+ {
+ std::string::const_iterator s;
+ bool empty = true;
+
+ for ( s = it->begin(); s != it->end(); ++s )
+ {
+ if ( *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r' )
+ {
+ empty = false;
+ short_desc.push_back(*it);
+ break;
+ }
+ }
+
+ if ( empty )
+ break;
+ }
+ else
+ {
+ short_desc.push_back(it->substr(0, end + 1));
+ break;
+ }
+ }
+ }
+
+void BroDocObj::WriteReST(FILE* file) const
+ {
+ int indent_spaces = 3;
+ ODesc desc;
+ desc.SetIndentSpaces(indent_spaces);
+ desc.SetQuotes(1);
+
+ broID->DescribeReST(&desc, use_role);
+
+ fprintf(file, "%s", desc.Description());
+
+ if ( HasDocumentation() )
+ {
+ fprintf(file, "\n");
+ std::list::const_iterator it;
+
+ for ( it = reST_doc_strings->begin();
+ it != reST_doc_strings->end(); ++it)
+ {
+ for ( int i = 0; i < indent_spaces; ++i )
+ fprintf(file, " ");
+
+ fprintf(file, "%s\n", it->c_str());
+ }
+ }
+
+ fprintf(file, "\n");
+ }
+
+int BroDocObj::ColumnSize() const
+ {
+ ODesc desc;
+ desc.SetQuotes(1);
+ broID->DescribeReSTShort(&desc);
+ return desc.Len();
+ }
+
+bool BroDocObj::IsPublicAPI() const
+ {
+ return (broID->Scope() == SCOPE_GLOBAL) ||
+ (broID->Scope() == SCOPE_MODULE && broID->IsExport());
+ }
+
+void BroDocObj::Combine(const BroDocObj* o)
+ {
+ if ( o->reST_doc_strings )
+ {
+ if ( ! reST_doc_strings )
+ reST_doc_strings = new std::list();
+
+ reST_doc_strings->splice(reST_doc_strings->end(),
+ *(o->reST_doc_strings));
+ }
+
+ delete o;
+ FormulateShortDesc();
+ }
diff --git a/src/BroDocObj.h b/src/BroDocObj.h
new file mode 100644
index 0000000000..0ad96afa86
--- /dev/null
+++ b/src/BroDocObj.h
@@ -0,0 +1,123 @@
+#ifndef brodocobj_h
+#define brodocobj_h
+
+#include
+#include
+#include
+
+#include "ID.h"
+
+/**
+ * This class wraps a Bro script identifier, providing methods relevant
+ * to automatic generation of reStructuredText (reST) documentation for it.
+ */
+class BroDocObj {
+public:
+ /**
+ * BroDocObj constructor
+ * @param id a pointer to an identifier that is to be documented
+ * @param reST a reference to a pointer of a list of strings that
+ * represent the reST documentation for the ID. The pointer
+ * will be set to 0 after this constructor finishes.
+ * @param is_fake whether the ID* is a dummy just for doc purposes
+ */
+ BroDocObj(const ID* id, std::list*& reST,
+ bool is_fake = false);
+
+ /**
+ * BroDocObj destructor
+ * Deallocates the memory associated with the list of reST strings
+ */
+ ~BroDocObj();
+
+ /**
+ * Writes the reST representation of this object which includes
+ * 1) a reST friendly description of the ID
+ * 2) "##" or "##<" stylized comments.
+ * Anything after these style of comments is inserted as-is into
+ * the reST document.
+ * @param file The (already opened) file to write the reST to.
+ */
+ void WriteReST(FILE* file) const;
+
+ /**
+ * Writes a compact version of the ID and associated documentation
+ * for insertion into a table.
+ * @param file The (already opened) file to write the reST to.
+ * @param max_col The maximum length of the first table column
+ */
+ void WriteReSTCompact(FILE* file, int max_col) const;
+
+ /**
+ * @return the column size required by the reST representation of the ID
+ */
+ int ColumnSize() const;
+
+ /**
+ * Check whether this documentation is part of the public API. In
+ * other words, this means that the identifier is declared as part of
+ * the global scope (has GLOBAL namespace or is exported from another
+ * namespace).
+ * @return true if the identifier is part of the script's public API
+ */
+ bool IsPublicAPI() const;
+
+ /**
+ * Return whether this object has documentation (## comments)
+ * @return true if the ID has comments associated with it
+ */
+ bool HasDocumentation() const
+ {
+ return reST_doc_strings && reST_doc_strings->size() > 0;
+ }
+
+ /**
+ * @return whether this object will use reST role (T) or directive (F)
+ * notation for the wrapped identifier. Roles are usually used
+ * for cross-referencing.
+ */
+ bool UseRole() const { return use_role; }
+
+ /**
+ * @param b whether this object will use reST role (T) or directive (F)
+ * notation for the wrapped identifier. Roles are usually used
+ * for cross-referencing.
+ */
+ void SetRole(bool b) { use_role = b; }
+
+ /**
+ * Append any reST documentation strings in a given BroDocObj to this
+ * object's list and then delete the given BroDocObj
+ * @param o a pointer to a BroDocObj to subsume
+ */
+ void Combine(const BroDocObj* o);
+
+ /**
+ * @return the name of the wrapped identifier
+ */
+ const char* Name() const { return broID->Name(); }
+
+ /**
+ * @return the longest string element of the short description's list of
+ * strings
+ */
+ int LongestShortDescLen() const;
+
+protected:
+ std::list* reST_doc_strings;
+ std::list short_desc;
+ const ID* broID;
+ bool is_fake_id; /**< Whether the ID* is a dummy just for doc purposes */
+ bool use_role; /**< Whether to use a reST role or directive for the ID */
+
+ /**
+ * Set the short_desc member to be a subset of reST_doc_strings.
+ * Specifically, short_desc will be everything in reST_doc_strings
+ * up until the first period or first empty string list element found.
+ */
+ void FormulateShortDesc();
+
+private:
+};
+
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9002c58076..b2e89033ad 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -285,6 +285,9 @@ set(bro_SRCS
BitTorrent.cc
BitTorrentTracker.cc
BPF_Program.cc
+ BroBifDoc.cc
+ BroDoc.cc
+ BroDocObj.cc
BroString.cc
CCL.cc
ChunkedIO.cc
diff --git a/src/Desc.cc b/src/Desc.cc
index baf3ad1160..86ae395aca 100644
--- a/src/Desc.cc
+++ b/src/Desc.cc
@@ -41,6 +41,7 @@ ODesc::ODesc(desc_type t, BroFile* arg_f)
want_quotes = 0;
do_flush = 1;
include_stats = 0;
+ indent_with_spaces = 0;
}
ODesc::~ODesc()
@@ -67,6 +68,12 @@ void ODesc::PopIndent()
NL();
}
+void ODesc::PopIndentNoNL()
+ {
+ if ( --indent_level < 0 )
+ internal_error("ODesc::PopIndent underflow");
+ }
+
void ODesc::Add(const char* s, int do_indent)
{
unsigned int n = strlen(s);
@@ -179,8 +186,17 @@ void ODesc::AddBytes(const BroString* s)
void ODesc::Indent()
{
- for ( int i = 0; i < indent_level; ++i )
- Add("\t", 0);
+ if ( indent_with_spaces > 0 )
+ {
+ for ( int i = 0; i < indent_level; ++i )
+ for ( int j = 0; j < indent_with_spaces; ++j )
+ Add(" ", 0);
+ }
+ else
+ {
+ for ( int i = 0; i < indent_level; ++i )
+ Add("\t", 0);
+ }
}
diff --git a/src/Desc.h b/src/Desc.h
index 49b331d51b..ff7e6e864d 100644
--- a/src/Desc.h
+++ b/src/Desc.h
@@ -51,8 +51,12 @@ public:
void PushIndent();
void PopIndent();
+ void PopIndentNoNL();
int GetIndentLevel() const { return indent_level; }
+ int IndentSpaces() const { return indent_with_spaces; }
+ void SetIndentSpaces(int i) { indent_with_spaces = i; }
+
void Add(const char* s, int do_indent=1);
void AddN(const char* s, int len) { AddBytes(s, len); }
void Add(int i);
@@ -135,6 +139,7 @@ protected:
int want_quotes;
int do_flush;
int include_stats;
+ int indent_with_spaces;
};
#endif
diff --git a/src/ID.cc b/src/ID.cc
index c908a8c3c4..8ba1913b87 100644
--- a/src/ID.cc
+++ b/src/ID.cc
@@ -607,6 +607,135 @@ void ID::DescribeExtended(ODesc* d) const
}
}
+void ID::DescribeReSTShort(ODesc* d) const
+ {
+ if ( is_type )
+ d->Add(":bro:type:`");
+ else
+ d->Add(":bro:id:`");
+
+ d->Add(name);
+ d->Add("`");
+
+ if ( type )
+ {
+ d->Add(": ");
+ d->Add(":bro:type:`");
+
+ if ( ! is_type && type->GetTypeID() )
+ d->Add(type->GetTypeID());
+ else
+ {
+ TypeTag t = type->Tag();
+
+ switch ( t ) {
+ case TYPE_TABLE:
+ d->Add(type->IsSet() ? "set" : type_name(t));
+ break;
+
+ case TYPE_FUNC:
+ d->Add(type->AsFuncType()->IsEvent() ? "event" : type_name(t));
+ break;
+
+ default:
+ d->Add(type_name(t));
+ }
+ }
+
+ d->Add("`");
+ }
+
+ if ( attrs )
+ {
+ d->SP();
+ attrs->DescribeReST(d);
+ }
+ }
+
+void ID::DescribeReST(ODesc* d, bool is_role) const
+ {
+ if ( is_role )
+ {
+ if ( is_type )
+ d->Add(":bro:type:`");
+ else
+ d->Add(":bro:id:`");
+ d->Add(name);
+ d->Add("`");
+ }
+ else
+ {
+ if ( is_type )
+ d->Add(".. bro:type:: ");
+ else
+ d->Add(".. bro:id:: ");
+ d->Add(name);
+ }
+
+ d->PushIndent();
+ d->NL();
+
+ if ( type )
+ {
+ d->Add(":Type: ");
+
+ if ( ! is_type && type->GetTypeID() )
+ {
+ d->Add(":bro:type:`");
+ d->Add(type->GetTypeID());
+ d->Add("`");
+ }
+ else
+ type->DescribeReST(d);
+
+ d->NL();
+ }
+
+ if ( attrs )
+ {
+ d->Add(":Attributes: ");
+ attrs->DescribeReST(d);
+ d->NL();
+ }
+
+ if ( val && type &&
+ type->Tag() != TYPE_FUNC &&
+ type->InternalType() != TYPE_INTERNAL_VOID )
+ {
+ d->Add(":Default:");
+
+ if ( type->InternalType() == TYPE_INTERNAL_OTHER )
+ {
+ switch ( type->Tag() ) {
+ case TYPE_TABLE:
+ if ( val->AsTable()->Length() == 0 )
+ {
+ d->Add(" ``{}``");
+ d->NL();
+ break;
+ }
+ // Fall-through.
+
+ default:
+ d->NL();
+ d->NL();
+ d->Add("::");
+ d->NL();
+ d->PushIndent();
+ val->DescribeReST(d);
+ d->PopIndent();
+ }
+ }
+
+ else
+ {
+ d->SP();
+ val->DescribeReST(d);
+ d->NL();
+ }
+ }
+ }
+
#ifdef DEBUG
void ID::UpdateValID()
{
diff --git a/src/ID.h b/src/ID.h
index f576232b0e..49d844ebc3 100644
--- a/src/ID.h
+++ b/src/ID.h
@@ -84,6 +84,9 @@ public:
void Describe(ODesc* d) const;
// Adds type and value to description.
void DescribeExtended(ODesc* d) const;
+ // Produces a description that's reST-ready.
+ void DescribeReST(ODesc* d, bool is_role=false) const;
+ void DescribeReSTShort(ODesc* d) const;
bool Serialize(SerialInfo* info) const;
static ID* Unserialize(UnserialInfo* info);
diff --git a/src/SerialObj.h b/src/SerialObj.h
index 9c02b21e5b..4a12d53fe6 100644
--- a/src/SerialObj.h
+++ b/src/SerialObj.h
@@ -330,6 +330,29 @@ public:
dst = 0; \
}
+#define UNSERIALIZE_OPTIONAL_STR_DEL(dst, del) \
+ { \
+ bool has_it; \
+ if ( ! info->s->Read(&has_it, "has_" #dst) ) \
+ { \
+ delete del; \
+ return 0; \
+ } \
+ \
+ if ( has_it ) \
+ { \
+ info->s->Read(&dst, 0, "has_" #dst); \
+ if ( ! dst ) \
+ { \
+ delete del; \
+ return 0; \
+ } \
+ } \
+ \
+ else \
+ dst = 0; \
+ }
+
#define UNSERIALIZE_OPTIONAL_STATIC(dst, unserialize, del) \
{ \
bool has_it; \
diff --git a/src/Type.cc b/src/Type.cc
index ce3bbc52af..bdb56b6aaa 100644
--- a/src/Type.cc
+++ b/src/Type.cc
@@ -10,6 +10,12 @@
#include "Scope.h"
#include "Serializer.h"
+#include
+#include
+#include