README
Snippin lets you re-use snippets from text files in other contexts.
Like say you're writing some documentation for your code and you want to show some examples of how it behaves. You figure you'll grab the examples from your test suite, because 1. they're already written and 2. they're guaranteed to work --it's so annoying to find bugs in sample code! But there's some boring boilerplate at the top of your test file, and there are some tests that go into too much detail to serve as good examples, so you don't want to just include the whole file in your documentation. With Snippin, you can identify just the specific parts you're interested in:
// test.js
const tap = require('tap')
// #snip "example"
tap.equals(1 + 1, 2)
// #snip
Now you can load them with Snippin and use them however you need:
const snippin = new Snippin()
tap.same(snippin.getSnippetsSync('test.js'), {
'example': 'tap.equals(1 + 1, 2)\n'
})
Writing snippets
Start a snippet by writing #snip <snippet name>
by itself on a line.
The snippet name must be a valid JSON string, e.g.
#snip "example snippet"
End a snippet by writing just "#snip" by itself on a line:
#snip
You're allowed as much whitespace as you need before "#snip", and you can also have comment markers on the same line. For example,
// prefixed.js
// #snip "inside a comment"
//
// That "// " at the start of these lines
// won't be part of the snippet
// #snip
tap.same(snippin.getSnippetsSync('prefixed.js'), {
'inside a comment': `
That "// " at the start of these lines
won't be part of the snippet
` })
Specifically, here are the regex components that match whitespace and comment markers:
- prefix:
/^\s*(?://|\*|/\*)?\s*/
- suffix:
/\s*$/
Snippets can be nested. For example,
// nested.txt
#snip "outer"
Outer snippet
#snip "inner"
Inner snippet
#snip
Still outer snippet
#snip
tap.same(snippin.getSnippetsSync('nested.txt'), {
'outer': 'Outer snippet\nInner snippet\nStill outer snippet\n',
'inner': 'Inner snippet\n'
})
You can escape "#snip" directives by prefixing them with a "\". And of course if you need a literal "\#snip" you can write "\\#snip":
// escaped.txt
#snip "snippet"
\#snip "literal #snip"
\#snip
// \\#snip "literal backslash"
#snip
tap.same(snippin.getSnippetsSync('escaped.txt'), {
'snippet': '#snip "literal #snip"\n#snip\n// \\#snip "literal backslash"\n'
})
API
new Snippin([options, [streamOptions]])
refDir
string The directory relative to which snippets are loaded. Default:'.'
.errorLogger
Function Function to use to log warnings; it will be called with a single argument: an error message string. Default:console.warn
.
snippin.getSnippets(file)
file
string The name of the file for which to get snippets.- Returns Promise Resolves to an object containing the snippets found in
file
; keys are the snippet names, values are their contents.Gets snippets from the given file. Throws if
file
doesn't exist.
snippin.getSnippetsSync(file)
file
string The name of the file for which to get snippets.- Returns Object The snippets found in
file
; keys are the snippet names, values are their contents.Gets snippets from the given file synchronously. Throws Error if
file
doesn't exist.
snippin.setSnippets(file, snippets)
file
string The name of the file for which to set snippets. This can be a path to a real file, or just a name.snippets
Object The snippets to set; properties are snippet names, and their value is the snippet contents.Snippets set this way take precedence over snippets loaded from files. This function operates on the entire set of snippets for a file; it will replace the set of snippets for the given file if it's already been loaded, or prevent it from being loaded if it hasn't yet.
Including snippets
Once you've loaded your snippets, you can include them into your text by using any of the popular templating libraries, or template literals, or whatever.
Snippin also ships with a stream transform that can process "#pin" directives
and render them as the named snippet. Include a snippet by writing #pin file snippet-name
by itself on a line. Both the file and snippet name must be
valid JSON strings.
Continuing the first example above, we can do
const doc = `We all know that one and one make two:
#pin "test.js" "example"`
let output = ''
const pinTx = new PinTx()
pinTx.on('data', chunk => { output += chunk })
pinTx.on('end', () => t.equals(output,
'We all know that one and one make two:\ntap.equals(1 + 1, 2)\n'))
pinTx.end(doc)
new PinTx([options, [streamOptions]])
options
Object
refDir
string The directory relative to which snippets are loaded. Default:'.'
.errorLogger
Function Function to use to log warnings; it will be called with a single argument: an error message string. Default:console.warn
.streamOptions
Object Passed as-is to stream.Transform's constructor. Default:{}
.This transform replaces each
#pin
directive with the snippet it references.If a
#pin
directive is malformed or references a snippet which can't be loaded, a warning is logged, and the directive is output as-is.
Command line
There's a command line interface to the stream transform.
Usage: snippin [-d REFDIR] [-o OUTFILE] [--] [INFILE]
Print standard input (or INFILE) to standard output (or OUTFILE)
with #pins expanded relative to the current working directory
(or INFILE's directory, or REFDIR).
Options:
-d, --refdir=DIRECTORY load snippets relative to DIRECTORY
-o, --outfile=FILE write output to FILE
-h, --help show this usage message
Contributing
This project is deliberately left imperfect to encourage you to participate in its development. If you make a Pull Request that
- explains and solves a problem,
- follows standard style, and
- maintains 100% test coverage
it will be merged: this project follows the C4 process.
To make sure your commits follow the style guide and pass all tests, you can add
./.pre-commit
to your git pre-commit hook.