0.1.0 • Published 5 years ago

marcco v0.1.0

Weekly downloads
1
License
MPL-2.0
Repository
gitlab
Last release
5 years ago

Marcco turns your source code into markdown: comments become the main body of the text, and code becomes code blocks inside that text. This lets you generate READMEs from your source code, or program in a more literate style. Marcco is inspired by Docco.

Behaviour

Marcco is language-agnostic; it doesn't try to parse or interpret the source code. It just reads it line by line and decides, based on whether the commentMarker regex matches, whether the line is prose or code. Comment markers are removed from blocks of prose, whereas indentation is added to blocks of code (or code fences, depending on the codePrefix option).

So

1 + 1 == 2 // This is some code
// Now this is some prose

renders as

    1 + 1 == 2 // This is some code

Now this is some prose

Source code is handled line-by-line

Marcco isn't smart enough to handle comments that "open" and "close", because it only looks at a single line to decide whether it's a comment or some code. For example,

1 + 1 == 2 // This is some code
/*
 * This is still code
 */
// Now this is some prose

becomes

    1 + 1 == 2 // This is some code
    /*
     * This is still code
     */

Now this is some prose

You can use this to your advantage when you want to control which comments become paragraphs of prose vs which should remain in the source code:

while (true) {
    // do something
}

would render as the confusing

    while (true) {

do something

    }

so instead you could write

// This is easier to read
while (true) {
    /* do something */
}

to get

This is easier to read

    while (true) {
        /* do something */
    }

Blocks

Marcco groups consecutive comment lines into a single prose block, and consecutive lines of code into a single code block. When switching from one type of block to another, it adds a newline:

Code 1
// Prose A
Code 2
Code 3
// Prose B
// Prose C

becomes

    Code 1

Prose A

    Code 2
    Code 3

Prose B
Prose C

Empty or blank lines don't cause a block to end, even if they're in the middle of a block of prose. They're just output as-is:

// This is prose.

// This is still the same prose block; notice the lack of extra newline.
if (a > max) {
    max = a

    continue // still same code block
}

gets you this markdown:

This is prose.

This is still the same prose block; notice the lack of extra newline.

    if (a > max) {
        max = a

        continue // still same code block
    }

Indentation

Marcco preserves the indentation you use in your prose blocks by removing only the prefix common to all non-blank lines in the block. So for example the following are three different ways to write the same prose block:

// Things to do today
//
//   - ship
//   - ship

// Oh and don't forget to ship!

or

        // Things to do today
        //
        //   - ship
        //   - ship

        // Oh and don't forget to ship!

or

//     Things to do today
//
//       - ship
//       - ship

//     Oh and don't forget to ship!

all render as

Things to do today

  - ship
  - ship

Oh and don't forget to ship!

Custom comment markers

You can set the commentMarker option to match the language you're using. For instance, you could use /^\s*(\/\/|#)\s*/ for PHP:

// PHP supports
/* multiple */
# comment styles

to get

PHP supports

    /* multiple */

comment styles

or /^\s*--\s*/ for SQL:

-- How many users registered in the last 30 days?
SELECT COUNT(*) FROM users
WHERE registered_at > CURRENT_DATE - INTERVAL '30 days';

which becomes

How many users registered in the last 30 days?

    SELECT COUNT(*) FROM users
    WHERE registered_at > CURRENT_DATE - INTERVAL '30 days';

Custom code prefixes

To benefit from the syntax highlighting provided by some markdown renderers, you can set the codePrefix option to a code fence with an info string. The above example with codePrefix set to '~~~sql' would render like so:

How many users registered in the last 30 days?

~~~sql
SELECT COUNT(*) FROM users
WHERE registered_at > CURRENT_DATE - INTERVAL '30 days';
~~~

The rule is this: if codePrefix looks like a code fence, optionally with an info string, then Marcco will fence your code with it. Otherwise, it will prefix every line of code with codePrefix. So you could use e.g. '\t' if you would like your code prefixed with tabs rather than the default four spaces. It doesn't make a difference to Markdown. Or you can put any string in there, e.g. '> ':

How many users registered in the last 30 days?

> SELECT COUNT(*) FROM users
> WHERE registered_at > CURRENT_DATE - INTERVAL '30 days';

API

Marcco gives you two ways to turn your code into Markdown: a function that works with strings, and a transform for working with streams.

code2doc(sourceCode, options = {})

  • sourceCode String the source code to convert
  • options Object
    • commentMarker RegExp lines that match this regex count as comments. Default: /^\s*\/\/\s*/
    • codePrefix String the indentation or code fence to use for sections of code. Default: ' '
  • Returns: String

Returns the Markdown representation of the given source code.

new DocTx(options = {}, streamOptions = {})

  • options Object same as the options passed to code2doc
  • streamOptions Object options to pass to the constructor of the parent class, stream.Transform

Treats its input as source code, and transforms it to markdown.

Example

const { code2doc, DocTx } = require('.')

tap.test('Example: convert a shell script to Markdown', t => {
  const script =
    '# Take a screenshot of a region of your screen\n' +
    'maim -s ~/screenshots/$(date -Is | sed "s/:/-/g").png\n'
  const markdown =
    'Take a screenshot of a region of your screen\n' +
    '\n    maim -s ~/screenshots/$(date -Is | sed "s/:/-/g").png\n'

  /* Convert a string by calling code2doc */
  t.equals(code2doc(script, { commentMarker: /^\s*#\s*/ }), markdown)

  /* Convert a stream by piping it to DocTx */
  const tx = new DocTx({ commentMarker: /^\s*#\s*/ })
  let md = ''
  tx.on('data', chunk => { md += chunk })
  tx.on('end', () => {
    t.equals(md, markdown)
    t.end()
  })
  tx.end(script)
})

Contributing

You're welcome to contribute to this project following the C4 process.

All patches must follow standard style and have 100% test coverage. You can make sure of this by adding

./.pre-commit

to your git pre-commit hook.