Skip to content

Commit

Permalink
Refactored the overall process, fixed #0005: link behaviour.
Browse files Browse the repository at this point in the history
* Refactored the overall process flow. Instead of ``JLPMain`` handling the
  process, it now reads the command line options and defers to ``Processor`` to
  handle the actual process. The ``Processor`` instance is responsible for
  processing one batch of input files and holds all the state that is common to
  this process.
* ``JLPBaseGenerator`` and generators based on it are now only responsible for
  handling one file, generating output from a source AST. As a consequence
  state that is common to the overall process is no longer stored in the
  generator but is stored on the ``Processor`` instance, which is exposed to the
  generators.
* Generators can now be instantiated directly (instead of having just a public
  static method) and are no longer one-time use. Now the life of a generator is
  expected to be the same as the life of the ``Processor``.
* Fixed inter-doc link behaviour.
* Created some data classes to replace the ad-hoc maps used to store state in
  the generator (now in the ``Processor``)
  • Loading branch information
jdbernard committed Sep 9, 2011
1 parent e6d515f commit d31d17d
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 109 deletions.
25 changes: 25 additions & 0 deletions doc/issues/0005ts3.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Internal links are not smart enough about cross-file linking.
=============================================================

There are two main problems with internal linking as it works now:

1. The links are resolved relative to the directory of the file being processed
when they should be resolved relative to the root output directory.

