HTML Document Generation
JavaDoc is useful documentation, but insufficient to give potential users how a tool or package is to be used. HTML is a standard medium and language for doing this. And in such documentation, code examples and their inputs and outputs should be given to illustrate key ideas and idiomatic usage. Writing such documentation is hard enough -- but keeping the examples up-to-date with ongoing package updates and refactorings without tool support is close to impossible.
HTML documentation for MDELite was produced by a document generator -- a simple Java class that extracts code examples + input/output files from regression tests and inserts them at designated locations into HTML templates to yield up-to-date documentation. The entire process of document generation is part of MDELite regression tests, which not only runs the afore-mentioned regression tests, it also generates the HTML output.
The file MDELite.DocumentGenerator offers a single static method to accomplish this:
void main( Source sourceCodeFile, String htmlTemplateFile, String htmlOutputFile )
where:
- sourceCodeFile is the path to a source file of a Java regression test that has examples to be read.
- htmTemplateFIle is the path to an .htm template file that has "holes" for examples and for input/output files.
- htmlOutputFIle is the path to a .html file that has the "holes" filled in.
Table of Contents
Examples of DocumentGeneration can be found in the MDELite Netbeans Project in files: test/DML/Prolog/Doc*.java.
Source Java Code File Format
Regression tests are Java files. A code example in such a file looks like:
@Test
public void toTableTest() {
RegTest.Utility.redirectStdOut(errorfile);
toTable();
RegTest.Utility.validate(errorfile, correct + "toTable.txt", false);
}
///toTable
void toTable() {
DB db = DB.read(testdata + "dogOwner.do.pl");
Table dog = db.getTableEH("dog");
// Step 1: stream dog, and collect into a new dog table
Table dog2 = dog.stream().collect(new TuplesToTable());
boolean result = dog2.equals(dog);
System.out.format("dog2 %s dog", result ? "=" : "!=");
}
///toTable
The JUnit regression test is indicated by the @Test annotation. The code example is inbetween the ///toTable markers, where the example is named "toTable". The DocumentGenerator reads a JUnit test file and harvests the text of examples and stores them by their name.
HTM Template File
An HTM template file ends in ".htm". It is a standard HTML file with the followning exceptions:
- A "hole" in which a code example is to be place is written as 3 lines of preformatted text, a blank line, followed by an indented, bold line with a ---exampleName--- marker in Courier font, followed by blank line:
---toTable---
- A "hole" for the contents of an entire file is to be placed is written as 3 lines of preformatted text, a blank line, followed by an indented, bold line with a threeColon filename threeColon marker in Courier font, followed by blank line:
:::DocGen/Figures/fileHole.txt:::
That's it. The purpose of the template file is to provide text to explain examples and illustrate input and outputs (files) to these examples.
HTML Output File
As said above, the Template file with its holes are read in. So too is a single Java regression test with examples. The DocumentGenerator fills these "holes" with named code examples that it harvested and the contents of files that it read. The resulting text is written to the "html output file".
Usage
The typical usage of DocumentGenerator is a single call, written as JUnit test (so when regression tests are run, up-to-date documentation is produced):
@Test
public void families() {
main("src/LectureExamples/FamiliesM2M.java", "DocGen/FamiliesDemo.htm", "Docs/FamiliesDemo.html");
}
Sometimes code examples are taken from several Java regression tests. In such cases, html output files are placed in a directory that ultimately will be deleted, such as this example where a template file requires code examples from three different sources:
@Test
public void allegories() {
main("src/LectureExamples/allegory/Main.java", "DocGen/AllegoryDemo.htm", "DELETE.htm");
main("src/LectureExamples/allegory/PDD.java", "DELETE.htm", "DELETE1.htm");
main("test/LectureExamples/Correct/x.PDD.pl", "DELETE1.htm", "Docs/AllegoryDemo.html");
}
Here's another idiom: A tool is invoked to produce a file whose output is to be inserted into an HTMLOutputFile.
void yumlSetup() {
Yuml.ClassParser.main("test/BuildDocumentation/Test/x.yuml.yuml", "outYuml1.txt");
Yuml.ClassParser.main("test/BuildDocumentation/Test/y.yuml.yuml", "outYuml2.txt");
}
@Test
public void yuml() {
yumlSetup();
main("test/BuildDocumentation/test/nothing.jav", "DocGen/YumlManualTemplate.htm", "Docs/YumlManual.html");
}
The method yumlSetup() calls the Yuml.ClassParser to parse two different files (x.yuml.yuml and y.yuml.yuml) into separate output files. These files are then read by the YumlManualTemplate.htm template file to fill "file holes". Interestingly, in this case, there is no Java regression test from which to harvest code examples -- that is, the template file contains holes only for files. So a "nothing.jav" file -- literally an empty file -- is given as the Java file to harvest.
JUnit Test Structure
Nothing fancy is needed. You should have a JUnit setUpClass() method to create a generated "Docs" directory in red below (which you should delete prior to running regression tests as part of ANT makefiles). Then you should have a series of HTML file generation tests in purple below, terminating with the creation of an index.html file from a template that, in this case, has no code or file holes to fill:
public class BuildDocTest extends DocumentGenerator {
@BeforeClass
public static void setUpClass() {
new File("Docs").mkdir();
}
@Test
public void m2m() {
main("test/DML/PrologDB/DocExamplesTest.java", "DocGen/M2MTemplate.htm", "Docs/M2MPrograms.html");
}
...
@Test
public void index() throws Exception {
main("test/BuildDocumentation/test/nothing.jav", "DocGen/indexTemplate.htm", "Docs/index.html");
}
Note: if a hole cannot be filled, either because there is no code example with the given name or that the file referenced does not exist, the marker of the code example or file will remain unchanged.