Added file and inline link anchors.

* Auto-link for documents. If there is an `org` directive in the first doc
  block of the file then it is used as the file link definition. If there is no
  such `org` directive then on is created automatically. This resolves issue
  #0008. There is a new LinkAnchor type for these links: `LinkType.FileLink`
* Multiple `org` directives per DocBlock are now allowed. There is a new
  LinkAnchor link type for these link: `LinkType.InlineLink`.
* Refactored `LinkType.OrgLink` to be `LinkType.BlockLink`.
* Tweaked CSS
* Refactored `LiterateMarkdownGenerator.emit(DocBlock)` for simplicity.
This commit is contained in:
Jonathan Bernard 2012-01-06 14:41:01 -06:00
parent 051cd54dcd
commit a86b55726f
10 changed files with 122 additions and 77 deletions

View File

@ -1,16 +0,0 @@
Fix delimited doc block behavior.
=================================
Delimited doc blocks require that the start token be the first non-space token
on the line it is on and that the end token be on it's own line. This is not in
line with the general nature of delimited comment blocks, which do not place
any restrictions on what comes before the start delimiter or after the end
delimiter.
----
========= ===================
Created : 2011-09-07
Resolved: 2011-12-25T23:26:07
========= ===================

View File

@ -7,9 +7,10 @@ line with the general nature of delimited comment blocks, which do not place
any restrictions on what comes before the start delimiter or after the end any restrictions on what comes before the start delimiter or after the end
delimiter. delimiter.
---- ----
========= ========== ========= ===================
Created: 2011-09-07 Created : 2011-09-07
Resolved: YYYY-MM-DD Resolved: 2011-12-25T23:26:07
========= ========== ========= ===================

18
doc/issues/0010fs5.rst Normal file
View File

@ -0,0 +1,18 @@
Modify `org` behavior to include simple anchors.
================================================
Currently JLP supports at most one `org` directive per block which identifies the block.
It would be useful to support multiple `org` directives within a block, particularly
when there is a large block that may have many interesting internal targets. Maybe the
`org` directive should be handled differently when it is used multiple times within a
block. This would be discovered in the generator parse phase and we could change the
LinkAnchor type at that time. During the parse phase we emit the new type of anchors
as `<a id="link-name"/>` into the document. We would also change our search for block
ids to inly look for the single-occurance type of `orgs`.
----
========= ===================
Created : 2012-01-05T11:40:35
Resolved: 2012-01-06T14:32:46
========= ===================

View File

@ -1,7 +1,7 @@
#Wed, 04 Jan 2012 18:05:01 -0600 #Fri, 06 Jan 2012 12:20:57 -0600
name=jlp name=jlp
version=1.4 version=1.5
build.number=11 build.number=6
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

