Hidden files are ignored. Added --version option and logging.

* Added logging with SLF4J and Logback
* Added `--version` option.
* Mofidied the input file rules. When an input object is a directory, JLPMain is
  adding all the files in that directory and its subdirectories. Now JLPMain is
  ignoring hidden files in the directory and subdirs. A file named explicitly on
  the command line is still included regardless of if it is hidden or not.
* Documentation continues.
This commit is contained in:
Jonathan Bernard 2011-12-29 10:53:14 -06:00
parent f5c7ac64e3
commit bfc0c12127
22 changed files with 393 additions and 116 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
#Sun, 25 Dec 2011 23:23:16 -0600 #Wed, 28 Dec 2011 15:44:01 -0600
name=jlp name=jlp
version=1.1 version=1.2
build.number=7 build.number=10
lib.local=true lib.local=true
release.dir=release release.dir=release
main.class=com.jdblabs.jlp.JLPMain main.class=com.jdblabs.jlp.JLPMain

View File

@ -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"])

View File

@ -9,15 +9,47 @@ import java.util.List
import java.util.Map 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 * @org jlp.jdb-labs.com/JLPBaseGenerator
*/ */
public abstract class 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 Processor processor
protected JLPBaseGenerator(Processor processor) { protected JLPBaseGenerator(Processor processor) {
this.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) { protected void parse(SourceFile sourceFile) {
sourceFile.blocks.each { block -> parse(block) } } sourceFile.blocks.each { block -> parse(block) } }

View File

@ -8,6 +8,8 @@ import com.jdblabs.jlp.ast.ASTNode
import com.jdblabs.jlp.ast.SourceFile import com.jdblabs.jlp.ast.SourceFile
import org.parboiled.Parboiled import org.parboiled.Parboiled
import org.parboiled.parserunners.ReportingParseRunner 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 * @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 class JLPMain {
public static final String VERSION = "1.2"
private static Logger log = LoggerFactory.getLogger(JLPMain.class)
public static void main(String[] args) { public static void main(String[] args) {
/// #### Define command-line options. /// #### Define command-line options.
@ -33,18 +39,24 @@ public class JLPMain {
longOpt: 'css-file', args: 1, required: false, argName: 'css-file') longOpt: 'css-file', args: 1, required: false, argName: 'css-file')
cli._(longOpt: 'relative-path-root', args: 1, required: false, cli._(longOpt: 'relative-path-root', args: 1, required: false,
'Resolve all relative paths against this root.') 'Resolve all relative paths against this root.')
cli._(longOpt: 'version', 'Display the JLP version information.')
/// #### Parse the options. /// #### Parse the options.
def opts = cli.parse(args) def opts = cli.parse(args)
/// Display help if requested. /// Display help and version information if requested.
if (opts.h) { if (opts.h) {
cli.usage() cli.usage()
return } return }
if (opts.version) {
println "JLP v$VERSION"
return }
/// Get the relative path root (or set to current directory if it was /// Get the relative path root (or set to current directory if it was
/// not given) /// not given)
def pathRoot = new File(opts."relative-path-root" ?: ".") 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 /// 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. /// 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. /// Create the output directory if it does not exist.
if (!outputDir.exists()) outputDir.mkdirs() 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 /// Get the CSS theme to use. We will start by assuming the default will
/// be used. /// be used.
def css = JLPMain.class.getResourceAsStream("/jlp.css") def css = JLPMain.class.getResourceAsStream("/jlp.css")
@ -76,7 +90,10 @@ public class JLPMain {
cssFile = new File(pathRoot, cssFile.path) } cssFile = new File(pathRoot, cssFile.path) }
/// Finally, make sure the CSS file actually exists. /// 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 /// If it does not, we are going to warn the user and keep the
/// default. /// default.
@ -111,9 +128,9 @@ public class JLPMain {
/// If this file is a directory, we want to add all the files in it /// 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 /// 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 (file.isDirectory()) { file.eachFileRecurse {
if (it.isFile()) { inputFiles << it }}} if (it.isFile() && !it.isHidden()) { inputFiles << it }}}
/// Not a directory, just add the file. /// Not a directory, just add the file.
else { inputFiles << file } } else { inputFiles << file } }

