**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:

~~~javascript
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

~~~javascript
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

~~~javascript
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

~~~javascript
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

~~~javascript
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](
>    https://nodejs.org/api/stream.html#stream_new_stream_transform_options)'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](https://standardjs.com/), and
 - maintains 100% test coverage

it _will_ be merged: this project follows the
[C4 process](https://rfc.zeromq.org/spec:42/C4/).

To make sure your commits follow the style guide and pass all tests, you can add

    ./.pre-commit

to your git pre-commit hook.