@ -15,6 +15,12 @@ h1, h2, h3, h4, h5, h6 { margin: 0 0 15px 0; }
h1 { margin-top: 40px; } h1 { margin-top: 40px; }
hr {
background-color: black;
color: black;
border: none;
height: 1px; }
dt { font-weight: bold; } dt { font-weight: bold; }
ul { ul {
@ -36,17 +42,17 @@ td.docs, th.docs {
vertical-align: top; vertical-align: top;
text-align: left; } text-align: left; }
.docs pre { .docs tt, .docs code, .docs pre {
background: #f8f8ff;
border: 1px solid #dedede;
margin: 15px 0 15px;
padding-left: 15px; }
.docs tt, .docs code {
background: #f8f8ff; background: #f8f8ff;
border: 1px solid #dedede; border: 1px solid #dedede;
padding: 0 0.2em; } padding: 0 0.2em; }
.docs pre {
margin: 15px 0 15px;
padding-left: 15px; }
.docs pre > code { border: none; }
.docs table { .docs table {
border: thin solid #dedede; border: thin solid #dedede;
margin-left: 60px; } margin-left: 60px; }

View File

@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory
*/ */
public class JLPMain { public class JLPMain {
public static final String VERSION = "1.4" public static final String VERSION = "1.5"
private static Logger log = LoggerFactory.getLogger(JLPMain.class) private static Logger log = LoggerFactory.getLogger(JLPMain.class)

View File

@ -20,12 +20,12 @@ import com.jdblabs.jlp.ast.ASTNode
*/ */
public class LinkAnchor { public class LinkAnchor {
public enum LinkType { OrgLink, FileLink } public enum LinkType { InlineLink, BlockLink, FileLink }
/// The anchor id. This comes from the text after the directive. /// The anchor id. This comes from the text after the directive.
public String id public String id
public LinkType type public LinkType type = LinkType.BlockLink
public ASTNode source public ASTNode source
public String sourceDocId public String sourceDocId

View File

@ -5,6 +5,7 @@
package com.jdblabs.jlp package com.jdblabs.jlp
import com.jdblabs.jlp.ast.* import com.jdblabs.jlp.ast.*
import com.jdblabs.jlp.LinkAnchor.LinkType
import com.jdblabs.jlp.ast.Directive.DirectiveType import com.jdblabs.jlp.ast.Directive.DirectiveType
import org.pegdown.Extensions import org.pegdown.Extensions
@ -38,6 +39,56 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
/** ### Parse phase implementation. ### */ /** ### Parse phase implementation. ### */
// =================================== // ===================================
/** Override the parse phase for [`SourceFile`] nodes. We are interested in
* detecting an `org` directive in the first DocBlock, or automatically
* creating one if it is not defined. The first `org` directive (found or
* created) will create a FileLink type LinkAnchor.
*/
protected void parse(SourceFile sourceFile) {
/// First we look for an `org` directive in the first block.
def firstOrg = sourceFile.blocks[0].docBlock.directives.find {
it.type == DirectiveType.Org }
/// And we create one if there are none.
if (!firstOrg) {
def docBlock = sourceFile.blocks[0].docBlock
firstOrg = new Directive(processor.currentDocId, 'org', 0, docBlock)
docBlock.directives << firstOrg }
/// Now parse the file as usual.
super.parse(sourceFile)
/// And mark the first `org` as a FileLink
processor.linkAnchors[firstOrg.value].type = LinkType.FileLink}
/** Override the parse phase for [`DocBlock`] nodes. We are interested in
* detecting a block that has multilple `org` directives. When there are
* multiple org directives in one block we change the LinkAnchor type from
* block-level links to specific anchors in the text. This allows the
* author to create a link to exact points within the document.
*
* [`DocBlock`]: jlp://jlp.jdb-labs.com/ast/DocBlock
*/
protected void parse(DocBlock docBlock) {
/// First parse the block as usual.
super.parse(docBlock)
/// Look for multiple `org` directives.
def orgDirectives = docBlock.directives.findAll {it.type == DirectiveType.Org }
/// If we have multiple `org` directives in one [`DocBlock`] then we
/// want to change the corresponding [`LinkAnchors`] to type
/// `AnchorType`.
///
/// [`DocBlock`]: jlp://jlp.jdb-labs.com/ast/DocBlock
/// [`LinkAnchors`]: jlp://jlp.jdb-labs.com/LinkAnchor
if (orgDirectives.size() > 1) {
orgDirectives.each { directive ->
/// Get the LinkAnchor for this `org` link.
def linkAnchor = processor.linkAnchors[directive.value]
linkAnchor.type = LinkType.InlineLink }}}
/** Implement the parse phase for [`Directive`] nodes. We are interested /** Implement the parse phase for [`Directive`] nodes. We are interested
* specifically in saving the link anchor information from *org* * specifically in saving the link anchor information from *org*
* directives. * directives.
@ -139,10 +190,12 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
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` (already found in the /// Look for an `@org` directive in the `Block` that is marked as a
/// parse phase).. /// block link (we may have many `orgs` in a block that are not block
Directive orgDir = block.docBlock.directives.find { /// links).
it.type == DirectiveType.Org } Directive orgDir = block.docBlock.directives.find { directive ->
directive.type == DirectiveType.Org &&
processor.linkAnchors[directive.value]?.type == LinkType.BlockLink }
/// Create the `tr` that will hold the `Block`. If we found an `@org` /// Create the `tr` that will hold the `Block`. If we found an `@org`
/// directive we will add the id here. /// directive we will add the id here.
@ -172,42 +225,15 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
/// 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. We are also assigning priorities here that /** We want to treat the whole block as one markdown chunk so we will
* we will use along with the line numbers to sort the elements. Our * concatenate the directives and texts and send the whole block at
* goal is to preserve the order of doc blocks and doc-block-like * once to the markdown processor.
* elements (examples, api documentation, etc.) while pushing orgs */
* and potentially other directives to the top. We used to re-order emitQueue = docBlock.directives + docBlock.docTexts
* authorship and copyright tags but stopped: in literate-style docs emitQueue.sort { it.lineNumber }
* the author should have control over their placement and in api-style
* docs we are chopping the whole block up anyways (and this is not
* that code). Given that the only item being re-ordered is *orgs*,
* which do not print anyways, it may be better to cut this out and
* just emit the blocks in their original order, or sort by line number
* only. */
emitQueue = docBlock.directives.collect { directive ->
def queueItem = [lineNumber: directive.lineNumber, value: directive]
switch(directive.type) {
case DirectiveType.Org: queueItem.priority = 0; break
default: queueItem.priority = 50; break }
return queueItem }
/// 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.
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 send the
* 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)) }
return processMarkdown(sb.toString()) return processMarkdown(sb.toString())
} }
@ -244,7 +270,6 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
processMarkdown(directive.value) + "</div>\n" processMarkdown(directive.value) + "</div>\n"
/// `@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"
@ -258,9 +283,15 @@ public class LiterateMarkdownGenerator extends JLPBaseGenerator {
// TODO: // TODO:
// case DirectiveType.Include: // case DirectiveType.Include:
/// An `@org` directive is ignored here. We already emitted the id /// An `@org` directive may be emitted if the [`LinkAnchor`] is an
/// when we started the block. /// `InlineLink` type.
case DirectiveType.Org: return "" } } ///
/// [`LinkAnchor`]: jlp://jlp.jdb-labs.com/LinkAnchor
case DirectiveType.Org:
def link = processor.linkAnchors[directive.value]
if (link.type == LinkType.InlineLink) {
return "<a id='${directive.value}'></a>\n" }
else { return "" }}}
/** This is a helper method to process a block of text as Markdown. We need /** 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 * to do some additional processing to deal with `jlp://` org links that

View File

@ -4,6 +4,7 @@
*/ */
package com.jdblabs.jlp package com.jdblabs.jlp
import com.jdblabs.jlp.LinkAnchor.LinkType
import com.jdbernard.util.JarUtils import com.jdbernard.util.JarUtils
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import org.parboiled.BaseParser import org.parboiled.BaseParser
@ -213,11 +214,15 @@ public class Processor {
if (!linkAnchor) { if (!linkAnchor) {
// We do not have any reference to this id. // We do not have any reference to this id.
/* TODO: log error */ log.warn("Unable to resolve a jlp link: {}.", link)
return "broken_link(${linkId})" } return "broken_link(${linkId})" }
/// If this is a `FileLink` then we do not need the actual
/// linkId, just the file being linked to.
if (linkAnchor.type == LinkType.FileLink) { linkId = "" }
/// This link points to a location in this document. /// This link points to a location in this document.
else if (targetDoc.sourceDocId == linkAnchor.sourceDocId) { if (targetDoc.sourceDocId == linkAnchor.sourceDocId) {
return "#${linkId}" } return "#${linkId}" }
/// The link should point to a different document. /// The link should point to a different document.