diff --git a/lib/compile/jar/slf4j-api-1.6.1.jar b/lib/compile/jar/slf4j-api-1.6.1.jar new file mode 100644 index 0000000..42e0ad0 Binary files /dev/null and b/lib/compile/jar/slf4j-api-1.6.1.jar differ diff --git a/lib/runtime/jar/logback-classic-0.9.26.jar b/lib/runtime/jar/logback-classic-0.9.26.jar new file mode 100644 index 0000000..b900b64 Binary files /dev/null and b/lib/runtime/jar/logback-classic-0.9.26.jar differ diff --git a/lib/runtime/jar/logback-core-0.9.26.jar b/lib/runtime/jar/logback-core-0.9.26.jar new file mode 100644 index 0000000..d50f3cd Binary files /dev/null and b/lib/runtime/jar/logback-core-0.9.26.jar differ diff --git a/lib/runtime/jar/slf4j-api-1.6.1.jar b/lib/runtime/jar/slf4j-api-1.6.1.jar new file mode 100644 index 0000000..42e0ad0 Binary files /dev/null and b/lib/runtime/jar/slf4j-api-1.6.1.jar differ diff --git a/project.properties b/project.properties index 4635431..99ea335 100644 --- a/project.properties +++ b/project.properties @@ -1,7 +1,7 @@ -#Sun, 25 Dec 2011 23:23:16 -0600 +#Wed, 28 Dec 2011 15:44:01 -0600 name=jlp -version=1.1 -build.number=7 +version=1.2 +build.number=10 lib.local=true release.dir=release main.class=com.jdblabs.jlp.JLPMain diff --git a/resources/main/logback.groovy b/resources/main/logback.groovy new file mode 100644 index 0000000..953ed54 --- /dev/null +++ b/resources/main/logback.groovy @@ -0,0 +1,17 @@ +import ch.qos.logback.classic.encoder.PatternLayoutEncoder +import ch.qos.logback.core.ConsoleAppender +import ch.qos.logback.core.FileAppender + +import static ch.qos.logback.classic.Level.* + +appender("stdout", ConsoleAppender) { + encoder(PatternLayoutEncoder) { + pattern = "%level %msg%n" }} + +appender("logfile", FileAppender) { + file = "jlp.log" + encoder(PatternLayoutEncoder) { + pattern = "%date %level %logger{10} [%file:%line] %msg%n" }} + +root(WARN, ["stdout"]) +//logger("com.jdblabs.jlp", TRACE, ["file"]) diff --git a/src/main/com/jdblabs/jlp/JLPBaseGenerator.groovy b/src/main/com/jdblabs/jlp/JLPBaseGenerator.groovy index 67be4b3..f8a5b12 100644 --- a/src/main/com/jdblabs/jlp/JLPBaseGenerator.groovy +++ b/src/main/com/jdblabs/jlp/JLPBaseGenerator.groovy @@ -9,15 +9,47 @@ import java.util.List import java.util.Map /** + * This class defines the interface for JLP Generators and implements basic + * functionality for the parse phase. * @org jlp.jdb-labs.com/JLPBaseGenerator */ public abstract class JLPBaseGenerator { + /** + * The generator works in close conjunction with the JLP Processor. + * This tight coupling in intended for these two classes. The distiction + * between the two classes is scope. The Processor class is concerned with + * data and behavior common to the whole documentation process whereas the + * Generator is concerned only with one file. The Generator does have + * access to information about the overall process through this link to the + * Processor. One example of the need for this link is the resolution of + * JLP link targets which may be defined in a different file but referenced + * in the file the Generator is processing. + * @org jlp.jdb-labs.com/notes/processor-generator-coupling + */ protected Processor processor protected JLPBaseGenerator(Processor processor) { this.processor = processor } + /** + * ### Generator phases + * + * There are two phases for JLP Generators: **parse** and **emit**. + * + * The **parse** phase allows the Generator to build up facts about the + * file being processed before emitting the documentation in the **emit** + * phase. There is a `parse` method for each `ASTNode` type. The default + * implementation for JLPBaseGenerator calls the `parse` methods in such a + * way that it visits each ASTNode in the file. + * + * The **emit** phase is where the Generator creates the documentation for + * each AST node. Unlike the parse phase, there is no default implementation + * for the emit phase as emitting the final result will be very dependant on + * the emitter. + * + * @org com.jdb-labs.jlp.JLPBaseGenerator/phases + */ protected void parse(SourceFile sourceFile) { sourceFile.blocks.each { block -> parse(block) } } diff --git a/src/main/com/jdblabs/jlp/JLPMain.groovy b/src/main/com/jdblabs/jlp/JLPMain.groovy index ca4d4f3..74ddeab 100644 --- a/src/main/com/jdblabs/jlp/JLPMain.groovy +++ b/src/main/com/jdblabs/jlp/JLPMain.groovy @@ -8,6 +8,8 @@ import com.jdblabs.jlp.ast.ASTNode import com.jdblabs.jlp.ast.SourceFile import org.parboiled.Parboiled import org.parboiled.parserunners.ReportingParseRunner +import org.slf4j.Logger +import org.slf4j.LoggerFactory /** * @api JLPMain is the entrypoint for the system. It is responsible for parsing @@ -16,6 +18,10 @@ import org.parboiled.parserunners.ReportingParseRunner */ public class JLPMain { + public static final String VERSION = "1.2" + + private static Logger log = LoggerFactory.getLogger(JLPMain.class) + public static void main(String[] args) { /// #### Define command-line options. @@ -33,18 +39,24 @@ public class JLPMain { longOpt: 'css-file', args: 1, required: false, argName: 'css-file') cli._(longOpt: 'relative-path-root', args: 1, required: false, 'Resolve all relative paths against this root.') + cli._(longOpt: 'version', 'Display the JLP version information.') /// #### Parse the options. def opts = cli.parse(args) - /// Display help if requested. + /// Display help and version information if requested. if (opts.h) { cli.usage() return } + if (opts.version) { + println "JLP v$VERSION" + return } + /// Get the relative path root (or set to current directory if it was /// not given) def pathRoot = new File(opts."relative-path-root" ?: ".") + log.debug("Relative path root: '{}'.", pathRoot.canonicalPath) /// If our root is non-existant we will print an error and exit.. This /// is possible if a relative path root was passed as an option. @@ -63,6 +75,8 @@ public class JLPMain { /// Create the output directory if it does not exist. if (!outputDir.exists()) outputDir.mkdirs() + log.debug("Output directory: '{}'.", outputDir.canonicalPath) + /// Get the CSS theme to use. We will start by assuming the default will /// be used. def css = JLPMain.class.getResourceAsStream("/jlp.css") @@ -76,7 +90,10 @@ public class JLPMain { cssFile = new File(pathRoot, cssFile.path) } /// Finally, make sure the CSS file actually exists. - if (cssFile.exists()) { css = cssFile } + if (cssFile.exists()) { + css = cssFile + log.debug("Loading CSS from this file: '{}'.", + cssFile.canonicalPath) } /// If it does not, we are going to warn the user and keep the /// default. @@ -111,9 +128,9 @@ public class JLPMain { /// If this file is a directory, we want to add all the files in it /// to our input list, recursing into all the subdirectories and - /// adding their files as well. + /// adding their files as well. We will ignore hidden files. if (file.isDirectory()) { file.eachFileRecurse { - if (it.isFile()) { inputFiles << it }}} + if (it.isFile() && !it.isHidden()) { inputFiles << it }}} /// Not a directory, just add the file. else { inputFiles << file } } diff --git a/src/main/com/jdblabs/jlp/JLPParser.java b/src/main/com/jdblabs/jlp/JLPParser.java index bc27efa..ea87717 100644 --- a/src/main/com/jdblabs/jlp/JLPParser.java +++ b/src/main/com/jdblabs/jlp/JLPParser.java @@ -1,6 +1,17 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp; import com.jdblabs.jlp.ast.SourceFile; +/** + * JLPParser is a simple interface. It has one method to return a parsed + * [`SourceFile`](jlp://jlp.jdb-labs.com/SourceFile) given an input string. It may + * be expanded in the future to be an abstract class implementing methods that + * take additional input for convenience. + * @org jlp.jdb-labs.com/JLPParser + */ public interface JLPParser { public SourceFile parse(String input); } diff --git a/src/main/com/jdblabs/jlp/JLPPegParser.java b/src/main/com/jdblabs/jlp/JLPPegParser.java index c80644a..beb93dd 100644 --- a/src/main/com/jdblabs/jlp/JLPPegParser.java +++ b/src/main/com/jdblabs/jlp/JLPPegParser.java @@ -10,6 +10,10 @@ import org.parboiled.Rule; import org.parboiled.annotations.*; import org.parboiled.parserunners.ReportingParseRunner; +/** + * + * @org jlp.jdb-labs.com/JLPPegParser + */ @BuildParseTree public class JLPPegParser extends BaseParser implements JLPParser { @@ -38,77 +42,79 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SourceFile = (Block / DocBlock / CodeBlock)+ + * + * SourceFile = (Block / DocBlock / CodeBlock)+ * * Pushes a SourceFile node on the stack. */ public Rule SourceFile() { return Sequence( - // At the start of processing a new SourceFile, we need to set up - // our internal state. + /// At the start of processing a new SourceFile, we need to set up + /// our internal state. - // Clear the line count. + /// Clear the line count. clearLineCount(), - // Add the top-level SourceFile AST node. + /// Add the top-level SourceFile AST node. push(new SourceFile()), - // A SourceFile is made up of one or more Blocks + /// A SourceFile is made up of one or more Blocks OneOrMore(Sequence( - // All of these options result in one new Block pushed onto the - // stack. + /// All of these options result in one new Block pushed onto the + /// stack. FirstOf( - // Match a whole Block. This pushes a Block on the stack. + /// Match a whole Block. This pushes a Block on the stack. Block(), - // A standalone DocBlock. We will create an empty CodeBlock - // to pair with it, then push a new Block onto the stack - // made from the DocBlock and the empty CodeBlock + /// A standalone DocBlock. We will create an empty CodeBlock + /// to pair with it, then push a new Block onto the stack + /// made from the DocBlock and the empty CodeBlock Sequence( - // 1. We need to remember the line number to create the - // Block + /// 1. We need to remember the line number to create the + /// Block push(curLineNum), - // 2. Match the DocBlock. + /// 2. Match the DocBlock. DocBlock(), - // 3. Create the empty CodeBlock. + /// 3. Create the empty CodeBlock. push(new CodeBlock(curLineNum)), - // 4. Create the Block and push it onto the stack. + /// 4. Create the Block and push it onto the stack. push(new Block((CodeBlock) pop(), (DocBlock) pop(), popAsInt()))), - // A standalone CodeBlock. Similar to the standalone - // DocBlock, we will create an empty DocBlock to pair with - // the CodeBlock to make a Block, which is pushed onto the - // stack: - // - // *Note: With the way the parser is currently written, - // this will only match a CodeBlock that occurs - // before any DocBlock.* + /// A standalone CodeBlock. Similar to the standalone + /// DocBlock, we will create an empty DocBlock to pair with + /// the CodeBlock to make a Block, which is pushed onto the + /// stack: + /// + /// *Note: With the way the parser is currently written, + /// this will only match a CodeBlock that occurs + /// before any DocBlock.* Sequence( - // 1. Remember the line number for the Block. + /// 1. Remember the line number for the Block. push(curLineNum), - // 2. Create the empty DocBlock. + /// 2. Create the empty DocBlock. push(new DocBlock(curLineNum)), - // 3. Match the CodeBlock + /// 3. Match the CodeBlock CodeBlock(), - // 4. Create the Block and push it onto the stack + /// 4. Create the Block and push it onto the stack push(new Block((CodeBlock) pop(), (DocBlock) pop(), popAsInt())))), - // pop the Block created by one of the above options and add it - // to the SourceFile + /// pop the Block created by one of the above options and add it + /// to the SourceFile addBlockToSourceFile((Block) pop())))); } /** * Parses the rule: - * Block = DocBlock CodeBlock + * + * Block = DocBlock CodeBlock * * Pushes a Block onto the stack */ @@ -117,14 +123,15 @@ public class JLPPegParser extends BaseParser implements JLPParser { push(curLineNum), DocBlock(), CodeBlock(), - // A DocBlock and a CodeBlock are pushed onto the stack by the - // above rules. Pop them off, along with the line number we pushed - // before that, and create a new Block node. + /// A DocBlock and a CodeBlock are pushed onto the stack by the + /// above rules. Pop them off, along with the line number we pushed + /// before that, and create a new Block node. push(new Block((CodeBlock) pop(), (DocBlock) pop(), popAsInt()))); } /** * Parses the rule: - * DocBlock = SDocBlock / MDocBlock + * + * DocBlock = SDocBlock / MDocBlock * * Pushes a DocBlock onto the stack. */ @@ -132,7 +139,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SDocBlock = (SDirective / SDocText)+ + * + * SDocBlock = (SDirective / SDocText)+ * * Pushes a DocBlock object onto the stack */ @@ -145,7 +153,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MDocBlock = MDOC_START (MDirective / MDocText)+ MDOC_END + * + * MDocBlock = MDOC_START (MDirective / MDocText)+ MDOC_END * * Pushes a DocBlock object onto the stack */ @@ -154,16 +163,17 @@ public class JLPPegParser extends BaseParser implements JLPParser { push(new DocBlock(curLineNum)), MDOC_START, ZeroOrMore(Sequence( - // We need to be careful to exclude MDOC_END here, as there can - // be some confusion otherwise between the start of a line with - // MDOC_LINE_START and MDOC_END depending on what values the - // user has chosen for them + /// We need to be careful to exclude MDOC_END here, as there can + /// be some confusion otherwise between the start of a line with + /// MDOC_LINE_START and MDOC_END depending on what values the + /// user has chosen for them TestNot(MDOC_END), FirstOf(MDirective(), MDocText()), addToDocBlock((ASTNode) pop()))), MDOC_END); } /** * Parses the rule: - * CodeBlock = (RemainingCodeLine)+ + * + * CodeBlock = (RemainingCodeLine)+ * * Pushes a CodeBlock onto the stack. */ @@ -175,7 +185,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SDirective = SDocLineStart AT (SLongDirective / SShortDirective) + * + * SDirective = SDocLineStart AT (SLongDirective / SShortDirective) * * Pushes a Directive node on the stack. */ @@ -185,7 +196,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MDirective = MDocLineStart? AT (MLongDirective / MShortDirective) + * + * MDirective = MDocLineStart? AT (MLongDirective / MShortDirective) * * Pushes a Directive node onto the stack. */ @@ -196,7 +208,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SLongDirective = + * + * SLongDirective = * (API_DIR / EXAMPLE_DIR) RemainingSDocLine SDocText? * * Pushes a Directive node onto the stack. @@ -217,7 +230,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MLongDirective = + * + * MLongDirective = * (API_DIR / EXAMPLE_DIR) RemainingMDocLine MDocText? * * Pushes a Directive node onto the stack. @@ -238,7 +252,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SShortDirective = (AUTHOR_DIR / ORG_DIR / COPYRIGHT_DIR) RemainingSDocLine + * + * SShortDirective = (AUTHOR_DIR / ORG_DIR / COPYRIGHT_DIR) RemainingSDocLine * * Pushes a Directive node onto the stack. */ @@ -252,7 +267,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MShortDirective = (AUTHOR_DIR / ORG_DIR / COPYRIGHT_DIR) RemainingMDocLine + * + * MShortDirective = (AUTHOR_DIR / ORG_DIR / COPYRIGHT_DIR) RemainingMDocLine * * Pushes a Directive node onto the stack. */ @@ -266,7 +282,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SDocText = (SDocLineStart !AT RemainingSDocLine)+ + * + * SDocText = (SDocLineStart !AT RemainingSDocLine)+ * * Pushes a DocText node onto the stack. */ @@ -279,7 +296,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MDocText = (MDocLineStart? !AT RemainingMDocLine)+ + * + * MDocText = (MDocLineStart? !AT RemainingMDocLine)+ * * Pushes a DocText node onto the stack. */ @@ -293,7 +311,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * SDocLineStart = SPACE* SDOC_START SPACE? + * + * SDocLineStart = SPACE* SDOC_START SPACE? */ Rule SDocLineStart() { return Sequence( @@ -301,7 +320,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * MDocLineStart = SPACE* !MDOC_END MDOC_LINE_START SPACE? + * + * MDocLineStart = SPACE* !MDOC_END MDOC_LINE_START SPACE? */ Rule MDocLineStart() { return Sequence( @@ -309,7 +329,8 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * RemainingSDocLine = ((!EOL)* EOL) / ((!EOL)+ EOI) + * + * RemainingSDocLine = ((!EOL)* EOL) / ((!EOL)+ EOI) */ Rule RemainingSDocLine() { return FirstOf( @@ -318,30 +339,32 @@ public class JLPPegParser extends BaseParser implements JLPParser { /** * Parses the rule: - * RemainingMDocLine = + * + * RemainingMDocLine = * ((!(EOL / MDOC_END))* EOL) / * ((!MDOC_END)+) */ Rule RemainingMDocLine() { return FirstOf( - // End of line, still within the an M-style comment block + /// End of line, still within the an M-style comment block Sequence( ZeroOrMore(Sequence(TestNot(FirstOf(EOL, MDOC_END)), ANY)), EOL, incLineCount()), - // End of M-style comment block + /// End of M-style comment block OneOrMore(Sequence(TestNot(MDOC_END), ANY))); } /** * Parses the rule: - * RemainingCodeLine = + * + * RemainingCodeLine = * ((!(EOL / MDOC_START / SDocLineStart))* EOL) / * (!(MDOC_START / SDocLineStart))+ */ Rule RemainingCodeLine() { return FirstOf( - // End of line, still within the code block. + /// End of line, still within the code block. Sequence( ZeroOrMore(Sequence( TestNot(FirstOf(EOL, MDOC_START, SDocLineStart())), @@ -349,7 +372,7 @@ public class JLPPegParser extends BaseParser implements JLPParser { EOL, incLineCount()), - // Found an MDOC_START or SDocLineStart + /// Found an MDOC_START or SDocLineStart OneOrMore(Sequence(TestNot(FirstOf(MDOC_START, SDocLineStart())), ANY))); } Rule AT = Ch('@').label("AT"); @@ -357,13 +380,13 @@ public class JLPPegParser extends BaseParser implements JLPParser { Rule NOT_EOL = Sequence(TestNot(EOL), ANY).label("NOT_EOL"); Rule SPACE = AnyOf(" \t").label("SPACE"); - // Configurable + /// Configurable Rule MDOC_START; Rule MDOC_END; Rule MDOC_LINE_START; Rule SDOC_START; - // directive terminals + /// directive terminals Rule AUTHOR_DIR = IgnoreCase("author"); Rule COPYRIGHT_DIR = IgnoreCase("copyright"); Rule API_DIR = IgnoreCase("api"); diff --git a/src/main/com/jdblabs/jlp/LinkAnchor.groovy b/src/main/com/jdblabs/jlp/LinkAnchor.groovy index c7cc865..54d163a 100644 --- a/src/main/com/jdblabs/jlp/LinkAnchor.groovy +++ b/src/main/com/jdblabs/jlp/LinkAnchor.groovy @@ -1,10 +1,28 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp import com.jdblabs.jlp.ast.Directive +/** + * A *LinkAnchor* in the documentation is very similar to an HTML anchor. It + * creates a reference in the documentation that can used by the author to + * link to this point. + * + * The author uses a URL with the `jlp` protocol to refer to anchors. For + * example: `jlp://jlp.jdb-labs.com/LinkAnchor` refers to this documentation. + * + * @api LinkAnchor is a data class to hold information about the link anchors + * defined by `@org` directives. + * @org jlp.jdb-labs.com/LinkAnchor + */ public class LinkAnchor { + /// The anchor id. This comes from the text after the directive. public String id + public Directive directive public String sourceDocId diff --git a/src/main/com/jdblabs/jlp/LiterateMarkdownGenerator.groovy b/src/main/com/jdblabs/jlp/LiterateMarkdownGenerator.groovy index 7d7423c..b028fc2 100644 --- a/src/main/com/jdblabs/jlp/LiterateMarkdownGenerator.groovy +++ b/src/main/com/jdblabs/jlp/LiterateMarkdownGenerator.groovy @@ -13,10 +13,14 @@ import org.pegdown.PegDownProcessor import java.util.List /** + * The LiterateMarkdownGenerator is an implementation of JLPBaseGenerator that + * uses [Markdown](http://daringfireball.net/projects/markdown/) to process the + * documentation into HTML output. * @org jlp.jdb-labs.com/LiterateMarkdownGenerator */ public class LiterateMarkdownGenerator extends JLPBaseGenerator { + /// We will use the PegDown library for generating the Markdown output. protected PegDownProcessor pegdown public LiterateMarkdownGenerator(Processor processor) { @@ -25,6 +29,13 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { pegdown = new PegDownProcessor( Extensions.TABLES | Extensions.DEFINITIONS) } + // =================================== + /** ### Parse phase implementation. ### */ + // =================================== + + /** Implement the parse phase for *Directive* nodes. We are interested + * specifically in saving the link anchor information from *org* + * directives. */ protected void parse(Directive directive) { switch(directive.type) { case DirectiveType.Org: @@ -39,14 +50,23 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { break // do nothing } } + /** We are not doing any parsing for *CodeBlocks* or *DocTexts*. We have to + * implement them, as they are abstract on JLPBaseGenerator. */ protected void parse(CodeBlock codeBlock) {} // nothing to do protected void parse(DocText docText) {} // nothing to do + // ================================== + /** ### Emit phase implementation. ### */ + // ================================== + + /** @api Emit a *SourceFile*. + * Each *SourceFile* becomes one ouput HTML file. This method is the entry + * point for a file to be emitted. */ protected String emit(SourceFile sourceFile) { StringBuilder sb = new StringBuilder() - // Create the HTML file head + /// Create the HTML head and begin the body. sb.append( """ @@ -64,10 +84,10 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { """) - // Emit the document to Markdown + /// Emit all of the blocks in the body of the html file. sourceFile.blocks.each { block -> sb.append(emit(block)) } - // Create the HTML file foot + /// Create the HTML footer. sb.append( """ @@ -77,39 +97,47 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { return sb.toString() } + /** @api Emit a *Block*. */ protected String emit(Block block) { StringBuilder sb = new StringBuilder() - // Look for an `@org` directive in the `Block` + /// Look for an `@org` directive in the `Block` (already found in the + /// parse phase).. Directive orgDir = block.docBlock.directives.find { it.type == DirectiveType.Org } - // Create the `tr` that will hold the `Block` + /// Create the `tr` that will hold the `Block`. If we found an `@org` + /// directive we will add the id here. if (orgDir) { sb.append("\n") } else { sb.append("") } - // Create the `td` for the documentation. + /// Create the `td` for the documentation. sb.append('\n') sb.append(emit(block.docBlock)) sb.append('') - // Create the `td` for the `CodeBlock` + /// Create the `td` for the `CodeBlock` sb.append('\n') sb.append(emit(block.codeBlock)) sb.append('') - // Close the table row. + /// Close the table row. sb.append('') } + /** @api Emit a *DocBlock*. */ protected String emit(DocBlock docBlock) { - // Create a queue for the doc block elements, we are going to - // sort them by type and line number + /// Create a queue for the doc block elements, we are going to + /// sort them by type and line number List emitQueue - // Later we will need a string builder to hold our result. + /// Later we will need a string builder to hold our result. StringBuilder sb - // Add all the directives + /** Add all the directives. We are also assigning priorities here that + * we will use along with the line numbers to sort the elements. Our + * goal is to preserve the order of doc blocks and doc-block-like + * elements (examples, api documentation, etc.) while pushing orgs, + * authorship and other directives to the top. */ emitQueue = docBlock.directives.collect { directive -> def queueItem = [lineNumber: directive.lineNumber, value: directive] switch(directive.type) { @@ -121,90 +149,98 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { return queueItem } - // Add all the doc text blocks + /// Add all the doc text blocks. emitQueue.addAll(docBlock.docTexts.collect { docText -> [lineNumber: docText.lineNumber, priority: 50, value: docText] }) - // Sort the emit queue by priority, then line number. + /// Sort the emit queue by priority, then line number. emitQueue.sort( {i1, i2 -> i1.priority != i2.priority ? i1.priority - i2.priority : i1.lineNumber - i2.lineNumber} as Comparator) - // Finally, we want to treat the whole block as one markdown chunk, so - // we will concatenate the values in the emit queue and then process - // the whole block once + /** Finally, we want to treat the whole block as one markdown chunk so + * we will concatenate the values in the emit queue and then send the + * whole block at once to the markdown processor. */ sb = new StringBuilder() emitQueue.each { queueItem -> sb.append(emit(queueItem.value)) } return processMarkdown(sb.toString()) } + /** @api Emit a *CodeBlock*. */ protected String emit(CodeBlock codeBlock) { def codeLines - // Collect the lines into an array. + /// Collect the lines into an array. codeLines = codeBlock.lines.collect { lineNumber, line -> [lineNumber, line] } - // Sort by line number. + /// Sort by line number. codeLines.sort({ i1, i2 -> i1[0] - i2[0] } as Comparator) codeLines = codeLines.collect { arr -> arr[1] } - // write out the lines in a
 block