View File

@ -1,6 +1,17 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp; package com.jdblabs.jlp;
import com.jdblabs.jlp.ast.SourceFile; 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 interface JLPParser {
public SourceFile parse(String input); } public SourceFile parse(String input); }

View File

@ -10,6 +10,10 @@ import org.parboiled.Rule;
import org.parboiled.annotations.*; import org.parboiled.annotations.*;
import org.parboiled.parserunners.ReportingParseRunner; import org.parboiled.parserunners.ReportingParseRunner;
/**
*
* @org jlp.jdb-labs.com/JLPPegParser
*/
@BuildParseTree @BuildParseTree
public class JLPPegParser extends BaseParser<Object> implements JLPParser { public class JLPPegParser extends BaseParser<Object> implements JLPParser {
@ -38,76 +42,78 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SourceFile = (Block / DocBlock / CodeBlock)+ * SourceFile = (Block / DocBlock / CodeBlock)+
* *
* Pushes a SourceFile node on the stack. * Pushes a SourceFile node on the stack.
*/ */
public Rule SourceFile() { public Rule SourceFile() {
return Sequence( return Sequence(
// At the start of processing a new SourceFile, we need to set up /// At the start of processing a new SourceFile, we need to set up
// our internal state. /// our internal state.
// Clear the line count. /// Clear the line count.
clearLineCount(), clearLineCount(),
// Add the top-level SourceFile AST node. /// Add the top-level SourceFile AST node.
push(new SourceFile()), 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( OneOrMore(Sequence(
// All of these options result in one new Block pushed onto the /// All of these options result in one new Block pushed onto the
// stack. /// stack.
FirstOf( FirstOf(
// Match a whole Block. This pushes a Block on the stack. /// Match a whole Block. This pushes a Block on the stack.
Block(), Block(),
// A standalone DocBlock. We will create an empty CodeBlock /// A standalone DocBlock. We will create an empty CodeBlock
// to pair with it, then push a new Block onto the stack /// to pair with it, then push a new Block onto the stack
// made from the DocBlock and the empty CodeBlock /// made from the DocBlock and the empty CodeBlock
Sequence( Sequence(
// 1. We need to remember the line number to create the /// 1. We need to remember the line number to create the
// Block /// Block
push(curLineNum), push(curLineNum),
// 2. Match the DocBlock. /// 2. Match the DocBlock.
DocBlock(), DocBlock(),
// 3. Create the empty CodeBlock. /// 3. Create the empty CodeBlock.
push(new CodeBlock(curLineNum)), 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(), push(new Block((CodeBlock) pop(), (DocBlock) pop(),
popAsInt()))), popAsInt()))),
// A standalone CodeBlock. Similar to the standalone /// A standalone CodeBlock. Similar to the standalone
// DocBlock, we will create an empty DocBlock to pair with /// DocBlock, we will create an empty DocBlock to pair with
// the CodeBlock to make a Block, which is pushed onto the /// the CodeBlock to make a Block, which is pushed onto the
// stack: /// stack:
// ///
// *Note: With the way the parser is currently written, /// *Note: With the way the parser is currently written,
// this will only match a CodeBlock that occurs /// this will only match a CodeBlock that occurs
// before any DocBlock.* /// before any DocBlock.*
Sequence( Sequence(
// 1. Remember the line number for the Block. /// 1. Remember the line number for the Block.
push(curLineNum), push(curLineNum),
// 2. Create the empty DocBlock. /// 2. Create the empty DocBlock.
push(new DocBlock(curLineNum)), push(new DocBlock(curLineNum)),
// 3. Match the CodeBlock /// 3. Match the CodeBlock
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(), push(new Block((CodeBlock) pop(), (DocBlock) pop(),
popAsInt())))), popAsInt())))),
// pop the Block created by one of the above options and add it /// pop the Block created by one of the above options and add it
// to the SourceFile /// to the SourceFile
addBlockToSourceFile((Block) pop())))); } addBlockToSourceFile((Block) pop())))); }
/** /**
* Parses the rule: * Parses the rule:
*
* Block = DocBlock CodeBlock * Block = DocBlock CodeBlock
* *
* Pushes a Block onto the stack * Pushes a Block onto the stack
@ -117,13 +123,14 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
push(curLineNum), push(curLineNum),
DocBlock(), CodeBlock(), DocBlock(), CodeBlock(),
// A DocBlock and a CodeBlock are pushed onto the stack by the /// A DocBlock and a CodeBlock are pushed onto the stack by the
// above rules. Pop them off, along with the line number we pushed /// above rules. Pop them off, along with the line number we pushed
// before that, and create a new Block node. /// before that, and create a new Block node.
push(new Block((CodeBlock) pop(), (DocBlock) pop(), popAsInt()))); } push(new Block((CodeBlock) pop(), (DocBlock) pop(), popAsInt()))); }
/** /**
* Parses the rule: * Parses the rule:
*
* DocBlock = SDocBlock / MDocBlock * DocBlock = SDocBlock / MDocBlock
* *
* Pushes a DocBlock onto the stack. * Pushes a DocBlock onto the stack.
@ -132,6 +139,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SDocBlock = (SDirective / SDocText)+ * SDocBlock = (SDirective / SDocText)+
* *
* Pushes a DocBlock object onto the stack * Pushes a DocBlock object onto the stack
@ -145,6 +153,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* MDocBlock = MDOC_START (MDirective / MDocText)+ MDOC_END * MDocBlock = MDOC_START (MDirective / MDocText)+ MDOC_END
* *
* Pushes a DocBlock object onto the stack * Pushes a DocBlock object onto the stack
@ -154,15 +163,16 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
push(new DocBlock(curLineNum)), push(new DocBlock(curLineNum)),
MDOC_START, MDOC_START,
ZeroOrMore(Sequence( ZeroOrMore(Sequence(
// We need to be careful to exclude MDOC_END here, as there can /// We need to be careful to exclude MDOC_END here, as there can
// be some confusion otherwise between the start of a line with /// be some confusion otherwise between the start of a line with
// MDOC_LINE_START and MDOC_END depending on what values the /// MDOC_LINE_START and MDOC_END depending on what values the
// user has chosen for them /// user has chosen for them
TestNot(MDOC_END), FirstOf(MDirective(), MDocText()), TestNot(MDOC_END), FirstOf(MDirective(), MDocText()),
addToDocBlock((ASTNode) pop()))), addToDocBlock((ASTNode) pop()))),
MDOC_END); } MDOC_END); }
/** /**
* Parses the rule: * Parses the rule:
*
* CodeBlock = (RemainingCodeLine)+ * CodeBlock = (RemainingCodeLine)+
* *
* Pushes a CodeBlock onto the stack. * Pushes a CodeBlock onto the stack.
@ -175,6 +185,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SDirective = SDocLineStart AT (SLongDirective / SShortDirective) * SDirective = SDocLineStart AT (SLongDirective / SShortDirective)
* *
* Pushes a Directive node on the stack. * Pushes a Directive node on the stack.
@ -185,6 +196,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* MDirective = MDocLineStart? AT (MLongDirective / MShortDirective) * MDirective = MDocLineStart? AT (MLongDirective / MShortDirective)
* *
* Pushes a Directive node onto the stack. * Pushes a Directive node onto the stack.
@ -196,6 +208,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SLongDirective = * SLongDirective =
* (API_DIR / EXAMPLE_DIR) RemainingSDocLine SDocText? * (API_DIR / EXAMPLE_DIR) RemainingSDocLine SDocText?
* *
@ -217,6 +230,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* MLongDirective = * MLongDirective =
* (API_DIR / EXAMPLE_DIR) RemainingMDocLine MDocText? * (API_DIR / EXAMPLE_DIR) RemainingMDocLine MDocText?
* *
@ -238,6 +252,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * 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. * Pushes a Directive node onto the stack.
@ -252,6 +267,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * 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. * Pushes a Directive node onto the stack.
@ -266,6 +282,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SDocText = (SDocLineStart !AT RemainingSDocLine)+ * SDocText = (SDocLineStart !AT RemainingSDocLine)+
* *
* Pushes a DocText node onto the stack. * Pushes a DocText node onto the stack.
@ -279,6 +296,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* MDocText = (MDocLineStart? !AT RemainingMDocLine)+ * MDocText = (MDocLineStart? !AT RemainingMDocLine)+
* *
* Pushes a DocText node onto the stack. * Pushes a DocText node onto the stack.
@ -293,6 +311,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* SDocLineStart = SPACE* SDOC_START SPACE? * SDocLineStart = SPACE* SDOC_START SPACE?
*/ */
Rule SDocLineStart() { Rule SDocLineStart() {
@ -301,6 +320,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* MDocLineStart = SPACE* !MDOC_END MDOC_LINE_START SPACE? * MDocLineStart = SPACE* !MDOC_END MDOC_LINE_START SPACE?
*/ */
Rule MDocLineStart() { Rule MDocLineStart() {
@ -309,6 +329,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* RemainingSDocLine = ((!EOL)* EOL) / ((!EOL)+ EOI) * RemainingSDocLine = ((!EOL)* EOL) / ((!EOL)+ EOI)
*/ */
Rule RemainingSDocLine() { Rule RemainingSDocLine() {
@ -318,30 +339,32 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
/** /**
* Parses the rule: * Parses the rule:
*
* RemainingMDocLine = * RemainingMDocLine =
* ((!(EOL / MDOC_END))* EOL) / * ((!(EOL / MDOC_END))* EOL) /
* ((!MDOC_END)+) * ((!MDOC_END)+)
*/ */
Rule RemainingMDocLine() { Rule RemainingMDocLine() {
return FirstOf( 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( Sequence(
ZeroOrMore(Sequence(TestNot(FirstOf(EOL, MDOC_END)), ANY)), ZeroOrMore(Sequence(TestNot(FirstOf(EOL, MDOC_END)), ANY)),
EOL, EOL,
incLineCount()), incLineCount()),
// End of M-style comment block /// End of M-style comment block
OneOrMore(Sequence(TestNot(MDOC_END), ANY))); } OneOrMore(Sequence(TestNot(MDOC_END), ANY))); }
/** /**
* Parses the rule: * Parses the rule:
*
* RemainingCodeLine = * RemainingCodeLine =
* ((!(EOL / MDOC_START / SDocLineStart))* EOL) / * ((!(EOL / MDOC_START / SDocLineStart))* EOL) /
* (!(MDOC_START / SDocLineStart))+ * (!(MDOC_START / SDocLineStart))+
*/ */
Rule RemainingCodeLine() { Rule RemainingCodeLine() {
return FirstOf( return FirstOf(
// End of line, still within the code block. /// End of line, still within the code block.
Sequence( Sequence(
ZeroOrMore(Sequence( ZeroOrMore(Sequence(
TestNot(FirstOf(EOL, MDOC_START, SDocLineStart())), TestNot(FirstOf(EOL, MDOC_START, SDocLineStart())),
@ -349,7 +372,7 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
EOL, EOL,
incLineCount()), incLineCount()),
// Found an MDOC_START or SDocLineStart /// Found an MDOC_START or SDocLineStart
OneOrMore(Sequence(TestNot(FirstOf(MDOC_START, SDocLineStart())), ANY))); } OneOrMore(Sequence(TestNot(FirstOf(MDOC_START, SDocLineStart())), ANY))); }
Rule AT = Ch('@').label("AT"); Rule AT = Ch('@').label("AT");
@ -357,13 +380,13 @@ public class JLPPegParser extends BaseParser<Object> implements JLPParser {
Rule NOT_EOL = Sequence(TestNot(EOL), ANY).label("NOT_EOL"); Rule NOT_EOL = Sequence(TestNot(EOL), ANY).label("NOT_EOL");
Rule SPACE = AnyOf(" \t").label("SPACE"); Rule SPACE = AnyOf(" \t").label("SPACE");
// Configurable /// Configurable
Rule MDOC_START; Rule MDOC_START;
Rule MDOC_END; Rule MDOC_END;
Rule MDOC_LINE_START; Rule MDOC_LINE_START;
Rule SDOC_START; Rule SDOC_START;
// directive terminals /// directive terminals
Rule AUTHOR_DIR = IgnoreCase("author"); Rule AUTHOR_DIR = IgnoreCase("author");
Rule COPYRIGHT_DIR = IgnoreCase("copyright"); Rule COPYRIGHT_DIR = IgnoreCase("copyright");
Rule API_DIR = IgnoreCase("api"); Rule API_DIR = IgnoreCase("api");

View File

@ -1,10 +1,28 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp package com.jdblabs.jlp
import com.jdblabs.jlp.ast.Directive 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 { public class LinkAnchor {
/// The anchor id. This comes from the text after the directive.
public String id public String id
public Directive directive public Directive directive
public String sourceDocId public String sourceDocId

View File

@ -13,10 +13,14 @@ import org.pegdown.PegDownProcessor
import java.util.List 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 * @org jlp.jdb-labs.com/LiterateMarkdownGenerator
*/ */
public class LiterateMarkdownGenerator extends JLPBaseGenerator { public class LiterateMarkdownGenerator extends JLPBaseGenerator {
/// We will use the PegDown library for generating the Markdown output.
protected PegDownProcessor pegdown protected PegDownProcessor pegdown
public LiterateMarkdownGenerator(Processor processor) { public LiterateMarkdownGenerator(Processor processor) {
@ -25,6 +29,13 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
pegdown = new PegDownProcessor( pegdown = new PegDownProcessor(
Extensions.TABLES | Extensions.DEFINITIONS) } 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) { protected void parse(Directive directive) {
switch(directive.type) { switch(directive.type) {
case DirectiveType.Org: case DirectiveType.Org:
@ -39,14 +50,23 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
break // do nothing 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(CodeBlock codeBlock) {} // nothing to do
protected void parse(DocText docText) {} // 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) { protected String emit(SourceFile sourceFile) {
StringBuilder sb = new StringBuilder() StringBuilder sb = new StringBuilder()
// Create the HTML file head /// Create the HTML head and begin the body.
sb.append( sb.append(
"""<!DOCTYPE html> """<!DOCTYPE html>
<html> <html>
@ -64,10 +84,10 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
</tr></thead> </tr></thead>
<tbody>""") <tbody>""")
// 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)) } sourceFile.blocks.each { block -> sb.append(emit(block)) }
// Create the HTML file foot /// Create the HTML footer.
sb.append( sb.append(
""" </tbody> """ </tbody>
</table> </table>
@ -77,39 +97,47 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
return sb.toString() } return sb.toString() }
/** @api Emit a *Block*. */
protected String emit(Block block) { protected String emit(Block block) {
StringBuilder sb = new StringBuilder() 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 { Directive orgDir = block.docBlock.directives.find {
it.type == DirectiveType.Org } 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<tr id='${orgDir.value}'>") } if (orgDir) { sb.append("\n<tr id='${orgDir.value}'>") }
else { sb.append("<tr>") } else { sb.append("<tr>") }
// Create the `td` for the documentation. /// Create the `td` for the documentation.
sb.append('\n<td class="docs">') sb.append('\n<td class="docs">')
sb.append(emit(block.docBlock)) sb.append(emit(block.docBlock))
sb.append('</td>') sb.append('</td>')
// Create the `td` for the `CodeBlock` /// Create the `td` for the `CodeBlock`
sb.append('\n<td class="code">') sb.append('\n<td class="code">')
sb.append(emit(block.codeBlock)) sb.append(emit(block.codeBlock))
sb.append('</td>') sb.append('</td>')
// Close the table row. /// Close the table row.
sb.append('</tr>') } sb.append('</tr>') }
/** @api Emit a *DocBlock*. */
protected String emit(DocBlock docBlock) { protected String emit(DocBlock docBlock) {
// Create a queue for the doc block elements, we are going to /// Create a queue for the doc block elements, we are going to
// sort them by type and line number /// sort them by type and line number
List emitQueue 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 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 -> emitQueue = docBlock.directives.collect { directive ->
def queueItem = [lineNumber: directive.lineNumber, value: directive] def queueItem = [lineNumber: directive.lineNumber, value: directive]
switch(directive.type) { switch(directive.type) {
@ -121,90 +149,98 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
return queueItem } return queueItem }
// Add all the doc text blocks /// Add all the doc text blocks.
emitQueue.addAll(docBlock.docTexts.collect { docText -> emitQueue.addAll(docBlock.docTexts.collect { docText ->
[lineNumber: docText.lineNumber, priority: 50, value: 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( emitQueue.sort(
{i1, i2 -> i1.priority != i2.priority ? {i1, i2 -> i1.priority != i2.priority ?
i1.priority - i2.priority : i1.priority - i2.priority :
i1.lineNumber - i2.lineNumber} as Comparator) i1.lineNumber - i2.lineNumber} as Comparator)
// Finally, we want to treat the whole block as one markdown chunk, so /** 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 * we will concatenate the values in the emit queue and then send the
// the whole block once * whole block at once to the markdown processor. */
sb = new StringBuilder() sb = new StringBuilder()
emitQueue.each { queueItem -> sb.append(emit(queueItem.value)) } emitQueue.each { queueItem -> sb.append(emit(queueItem.value)) }
return processMarkdown(sb.toString()) return processMarkdown(sb.toString())
} }
/** @api Emit a *CodeBlock*. */
protected String emit(CodeBlock codeBlock) { protected String emit(CodeBlock codeBlock) {
def codeLines def codeLines
// Collect the lines into an array. /// Collect the lines into an array.
codeLines = codeBlock.lines.collect { lineNumber, line -> codeLines = codeBlock.lines.collect { lineNumber, line ->
[lineNumber, line] } [lineNumber, line] }
// Sort by line number. /// Sort by line number.
codeLines.sort({ i1, i2 -> i1[0] - i2[0] } as Comparator) codeLines.sort({ i1, i2 -> i1[0] - i2[0] } as Comparator)
codeLines = codeLines.collect { arr -> arr[1] } codeLines = codeLines.collect { arr -> arr[1] }
// write out the lines in a <pre><code> block /// Write out the lines in a <pre><code> block
return "<pre><code>${codeLines.join('')}</code></pre>" } return "<pre><code>${codeLines.join('')}</code></pre>" }
/** @api Emit a *DocText*. */
protected String emit(DocText docText) { return docText.value } protected String emit(DocText docText) { return docText.value }
/** @api Emit a *Directive*. */
protected String emit(Directive directive) { protected String emit(Directive directive) {
switch(directive.type) { switch(directive.type) {
// An `@api` directive is immediately processed and wrapped in a /** An `@api` directive is immediately processed and wrapped in a
// div (we need to process this now because Markdown does not * div (we need to process this now because Markdown does not
// process input inside HTML elements). * process input inside HTML elements). */
case DirectiveType.Api: case DirectiveType.Api:
return "<div class='api'>" + return "<div class='api'>" +
processMarkdown(directive.value) + "</div>\n" processMarkdown(directive.value) + "</div>\n"
// An `@author` directive is turned into a definition list. /// `@author` directive is turned into a definition list.
case DirectiveType.Author: case DirectiveType.Author:
return "Author\n: ${directive.value}\n" return "Author\n: ${directive.value}\n"
case DirectiveType.Copyright: case DirectiveType.Copyright:
return "\n&copy; ${directive.value}\n" return "\n&copy; ${directive.value}\n"
// An `@example` directive is returned as is /// An `@example` directive is returned as is.
case DirectiveType.Example: case DirectiveType.Example:
return directive.value return directive.value
// An `@org` directive is ignored. /// An `@org` directive is ignored here. We already emitted the id
case DirectiveType.Org: return "" } /// 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) { protected String processMarkdown(String markdown) {
// convert to HTML from Markdown
/// Convert to HTML from Markdown
String html = pegdown.markdownToHtml(markdown) String html = pegdown.markdownToHtml(markdown)
// replace internal `jlp://` links with actual links based on`@org` /// Replace internal `jlp://` links with actual links based on`@org`
// references /// references.
html = html.replaceAll(/jlp:\/\/([^\s"]+)/) { wholeMatch, linkId -> 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] def link = processor.linkAnchors[linkId]
String newLink String newLink
if (!link) { if (!link) {
// We do not have any reference to this id. // We do not have any reference to this id.
/* TODO: log error */ /* 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) { 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 { else {
TargetDoc thisDoc = processor.currentDoc TargetDoc thisDoc = processor.currentDoc
TargetDoc linkDoc = processor.docs[link.sourceDocId] TargetDoc linkDoc = processor.docs[link.sourceDocId]
@ -213,13 +249,12 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
thisDoc.sourceFile.parentFile, thisDoc.sourceFile.parentFile,
linkDoc.sourceFile) linkDoc.sourceFile)
// The target document may not be in the same directory /** The target document may not be in the same directory
// as us, backtrack to the (relative) top of our directory * as us, backtrack to the (relative) top of our directory
// structure. * structure. */
newLink = pathToLinkedDoc + ".html#${linkId}" } newLink = 'href="' + pathToLinkedDoc + ".html#${linkId}\"" }
return newLink } return newLink }
return html; return html; }
}
} }

View File

@ -1,7 +1,14 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp package com.jdblabs.jlp
import com.jdblabs.jlp.ast.* import com.jdblabs.jlp.ast.*
/**
* @org jlp.jdb-labs.com/MarkdownParser
*/
public class MarkdownParser implements JLPParser { public class MarkdownParser implements JLPParser {
public SourceFile parse(String input) { public SourceFile parse(String input) {

View File

@ -6,6 +6,8 @@ package com.jdblabs.jlp
import org.parboiled.BaseParser import org.parboiled.BaseParser
import org.parboiled.Parboiled 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. * Processor processes one batch of input files to create a set of output files.
@ -49,6 +51,8 @@ public class Processor {
protected Map<String, JLPParser> parsers = [:] protected Map<String, JLPParser> parsers = [:]
protected Map<String, JLPBaseGenerator> generators = [:] protected Map<String, JLPBaseGenerator> generators = [:]
private Logger log = LoggerFactory.getLogger(getClass())
/// ### Public Methods. /// ### Public Methods.
/// @org jlp.jdb-labs.com/Processor/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 /// the parser for that file type and parse the file into an abstract
/// syntax tree (AST). /// syntax tree (AST).
processDocs { processDocs {
log.trace("Parsing '{}'.", currentDocId)
def parser = getParser(sourceTypeForFile(currentDoc.sourceFile)) def parser = getParser(sourceTypeForFile(currentDoc.sourceFile))
// TODO: error detection // TODO: error detection
currentDoc.sourceAST = parser.parse(currentDoc.sourceFile.text) } currentDoc.sourceAST = parser.parse(currentDoc.sourceFile.text) }
/// * Run our generator parse phase (see /// * Run our generator parse phase (see
/// jlp://com.jdb-labs.jlp.JLPBaseGenerator/phases for an explanation /// [`JLPBaseGenerator`](jlp://com.jdb-labs.jlp.JLPBaseGenerator/phases)
/// of the generator phases). /// for an explanation of the generator phases).
processDocs { processDocs {
log.trace("Second-pass parsing for '{}'.", currentDocId)
def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile)) def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile))
// TODO: error detection // TODO: error detection
generator.parse(currentDoc.sourceAST) } generator.parse(currentDoc.sourceAST) }
@ -112,6 +118,7 @@ public class Processor {
/// * Second pass by the generators, the emit phase. /// * Second pass by the generators, the emit phase.
processDocs { processDocs {
log.trace("Emitting documentation for '{}'.", currentDocId)
def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile)) def generator = getGenerator(sourceTypeForFile(currentDoc.sourceFile))
currentDoc.output = generator.emit(currentDoc.sourceAST) } currentDoc.output = generator.emit(currentDoc.sourceAST) }
@ -125,6 +132,9 @@ public class Processor {
File outputFile = new File(outputRoot, relativePath + ".html") File outputFile = new File(outputRoot, relativePath + ".html")
File outputDir = outputFile.parentFile File outputDir = outputFile.parentFile
log.trace("Saving output for '{}' to '{}'",
currentDocId, outputFile)
/// Create the directory for this file if it does not exist. /// Create the directory for this file if it does not exist.
if (!outputDir.exists()) { outputDir.mkdirs() } if (!outputDir.exists()) { outputDir.mkdirs() }

View File

@ -1,10 +1,22 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp package com.jdblabs.jlp
import com.jdblabs.jlp.ast.SourceFile 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 { public class TargetDoc {
/// The result of parsing the input file.
public SourceFile sourceAST public SourceFile sourceAST
/// The original source file.
public File sourceFile public File sourceFile
public String output public String output
} }

View File

@ -1,7 +1,16 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp.ast 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 { public class ASTNode {
/// This is the line number the element began on.
protected int lineNumber protected int lineNumber
public ASTNode(int lineNum) { this.lineNumber = lineNum } public ASTNode(int lineNum) { this.lineNumber = lineNum }

View File

@ -1,5 +1,13 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp.ast package com.jdblabs.jlp.ast
/**
* ASTNode for *Block*s.
* @org jlp.jdb-labs.com/ast/Block
*/
public class Block extends ASTNode { public class Block extends ASTNode {
public final DocBlock docBlock public final DocBlock docBlock
public final CodeBlock codeBlock public final CodeBlock codeBlock

View File

@ -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 package com.jdblabs.jlp.ast
import java.util.Map import java.util.Map
/**
* @api ASTNode for *CodeBlock*s.
* @org jlp.jdb-labs.com/ast/CodeBlock
*/
public class CodeBlock extends ASTNode { public class CodeBlock extends ASTNode {
public Map<Integer, String> lines = [:] public Map<Integer, String> lines = [:]

View File

@ -1,7 +1,47 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp.ast 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 { 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 { public static enum DirectiveType {
Api, Author, Copyright, Example, Org; Api, Author, Copyright, Example, Org;

View File

@ -1,8 +1,16 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp.ast package com.jdblabs.jlp.ast
import java.util.ArrayList import java.util.ArrayList
import java.util.List import java.util.List
/**
* @api ASTNode for *DocBlock*s.
* @org jlp.jdb-labs.com/ast/DocBlock
*/
public class DocBlock extends ASTNode { public class DocBlock extends ASTNode {
public List<Directive> directives = new ArrayList<Directive>() public List<Directive> directives = new ArrayList<Directive>()

View File

@ -1,5 +1,13 @@
/**
* @author Jonathan Bernard (jdb@jdb-labs.com)
* @copyright JDB Labs 2010-2011
*/
package com.jdblabs.jlp.ast package com.jdblabs.jlp.ast
/**
* @api ASTNode for *DocText*s.
* @org jlp.jdb-labs.com/ast/DocText
*/
public class DocText extends ASTNode { public class DocText extends ASTNode {
public String value public String value

View File

@ -6,11 +6,24 @@ package com.jdblabs.jlp.ast
/** /**
* The top-level AST element. This represents a source file. * 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 { 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<ASTNode> blocks = [] public List<ASTNode> 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 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 public String id
} }