For example, consider ``@org id1`` defined in ``dir/file1.src``, then
referenced in ``dir/file2.src`` in this manner: ``[link](jlp://id1``. The
url written in ``dir/file2.html`` is ``dir/file1.html#id1``. However, when
the page is viewed, this url is interpreted relative to the directory of the
current page. So if the docs live at ``file:///docs`` then the url will
resolve to ``file:///docs/dir/dir/file1.src#id1`` instead of
``file:///docs/dir/file1.html#id1``.

2. The links do substitute the ``html`` suffix to the file names in place of the
files' suffixes. In the above example note that the actual urls end in
``.src`` like the original input files, but the expected url ends in
``.html``.

========= ==========
Created: 2011-09-08
Resolved: 2011-09-09
========= ==========
4 changes: 2 additions & 2 deletions project.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu, 08 Sep 2011 12:27:26 -0500
#Fri, 09 Sep 2011 14:26:03 -0500
name=jlp
version=0.2
version=0.3
build.number=1
lib.local=true
release.dir=release
30 changes: 6 additions & 24 deletions src/main/com/jdblabs/jlp/JLPBaseGenerator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,18 @@ import java.util.Map

public abstract class JLPBaseGenerator {

protected Map docState
protected Processor processor

protected JLPBaseGenerator() {
docState = [orgs: [:], // stores `@org` references
codeTrees: [:], // stores code ASTs for
currentDocId: false ] } // store the current docid
protected JLPBaseGenerator(Processor processor) {
this.processor = processor }

protected Map<String, String> generate(Map<String, SourceFile> sources) {
Map result = [:]
protected String generate(SourceFile source) {

// run the parse phase
sources.each { sourceId, sourceAST ->

// set up the current generator state for this source
docState.currentDocId = sourceId
docState.codeTrees[sourceId] = sourceAST.codeAST

parse(sourceAST) }
parse(source)

// run the emit phase
sources.each { sourceId, sourceAST ->

// set up the current generator state for this source
docState.currentDocId = sourceId

// generate the doc for this source
result[sourceId] = emit(sourceAST) }

// return our results
return result }
return emit(source) }

protected void parse(SourceFile sourceFile) {
sourceFile.blocks.each { block -> parse(block) } }
Expand Down
83 changes: 17 additions & 66 deletions src/main/com/jdblabs/jlp/JLPMain.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,17 @@ import org.parboiled.parserunners.ReportingParseRunner

public class JLPMain {

private JLPPegParser parser

public static void main(String[] args) {

JLPMain inst = new JLPMain()

// create command-line parser
CliBuilder cli = new CliBuilder(
usage: 'jlp [options] <src-file> <src-file> ...')

// define options
cli.h('Print this help information.', longOpt: 'help', required: false)
cli.o("Output directory (defaults to 'jlp-docs').",
longOpt: 'output-dir', required: false)
longOpt: 'output-dir', args: 1, argName: "output-dir",
required: false)
cli._(longOpt: 'relative-path-root', args: 1, required: false,
'Resolve all relative paths against this root.')

Expand Down Expand Up @@ -56,67 +53,21 @@ public class JLPMain {

// get files passed in
def filenames = opts.getArgs()

// parse input
Map parsedFiles = filenames.inject([:]) { acc, filename ->

// create the File object
File file = new File(filename)

// if this is a relative path, resolve it against our root path
if (!file.isAbsolute()) { file = new File(pathRoot, filename) }

// parse the file, store the result
acc[filename] = inst.parse(file)
return acc }

// generate output
Map htmlDocs = LiterateMarkdownGenerator.generateDocuments(parsedFiles)

// write output files
htmlDocs.each { filename, html ->

// split the path into parts
def fileParts = filename.split(/[\.\/]/)

// default the subdirectory to the output directory
File subDir = outputDir

// if the input file was in a subdirectory, we want to mirror that
// structure here.
if (fileParts.length > 2) {

// find the relative subdirectory of this file
subDir = new File(outputDir, fileParts[0..-3].join('/'))

// create that directory if needed
if (!subDir.exists()) subDir.mkdirs()
}

// recreate the output filename
def outputFilename = fileParts[-2] + ".html"

// write the HTML to the file
new File(subDir, outputFilename).withWriter { fileOut ->

// write the CSS if it is not present
File cssFile = new File(subDir, "jlp.css")
if (!cssFile.exists()) cssFile.withWriter { cssOut ->
cssOut.println css }


// write the file
fileOut.println html } }
}

public JLPMain() {
parser = Parboiled.createParser(JLPPegParser.class)
def inputFiles = (filenames.collect { filename ->
// create a File object
File file = new File(filename)

// if this is a relative path, resolve it against our path root
if (!file.isAbsolute()) { file = new File(pathRoot, filename) }

// warn the user about files that do not exist
if (!file.exists()) {
System.err.println
"'${file.canonicalPath}' does not exist: ignored." }

return file }).findAll { it.exists() }

Processor.process(outputDir, css, inputFiles)
}

public SourceFile parse(File inputFile) {
def parseRunner = new ReportingParseRunner(parser.SourceFile())

// parse the file
return parseRunner.run(inputFile.text).resultValue
}
}
11 changes: 11 additions & 0 deletions src/main/com/jdblabs/jlp/LinkAnchor.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.jdblabs.jlp

import com.jdblabs.jlp.ast.Directive

public class LinkAnchor {

public String id
public Directive directive
public String sourceDocId

}
49 changes: 32 additions & 17 deletions src/main/com/jdblabs/jlp/LiterateMarkdownGenerator.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,21 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {

protected PegDownProcessor pegdown

protected LiterateMarkdownGenerator() {
super()
public LiterateMarkdownGenerator(Processor processor) {
super(processor)

pegdown = new PegDownProcessor(
Extensions.TABLES | Extensions.DEFINITIONS) }

protected static Map<String, String> generateDocuments(
Map<String, SourceFile> sources) {
LiterateMarkdownGenerator inst = new LiterateMarkdownGenerator()
return inst.generate(sources) }

protected void parse(Directive directive) {
switch(directive.type) {
case DirectiveType.Org:
def orgMap = [:]
orgMap.id = directive.value
orgMap.directive = directive
orgMap.sourceDocId = docState.currentDocId
docState.orgs[directive.value] = orgMap
LinkAnchor anchor = new LinkAnchor(
id: directive.value,
directive: directive,
sourceDocId: processor.currentDocId)

processor.linkAnchors[anchor.id] = anchor
break;
default:
break // do nothing
Expand All @@ -48,15 +44,15 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
"""<!DOCTYPE html>
<html>
<head>
<title>${docState.currentDocId}</title>
<title>${processor.currentDocId}</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="jlp.css"/>
</head>
<body>
<div id="container">
<table cellpadding="0" cellspacing="0">
<thead><tr>
<th class="docs"><h1>${docState.currentDocId}</h1></th>
<th class="docs"><h1>${processor.currentDocId}</h1></th>
<th class="code"/>
</tr></thead>
<tbody>""")
Expand Down Expand Up @@ -187,14 +183,33 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
// replace internal `jlp://` links with actual links based on`@org`
// references
html = html.replaceAll(/jlp:\/\/([^\s"]+)/) { wholeMatch, linkId ->
def link = docState.orgs[linkId]

// Get the org data stored 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})" }
else if (docState.currentDocId == link.sourceDocId) {

// This link points to a location in this document.
else if (processor.currentDocId == link.sourceDocId) {
newLink = "#$linkId" }
else { newLink = "${link.sourceDocId}#${linkId}" }

// The link should point to a different document.
else {
thisDoc = processor.currentDoc
linkDoc = processor.docs[link.sourceDocId]

pathToLinkedDoc = processor.getRelativePath(
thisDoc.sourceFile.parentFile,
thatDoc.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}" }

return newLink }

Expand Down
Loading

0 comments on commit d31d17d

Please sign in to comment.