markdown_conrefs
v1.2.19
Published
A system for including content references (conrefs) into Markdown files
Downloads
29
Maintainers
Readme
Introduction
I wrote technical documentation in my formative years using the DITA standard. Nowadays, I write documentation in Markdown, but one of the things I really miss is the ability to do content references—or conrefs—from one file to another.
The idea is simple: if you have a term, phrase, or block that you're reusing throughout the documentation, it's best to just define it once, tag it with a unique ID, then refer to that ID whenever you want to pull the content in. Simple, and much more efficient then search and replace.
I wrote a module for this functionality in Node.js, and it would be my dream of dreams if other Markdown packages for other languages implement similar functionality.
Installation
From npm, just do
npm install markdown_conrefs
Syntax
The syntax follows the superior Maruku metadata format for Markdown. IDs are either attached at the block level, or inline. There are a few ways in Maruku to define attributes; this conref system supports all of them (with #blah
, with id="blah"
, with id='blah'
, or just plain id=blah
).
To attach to the block level, just create metadata for the last line of the block, with an id element, like so:
* Item 1
* Item 2
* Item 3
{: #aNiceList}
Here's a great, and final, paragraph.
{: id="myPara"}
To attach conrefs inline, you'll need to wrap the content with brackets ([ ]
), and then continue with the same attribute format, like so:
[I don't want to have to rewrite this.]{: .class1 id=aTruth .class2}
[**A RELIEF**]{: key=value id='expression' foo=bar}
[Genesis]{: #newProj}
Note that this format differs slightly from Maruku. Maruku metadata only applies to Markdown inline elements, like bold, or italic. This system allows you to conref even plain-text. In fact, namp, my Markdown processor written in Node.js, also supports this notation.
Remember: the syntax to define this metadata is curly brace-colon-space
! Why is that important? Well...
In order to reference the conref, you'll just use this syntax wherever you want to do an insertion: {:id}
. That's curly brace-colon-id-curly brace
--nothing else! It's irrelevant if you defined your id with #
, id=""
, or id=''
--all you want to do is use the id name. To use the examples above, that'd be:
{:aNiceList}
{:myPara}
{:aTruth}
{:expression}
{:newProj}
Pulling additional attributes
Any other attributes you've defined--class names, language identifiers for fenced code blocks, e.t.c.--get pulled into the resulting document as well, but the ID is stripped.
Why? Consider the following text:
I am working on [Project X]{: #product .secret}. I love being on {:product}. It's more rewarding to be a part of {:product}.
If IDs were kept, the block would resolve as:
I am working on [Project X]{: #product .secret}. I love being on [Project X]{: #product .secret}. It's more rewarding to be a part of [Project X]{: #product .secret}.
That's now three elements in the same document with the same ID, product
. Instead, the resolve happens like this:
I am working on [Project X]{: #product .secret}. I love being on [Project X]{: .secret}. It's more rewarding to be a part of [Project X]{: .secret}.
Using
First, add require('markdown_conrefs')
to your code. This module only has the following functions:
init(source [, options])
This must always be called first! This creates the id-to-content hash. The parameters are:
source
is a string of a directory or file name, or, an array of strings for directories and filenames.source
can represent the file you want to parse, the files you want to parse, or the highest level directory you want to start searching content references for--this module will recursively find all conref IDs in files to keep track of them.options
is a JSON object to path that defines any options for the parser. These are the possible properties:supportsAttributes
: set this totrue
if you know that your Markdown parser supports the Maruku syntax of[ ]
/{: }
to pass attributes into text. When the conrefs are replaced, these attributes will be preserved; otherwise, they are wiped. Default isfalse
.type
: the extension of the files you want to keep. If omitted, all files insource
are parsed. You can either include the starting dot (.md
) or omit it (md
).exclusions
: an array of strings, indicating any files or directories you don't want to process whensource
is a directory.blockPrefixChar
: a string, indicating the starting line character in your files. This is used when parsing block conrefs. For example, you may be parsing Javascript comments, in which caseblockPrefixChar: "*"
.blockPrefixCharOptional
: a boolean which, iftrue
, means that theblockPrefixChar
could, or could not, be there. In other (RegExp) words:options.blockPrefixCharOptional ? "?"
.
This function has no return value, and is synchronous/blocking.
replaceConref(data)
When you're ready to apply the conrefs, use this function. This function takes one parameter:
data
, the string containing the markdown text--it's usually the file you're reading.
This function returns data
with the conrefs replaced; if no conref IDs are found, just plain old data
is returned
getData()
Returns the entire hash list.
Check out the test/ directory for an example; just run cd test && node test/test.js
from this directory.
Keep in mind that this module only replaces the references; you'll still need to run a Markdown parser in order to actually generate HTML.
Error Checking
The module automatically halts if:
- You have more than one ID with the same reference
- You refer to an ID that doesn't exist
Benchmarks
I tested this module by creating a single file with 10,000 conref IDs, and 10,000 files with a single conref ID. Presumably, lookups would cost the same amount of time, so I made a single call to markdown_conref.init()
on each set of files.
On average, the single file took 7ms to complete; the multiple files took 450ms. In other words, they're both damned fast, despite my mediocre programming. For the technical writer, a single file might be easier to manage.