+        /// Write out the lines in a 
 block
         return "
${codeLines.join('')}
" } + /** @api Emit a *DocText*. */ protected String emit(DocText docText) { return docText.value } + /** @api Emit a *Directive*. */ protected String emit(Directive directive) { switch(directive.type) { - // An `@api` directive is immediately processed and wrapped in a - // div (we need to process this now because Markdown does not - // process input inside HTML elements). + /** An `@api` directive is immediately processed and wrapped in a + * div (we need to process this now because Markdown does not + * process input inside HTML elements). */ case DirectiveType.Api: return "
" + processMarkdown(directive.value) + "
\n" - // An `@author` directive is turned into a definition list. + /// `@author` directive is turned into a definition list. + case DirectiveType.Author: return "Author\n: ${directive.value}\n" case DirectiveType.Copyright: return "\n© ${directive.value}\n" - // An `@example` directive is returned as is + /// An `@example` directive is returned as is. case DirectiveType.Example: return directive.value - // An `@org` directive is ignored. - case DirectiveType.Org: return "" } - } + /// An `@org` directive is ignored here. We already emitted the id + /// when we started the block. + case DirectiveType.Org: return "" } } + /** This is a helper method to process a block of text as Markdown. We need + * to do some additional processing to deal with `jlp://` org links that + * may be present. */ protected String processMarkdown(String markdown) { - // convert to HTML from Markdown + + /// Convert to HTML from Markdown String html = pegdown.markdownToHtml(markdown) - // replace internal `jlp://` links with actual links based on`@org` - // references - html = html.replaceAll(/jlp:\/\/([^\s"]+)/) { wholeMatch, linkId -> + /// Replace internal `jlp://` links with actual links based on`@org` + /// references. + html = html.replaceAll(/href=['"]jlp:\/\/([^\s"]+)['"]/) { wholeMatch, linkId -> - // Get the org data stored for this org id. + /// Get the org data we found in the parse phase for this org id. def link = processor.linkAnchors[linkId] String newLink if (!link) { // We do not have any reference to this id. /* TODO: log error */ - newLink = "broken_link(${linkId})" } + newLink = "href=\"broken_link(${linkId})\"" } - // This link points to a location in this document. + /// This link points to a location in this document. else if (processor.currentDocId == link.sourceDocId) { - newLink = "#$linkId" } + newLink = "href=\"#${linkId}\"" } - // The link should point to a different document. + /// The link should point to a different document. else { TargetDoc thisDoc = processor.currentDoc TargetDoc linkDoc = processor.docs[link.sourceDocId] @@ -213,13 +249,12 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator { thisDoc.sourceFile.parentFile, linkDoc.sourceFile) - // The target document may not be in the same directory - // as us, backtrack to the (relative) top of our directory - // structure. - newLink = pathToLinkedDoc + ".html#${linkId}" } + /** The target document may not be in the same directory + * as us, backtrack to the (relative) top of our directory + * structure. */ + newLink = 'href="' + pathToLinkedDoc + ".html#${linkId}\"" } return newLink } - return html; - } + return html; } } diff --git a/src/main/com/jdblabs/jlp/MarkdownParser.groovy b/src/main/com/jdblabs/jlp/MarkdownParser.groovy index 167cfcc..1604ecc 100644 --- a/src/main/com/jdblabs/jlp/MarkdownParser.groovy +++ b/src/main/com/jdblabs/jlp/MarkdownParser.groovy @@ -1,7 +1,14 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp import com.jdblabs.jlp.ast.* +/** + * @org jlp.jdb-labs.com/MarkdownParser + */ public class MarkdownParser implements JLPParser { public SourceFile parse(String input) { diff --git a/src/main/com/jdblabs/jlp/Processor.groovy b/src/main/com/jdblabs/jlp/Processor.groovy index da02880..43bd6e5 100644 --- a/src/main/com/jdblabs/jlp/Processor.groovy +++ b/src/main/com/jdblabs/jlp/Processor.groovy @@ -6,6 +6,8 @@ package com.jdblabs.jlp import org.parboiled.BaseParser import org.parboiled.Parboiled +import org.slf4j.Logger +import org.slf4j.LoggerFactory /** * Processor processes one batch of input files to create a set of output files. @@ -49,6 +51,8 @@ public class Processor { protected Map parsers = [:] protected Map generators = [:] + private Logger log = LoggerFactory.getLogger(getClass()) + /// ### Public Methods. /// @org jlp.jdb-labs.com/Processor/public-methods @@ -97,14 +101,16 @@ public class Processor { /// the parser for that file type and parse the file into an abstract /// syntax tree (AST). processDocs { + log.trace("Parsing '{}'.", currentDocId) def parser = getParser(sourceTypeForFile(currentDoc.sourceFile)) // TODO: error detection currentDoc.sourceAST = parser.parse(currentDoc.sourceFile.text) } /// * Run our generator parse phase (see - /// jlp://com.jdb-labs.jlp.JLPBaseGenerator/phases for an explanation - /// of the generator phases). + /// [`JLPBaseGenerator`](jlp://com.jdb-labs.jlp.JLPBaseGenerator/phases) + /// for an explanation of the generator phases). processDocs { + log.trace("Second-pass parsing for '{}'.", currentDocId) def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile)) // TODO: error detection generator.parse(currentDoc.sourceAST) } @@ -112,6 +118,7 @@ public class Processor { /// * Second pass by the generators, the emit phase. processDocs { + log.trace("Emitting documentation for '{}'.", currentDocId) def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile)) currentDoc.output = generator.emit(currentDoc.sourceAST) } @@ -125,6 +132,9 @@ public class Processor { File outputFile = new File(outputRoot, relativePath + ".html") File outputDir = outputFile.parentFile + log.trace("Saving output for '{}' to '{}'", + currentDocId, outputFile) + /// Create the directory for this file if it does not exist. if (!outputDir.exists()) { outputDir.mkdirs() } diff --git a/src/main/com/jdblabs/jlp/TargetDoc.groovy b/src/main/com/jdblabs/jlp/TargetDoc.groovy index 273c88f..996b45f 100644 --- a/src/main/com/jdblabs/jlp/TargetDoc.groovy +++ b/src/main/com/jdblabs/jlp/TargetDoc.groovy @@ -1,10 +1,22 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp import com.jdblabs.jlp.ast.SourceFile +/** + * @api TargetDoc is a data class to hold information about the output file. + * @org jlp.jdb-labs.com/TargetDoc + */ public class TargetDoc { + /// The result of parsing the input file. public SourceFile sourceAST + + /// The original source file. public File sourceFile + public String output } diff --git a/src/main/com/jdblabs/jlp/ast/ASTNode.groovy b/src/main/com/jdblabs/jlp/ast/ASTNode.groovy index d0ace5b..6622177 100644 --- a/src/main/com/jdblabs/jlp/ast/ASTNode.groovy +++ b/src/main/com/jdblabs/jlp/ast/ASTNode.groovy @@ -1,7 +1,16 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp.ast +/** + * This defines the interface for nodes of the source file parse tree. + * @org jlp.jdb-labs.com/ast/ASTNode + */ public class ASTNode { + /// This is the line number the element began on. protected int lineNumber public ASTNode(int lineNum) { this.lineNumber = lineNum } diff --git a/src/main/com/jdblabs/jlp/ast/Block.groovy b/src/main/com/jdblabs/jlp/ast/Block.groovy index d242e7e..a413ebf 100644 --- a/src/main/com/jdblabs/jlp/ast/Block.groovy +++ b/src/main/com/jdblabs/jlp/ast/Block.groovy @@ -1,5 +1,13 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp.ast +/** + * ASTNode for *Block*s. + * @org jlp.jdb-labs.com/ast/Block + */ public class Block extends ASTNode { public final DocBlock docBlock public final CodeBlock codeBlock diff --git a/src/main/com/jdblabs/jlp/ast/CodeBlock.groovy b/src/main/com/jdblabs/jlp/ast/CodeBlock.groovy index 0f40058..075eaeb 100644 --- a/src/main/com/jdblabs/jlp/ast/CodeBlock.groovy +++ b/src/main/com/jdblabs/jlp/ast/CodeBlock.groovy @@ -1,7 +1,16 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + * @org jlp.jdb-labs.com/ast/CodeBlock + */ package com.jdblabs.jlp.ast import java.util.Map +/** + * @api ASTNode for *CodeBlock*s. + * @org jlp.jdb-labs.com/ast/CodeBlock + */ public class CodeBlock extends ASTNode { public Map lines = [:] diff --git a/src/main/com/jdblabs/jlp/ast/Directive.groovy b/src/main/com/jdblabs/jlp/ast/Directive.groovy index 264d230..35047e9 100644 --- a/src/main/com/jdblabs/jlp/ast/Directive.groovy +++ b/src/main/com/jdblabs/jlp/ast/Directive.groovy @@ -1,7 +1,47 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp.ast +/** + * @api ASTNode for *Directive*s. + * A documentation directive allows the author to add metadata and processing + * instructions. + * @org jlp.jdb-labs.com/ast/Directive + */ public class Directive extends ASTNode { + /** + * There are currently five types of directive currently available: + * + * API + * : The *API* directive allows the author to seperate the parts of the + * documentation that should be included in javadoc-style API + * documentation, as opposed to full literate code-style documentation. + * + * Author + * : The *Author* directive is used to specify the author of a set of + * documentation. It can be used to denote the author of an entire file + * when used in the first documentation block or just the author of a + * specific method when used in the documentation block before that + * method. + * + * Copyright + * : Similar to *Author*, this directive allows you to mark the copyright + * information for a set of documentation. + * + * Example + * : Used to mark an example in the documentation. The full doc block + * following the directive will be included inline as an example, + * typically typeset in a monospace font. + * + * Org + * : Used to create a link anchor in the documentation. The `jlp` protocol + * in a URL allows the author to link back to a link anchor. Refer to + * the [LinkAnchor](jlp://jlp.jdb-labs.com/LinkAnchor) documentation for + * more information about link anchors. + */ public static enum DirectiveType { Api, Author, Copyright, Example, Org; diff --git a/src/main/com/jdblabs/jlp/ast/DocBlock.groovy b/src/main/com/jdblabs/jlp/ast/DocBlock.groovy index 41640fb..9279a10 100644 --- a/src/main/com/jdblabs/jlp/ast/DocBlock.groovy +++ b/src/main/com/jdblabs/jlp/ast/DocBlock.groovy @@ -1,8 +1,16 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp.ast import java.util.ArrayList import java.util.List +/** + * @api ASTNode for *DocBlock*s. + * @org jlp.jdb-labs.com/ast/DocBlock + */ public class DocBlock extends ASTNode { public List directives = new ArrayList() diff --git a/src/main/com/jdblabs/jlp/ast/DocText.groovy b/src/main/com/jdblabs/jlp/ast/DocText.groovy index e76f418..f174201 100644 --- a/src/main/com/jdblabs/jlp/ast/DocText.groovy +++ b/src/main/com/jdblabs/jlp/ast/DocText.groovy @@ -1,5 +1,13 @@ +/** + * @author Jonathan Bernard (jdb@jdb-labs.com) + * @copyright JDB Labs 2010-2011 + */ package com.jdblabs.jlp.ast +/** + * @api ASTNode for *DocText*s. + * @org jlp.jdb-labs.com/ast/DocText + */ public class DocText extends ASTNode { public String value diff --git a/src/main/com/jdblabs/jlp/ast/SourceFile.groovy b/src/main/com/jdblabs/jlp/ast/SourceFile.groovy index 4900df6..eb00d53 100644 --- a/src/main/com/jdblabs/jlp/ast/SourceFile.groovy +++ b/src/main/com/jdblabs/jlp/ast/SourceFile.groovy @@ -6,11 +6,24 @@ package com.jdblabs.jlp.ast /** * The top-level AST element. This represents a source file. - * @org jlp.jdb-labs.com/ast/SourceFile - */ + * @org jlp.jdb-labs.com/ast/SourceFile */ public class SourceFile { + + /** The source file gets split into two parts during the initial parsing by + * the PEG grammer: a list of documentation blocks and a list of code + * blocks. Both are stored in the `Blocks` but the code block may get + * processed again if there is a language-specific parser involved for code + * awareness. In this case, we will keep a copy of the result of that + * parsing here as well. */ + + /// A list of the blocks in this `SourceFile`. public List blocks = [] + + /// The result from parsing the code in this source file. Currently there + /// are no language-specific parsers and this is always `null`. public def codeAST + /// The id for this source file, currently set to the path name for the + /// input file relative to the input root. public String id }