The easiest way to get started using Synopsis is to copy a config file and maybe a Makefile from one of the demos.
Let's say your project is called Player (it's a CD player!) has the following layout:
/src/Player/Makefile /src/Player/src/player.cc # main file /src/Player/src/gui.cc # GUI code /src/Player/src/driver.cc # CD playing code /src/Player/include/driver.hh # Classes for the CD playing code /src/Player/include/gui.hh # Classes for the GUI code /src/Player/syn/ # Intermediate Synopsis files will go here /src/Player/doc/html/ # Synopsis documentation will go here
Since it is a C++ project, we will copy the simple-config.py from the C++ demo. The C++ demo has several examples it compiles, so the config.py file there is a bit more complex than we need.
Your Makefile probably already has a variable FILES which lists the .cc files in src/, but Synopsis cares most about the header files. You will also need variables which contain the names of the .syn files generated by Synopsis' parser, so add these lines to your Makefile:
# Project files FILES = src/player.cc src/gui.cc src/driver.cc INCLUDES = include/driver.hh include/gui.hh # List of output .syn files for each input file SYN_FILES = $(patsubst %,syn/%.syn,$(FILES) $(INCLUDES))
The patsubst line is a feature of (GNU) make that replaces a word (the %) with the word prefixed with syn/ (so the file goes in the syn directory) and suffixed with .syn (to mark it as a Synopsis file). This is done for both the source files and the include files.
The first step is to add a rule which will compile your source files (including .hh files!) into Synopsis .syn files containing the parsed AST:
# A pattern rule to parse each input file $(SYN_FILES): syn/%.syn: % # Here $@ is the output .syn file and $< is the .cc or .hh file synopsis -c config.py -Wc,parser=C++ -o $@ $<
The first line there is a pattern rule. It says that for each of the SYN_FILES, the output syn/%.syn depends on %. This means, for example, that syn/src/player.cc.syn depends on src/player.cc.
The arguments to synopsis are:
Use the config file "config.py". This is the Python script with the configuration options in it. We will get to this file later...
Gives the "parser=C++" option to the configuration module. Note that the -Wx,foo,bar style of command line option is borrowed from the GCC parser. Options are separated by commas and are passed to the relevant modules. Other modules you can address in this way are the parser ("-Wp,-m,-s,myfile.cc.links"), the linker ("-Wl,-s,Foo::Bar") and the formatter ("-Wf,-s,styleshee.css"). In this case we are telling Synopsis to use the parser config with the name C++. This also tells it that the inputs are source files and must be parsed. See the config.py section below for more on this.
Output the AST to $@, which will be expanded by Make to be the output file, eg: syn/src/player.cc.syn
Input file, which will be expanded by Make to be the first dependancy - the source file, eg: src/player.cc
Next is a rule to combine the multiple .syn files into a single one containing the whole AST for your project:
# The combined AST file which is the result of linking all files together COMBINED = syn/combined.syn # Make the combined file depend on all the intermediate files $(COMBINED): $(SYN_FILES) # Link all the files together synopsis -c config.py -Wc,linker=Linker -o $(COMBINED) $(SYN_FILES)
The arguments to synopsis here are:
Similar to the previous -Wc option, this one tells Synopsis to use the linker config named as "Linker" in the config file. Since we didn't specify a parser, Synopsis assumes that the inputs are binary .syn files containing parsed ASTs.
A small note that make expands this variable to many files (5 in our example). Multiple AST files can be passed to the Linker, or to a formatter (in which case Synopsis links them with default options).
Note that although there is only one Linker module, you can have different configurations for it to do different things such as different commenting styles or stages of processing.
Finally, a rule to format the combined AST into the HTML output. This step and the previous one can be done in one go, but this way you can generate multiple output formats from the one AST.
# The (main) final output file HTML = doc/html/index.html # Make the output file depend on the combined file $(HTML): $(COMBINED) # Run synopsis to generate output synopsis -c config.py -Wc,formatter=HTML $(COMBINED)
The arguments should be obvious by now:
Tells Synopsis to use the formatter config named "HTML".
Some housekeeping rules come last: A nice "doc" target so you can run "make doc", and a clean target so you can run "make clean". If you already have a clean target, just add this to your existing one.
# A doc target which creates the output file doc: $(HTML) # A clean target which removes all the .syn files clean: rm -rf $(COMBINED) $(SYN_FILES)
The final Makefile looks like this:
# Project files FILES = src/player.cc src/gui.cc src/driver.cc INCLUDES = include/driver.hh include/gui.hh # List of output .syn files for each input file SYN_FILES = $(patsubst %,syn/%.syn,$(FILES) $(INCLUDES)) # The (main) final output file HTML = doc/html/index.html # The combined AST file which is the result of linking all files together COMBINED = syn/combined.syn # A doc target which creates the output file doc: $(HTML) # A clean target which removes all the .syn files clean: rm -rf $(COMBINED) $(SYN_FILES) # Make the output file depend on the combined file $(HTML): $(COMBINED) # Run synopsis to generate output synopsis -c config.py -Wc,formatter=HTML $(COMBINED) # Make the combined file depend on all the intermediate files $(COMBINED): $(SYN_FILES) # Link all the files together synopsis -c config.py -Wc,linker=Linker -o $(COMBINED) $(SYN_FILES) # A pattern rule to parse each input file $(SYN_FILES): syn/%.syn: % # Here $@ is the output .syn file and $< is the .cc or .hh file synopsis -c config.py -Wc,parser=C++ -o $@ $<
You can find this Makefile in demo/Tutorial/Player.
Now that the Makefile is written it's time to look at the config file. The simple-config.py looks like this:
# Config file for C++ demos # Only some files have comments in //. style from Synopsis.Config import Base class Config (Base): class Parser: class CXX (Base.Parser.CXX): main_file = 1 modules = { 'C++':CXX, } class Linker: class LinkerJava (Base.Linker.Linker): comment_processors = ['java', 'javatags', 'summary'] modules = { 'Linker':LinkerJava, } class Formatter: class HTML (Base.Formatter.HTML): stylesheet_file = '../html.css' modules = Base.Formatter.modules modules['HTML'] = HTML
It is important to remember that the config.py file is a real python script and is executed just like a Python program. This has the advantage of allowing familiar syntax and flexibility, but at the expense of a little security risk - however generating documentation for source code is probably less risky than running the source code itself, and a config.py file is easier to check.
The first Python line is to access the "default" config options in the module Synopsis.Config. The default options are stored in the Base class, which has many subclasses for the different modules. You can see these in either the Config.py file itself, or in the generated reference manual. It is not strictly necessary to use the Base class, but it simplifies the config file somewhat by providing many sensible defaults.
The rest of the file defines a class called Config, which has three subclasses called Parser, Linker and Formatter, representing the three stages of processing. Each of these subclasses contains a "modules" dictionary which lists different named configurations for that stage. Usually the only use for having different configurations is to use different modules (different parsers, different formatters), but you can also have multiple configurations for the same module. The most common example of this is to have multiple Linker configurations for different commenting styles and other options.
Let's examine the file a section at a time:
class Config (Base):
The Config class derives from the Base class (that we imported at the start of the file) which has a constructor to sort out the configurations in the config class. Remember that Python uses indentation to indicate scope, so the lines after this are all indented to indicate that they are part of the Config class.
class Parser:
This is the first subclass, containing configurations for the Parser stage.
class CXX (Base.Parser.CXX): main_file = 1
This is the only parser configuration in this config file. Configurations are classes too, but they are instantiated when they are used so they can have constructors to set options at run time. This configuration class is called "CXX" and derives from the class Base.Parser.CXX which has some default config options and also a constructor (so be sure to call it if you write your own).
Configuration options are set as attributes in the config object, which the parser examines when it runs. The only one here sets the "main_file" option to "1". See later chapters in this manual for more options, or the reference manual for a complete list.
modules = { 'C++':CXX, }
This little bit of code sets an attribute in the Parser class called modules, to a dictionary (note the braces aka curly brackets) of named configurations. Here 'C++' is the name of the configuration to be used on the command line, and CXX (no quotes!) refers to the CXX class in the current scope. Note that the class was called CXX rather than C++ since C++ is not a valid class name.
There is just one thing missing here: how does Synopsis actually know to use the C++ parser for this config? Surely it doesn't go by the name of the class... The answer lies in the fact that we derived from the default configuration class Base.Parser.CXX. Looking in the Config.py file shows (amongst other things):
class CXX: name = 'C++'
Synopsis appends this "name" attribute to "Synopsis.Parser." to make the module name "Synopsis.Parser.C++". This means that all parsers must be in the Synopsis.Parser package, for now.
To use a parser for a different language, the easiest thing to do is derive from one of Base.Parser.IDL or Base.Parser.Python, as appropriate. Note that the different parsers generally have different options.
Getting back to the config file, the Linker section is pretty similar to the Parser section, except for this line:
comment_processors = ['java', 'javatags', 'summary']
The syntax here is setting the comment_processors attribute to a list of strings. The (square) brackets indicate a list (parentheses aka round brackets indicate a tuple, which is an immutable list). Strings can be either single or double quotes - Python makes no distinction between the two so it's just a matter of style.
This particular option tells the Linker to apply the three comment processors called 'java', 'javatags' and 'summary' in that order. Comment processors are operations that use the comments attached to declarations to perform manipulations of either the comments or the AST itself:
Looks for comments with the java commenting style of /** foo bar */. Any comments not matching this format are removed, and then the /** and */ strings are also removed. Intermediate lines must begin with a *, so a multi-line comment looks like:
/** The first sentence is a summary. * The rest of the comment goes here, with each line * beginning with a star. The closing star-slash can go * either by itself or at the end of a line. */
Comments can have special "tags" which indicate either processing or formatting. This processor extracts the JavaDoc style tags from the comments and stores them with the comment for other processors or formatters to use. The tags must be at the end of the comment, but this allows them to span multiple lines if necessary. An example of JavaDoc tags is:
/** Returns the height of the given person. * @param firstname the first name of the person * @param surname the surname of the person * @return the height of the person or 0 if the person was not found */ float getHeight(std::string firstname, std::string surname);
This processor extracts a summary of each comment and stores it with the comment, allowing the documentation to display a (usually one line) summary next to declarations in order to keep the pages tidy, and show the full comments elsewhere (e.g. further down the page). This processor also combines the comments for each declaration, so it should be the last one in the list.
See chapter XXX for a full list of comment processors.
The HTML formatter we will have to change slightly. The stylesheet_file option tells the HTML formatter where to read the stylesheet from, and since the demo's are designed to run without Synopsis being installed, this is probably wrong. The default is to use the one installed in $prefix/share/Synopsis, so you can either delete the line or set it to the correct path. If you delete it, remember that Python needs the word "pass" for an empty class (or function):
class HTML (Base.Formatter.HTML): pass
Finally, the way the modules dictionary is set for the Formatter is a bit different. Here we first set it to the default value from the base class, then overwrite the entry for HTML with the new HTML formatter configuration. By using the default value like this, you get (default) configurations for all the different formatters.
That's it! Just run "make doc" and hopefully it should all work. You will see Synopsis being run for each input file, to combine the ASTs, and the format the output.
The HTML is output to whatever directory you set in the Makefile, which was written above as doc/html/. Point your web browser at the index.html file there to view the documentation, and you will see three frames (using the default configuration) similar to JavaDoc documentation:
This frame shows a tree heirarchy of the modules or files in the project. Clicking on a link opens that module or file in the "index" frame below.
This frame shows an index of the currently selected module or file, but only shows child classes, structs, namespaces or modules. When a module page is loaded in this frame and you have JavaScript enabled, a more detailed page will open in the main content area. For file pages you will need to click the "File Details" link.
This frame shows the main documentation. At the top of each page you can see a list of documentation areas you can visit: The Modules and Files open in the top-left frame, the Inheritance Graph/Tree and Name Index load in the main frame. For "scope pages" (i.e. the pages for classes and namespaces, including the global namespace which is the default page) the page is divided into summary and detail sections: the summary shows all the declarations in this class or namespace, with a summary for each. For declarations with more comments than just the summary, the name of the declaration will be highlighted, and clicking on it will take you to its detailed information further down the page.
You can also try out the other formatters. To see which formatters are available, run "synopsis -l". To use a particular formatter, type:
$ synopsis -c config.py -Wc,formatter=Dia -o player.dia syn/combined.syn
to use configuration from the config.py file (in our config file we just inherited the default options anyway), or you can just use the default options by typing:
$ synopsis -f Dia -o player.dia syn/combined.syn
You can also pass command line options to the formatter with the following syntax:
$ synopsis -f Dia -Wf,-p -o player.dia syn/combined.syn
This passes the -p option to the formatter. For the Dia formatter this tells it not to include parameters.