Home

Awesome

Markdown Magic npm-version

✨ Add a little magic to your markdown ✨

About

<img align="right" width="200" height="183" src="https://cloud.githubusercontent.com/assets/532272/21507867/3376e9fe-cc4a-11e6-9350-7ec4f680da36.gif">Markdown magic uses comment blocks in markdown files to automatically sync or transform its contents.

The comments markdown magic uses are hidden in markdown and when viewed as HTML.

This README.md is generated with markdown-magic view the raw file to see how.

Video demoExample Repo

Table of Contents

<!-- ⛔️ MD-MAGIC-EXAMPLE:START TOC collapse=true collapseText="Click to expand" --> <details> <summary>Click to expand</summary> </details> <!-- ⛔️ MD-MAGIC-EXAMPLE:END --> <!-- ⛔️ MD-MAGIC-EXAMPLE:START FILE src=./docs/1_Getting-Started.md -->

Install

To get started. Install the npm package.

npm install markdown-magic --save-dev

Usage

Use comment blocks in your markdown

Example:

<!-- doc-gen remote url=http://url-to-raw-md-file.md -->
This content will be dynamically replaced from the remote url
<!-- end-doc-gen -->

Then run markdown-magic via it's CLI or programmatically.

Running via CLI

Run markdown --help to see all available CLI options

markdown
# or
md-magic

CLI usage example with options

md-magic --path '**/*.md' --config ./config.file.js

In NPM scripts, npm run docs would run the markdown magic and parse all the .md files in the directory.

"scripts": {
  "docs": "md-magic --path '**/*.md'"
},

If you have a markdown.config.js file where markdown-magic is invoked, it will automatically use that as the configuration unless otherwise specified by --config flag.

Running programmatically

const { markdownMagic } = require('../lib')

/* By default all .md files in cwd will be processed */
markdownMagic().then((results) => {
  console.log('result keys', Object.keys(results))
})
import path from 'path'
import markdownMagic from 'markdown-magic'

// Process a Single File
const markdownPath = path.join(__dirname, 'README.md')
markdownMagic(markdownPath)
<!-- ⛔️ MD-MAGIC-EXAMPLE:END *--> <!-- ⛔️ MD-MAGIC-EXAMPLE:START (FILE:src=./docs/Syntax.md) -->

Syntax Examples

There are various syntax options. Choose your favorite.

Basic

openWord transformName [opts]

<!-- doc-gen transformName optionOne='hello' optionTwo='there' -->
content to be replaced
<!-- end-doc-gen -->

Curly braces

openWord {transformName} [opts]

<!-- doc-gen {transformName} optionOne='hello' optionTwo='there' -->
content to be replaced
<!-- end-doc-gen -->

Square brackets

openWord [transformName] [opts]

<!-- doc-gen [transformName] optionOne='hello' optionTwo='there' -->
content to be replaced
<!-- end-doc-gen -->

Parentheses

openWord (transformName) [opts]

<!-- doc-gen (transformName) optionOne='hello' optionTwo='there' -->
content to be replaced
<!-- end-doc-gen -->

Functions

openWord transformName([opts])

<!-- doc-gen transformName(
  foo='bar'
  baz=['qux', 'quux']
) -->
content to be replaced
<!-- end-doc-gen -->
<!-- ⛔️ MD-MAGIC-EXAMPLE:END *--> <!-- ⛔️ MD-MAGIC-EXAMPLE:START JSDocs path="./lib/index.js" -->

API

Markdown Magic Instance

markdownMagic(globOrOpts, options)
NameTypeDescription
globOrOptsFilePathsOrGlobs or MarkdownMagicOptionsFiles to process or config.
options (optional)MarkdownMagicOptionsMarkdown magic config.

Returns

Promise<MarkdownMagicResult>

Example

markdownMagic(['**.**.md'], options).then((result) => {
  console.log(`Processing complete`, result)
})

MarkdownMagicOptions

Configuration for markdown magic

Below is the main config for markdown-magic

NameTypeDescription
files (optional)FilePathsOrGlobsFiles to process.
transforms (optional)ArrayCustom commands to transform block contents, see transforms & custom transforms sections below. Default: defaultTransforms
output (optional)OutputConfigOutput configuration.
syntax (optional)SyntaxTypeSyntax to parse. Default: md
open (optional)stringOpening match word. Default: doc-gen
close (optional)stringClosing match word. If not defined will be same as opening word. Default: end-doc-gen
cwd (optional)stringCurrent working directory. Default process.cwd(). Default: process.cwd()
outputFlatten (optional)booleanFlatten files that are output.
useGitGlob (optional)booleanUse git glob for LARGE file directories.
dryRun (optional)booleanSee planned execution of matched blocks. Default: false
debug (optional)booleanSee debug details. Default: false
silent (optional)booleanSilence all console output. Default: false
failOnMissingTransforms (optional)booleanFail if transform functions are missing. Default skip blocks. Default: false

OutputConfig

Optional output configuration

NameTypeDescription
directory (optional)stringChange output path of new content. Default behavior is replacing the original file.
removeComments (optional)booleanRemove comments from output. Default is false. Default: false
pathFormatter (optional)functionCustom function for altering output paths.
applyTransformsToSource (optional)booleanApply transforms to source file. Default is true. This is for when outputDir is set. Default: false

MarkdownMagicResult

Result of markdown processing

NameTypeDescription
errorsArrayAny errors encountered.
filesChangedArray<string>Modified files.
resultsArraymd data.
<!-- ⛔️ MD-MAGIC-EXAMPLE:END - Do not remove or modify this section -->

Transforms

Markdown Magic comes with a couple of built-in transforms for you to use or you can extend it with your own transforms. See 'Custom Transforms' below.

<!-- ⛔️ MD-MAGIC-EXAMPLE:START JSDocs path="./lib/transforms/index.js" -->

> TOC

Generate table of contents from markdown file

Options:

Example:

<!-- doc-gen TOC -->
toc will be generated here
<!-- end-doc-gen -->

Default MATCHWORD is AUTO-GENERATED-CONTENT


NameTypeDescription
contentstringThe current content of the comment block.
optionsobjectThe options passed in from the comment declaration.

> CODE

Get code from file or URL and put in markdown

Options:

Example:

<!-- doc-gen CODE src="./relative/path/to/code.js" -->
This content will be dynamically replaced with code from the file
<!-- end-doc-gen -->
 <!-- doc-gen CODE src="./relative/path/to/code.js" lines=22-44 -->
 This content will be dynamically replaced with code from the file lines 22 through 44
 <!-- end-doc-gen -->

Default MATCHWORD is AUTO-GENERATED-CONTENT


NameTypeDescription
contentstringThe current content of the comment block.
optionsobjectThe options passed in from the comment declaration.

> FILE

Get local file contents.

Options:

Example:

<!-- doc-gen FILE src=./path/to/file -->
This content will be dynamically replaced from the local file
<!-- end-doc-gen -->

Default MATCHWORD is AUTO-GENERATED-CONTENT


NameTypeDescription
contentstringThe current content of the comment block.
optionsobjectThe options passed in from the comment declaration.

> REMOTE

Get any remote Data and put in markdown

Options:

Example:

<!-- doc-gen REMOTE url=http://url-to-raw-md-file.md -->
This content will be dynamically replaced from the remote url
<!-- end-doc-gen -->

Default MATCHWORD is AUTO-GENERATED-CONTENT


NameTypeDescription
contentstringThe current content of the comment block.
optionsobjectThe options passed in from the comment declaration.
<!-- ⛔️ MD-MAGIC-EXAMPLE:END - Do not remove or modify this section -->

Inline transforms

Any transform, including custom transforms can be used inline as well to insert content into paragraphs and other places.

The face symbol 👉 <!-- MD-MAGIC-EXAMPLE:START (INLINE_EXAMPLE) -->⊂◉‿◉つ<!-- MD-MAGIC-EXAMPLE:END --> is auto generated inline.

Example:

<!-- doc-gen (FILE:src=./path/to/file) -->xyz<!-- end-doc-gen -->

Legacy v1 & v2 plugins

These plugins work with older versions of markdown-magic. Adapting them to the newer plugin syntax should be pretty straight forward.

Adding Custom Transforms

Markdown Magic is extendable via plugins.

Plugins allow developers to add new transforms to the config.transforms object. This allows for things like using different rendering engines, custom formatting, or any other logic you might want.

Plugins run in order of registration.

The below code is used to generate this markdown file via the plugin system.

<!-- ⛔️ MD-MAGIC-EXAMPLE:START (CODE:src=./examples/generate-readme.js) -->
const path = require('path')
const { readFileSync } = require('fs')
const { parseComments } = require('doxxx')
const { markdownMagic } = require('../lib')
const { deepLog } = require('../lib/utils/logs')

const config = {
  matchWord: 'MD-MAGIC-EXAMPLE', // default matchWord is AUTO-GENERATED-CONTENT
  transforms: {
    /* Match <!-- AUTO-GENERATED-CONTENT:START (customTransform:optionOne=hi&optionOne=DUDE) --> */
    customTransform({ content, options }) {
      console.log('original content in comment block', content)
      console.log('options defined on transform', options)
      // options = { optionOne: hi, optionOne: DUDE}
      return `This will replace all the contents of inside the comment ${options.optionOne}`
    },
    /* Match <!-- AUTO-GENERATED-CONTENT:START JSDocs path="../file.js" --> */
    JSDocs(markdownMagicPluginAPI) {
      const { options } = markdownMagicPluginAPI
      const fileContents = readFileSync(options.path, 'utf8')
      const docBlocs = parseComments(fileContents, { skipSingleStar: true })
        .filter((item) => {
          return !item.isIgnored
        })
        /* Remove empty comments with no tags */
        .filter((item) => {
          return item.tags.length
        })
        /* Remove inline type defs */
        .filter((item) => {
          return item.description.text !== ''
        })
        /* Sort types to end */
        .sort((a, b) => {
          if (a.type && !b.type) return 1
          if (!a.type && b.type) return -1
          return 0
        })

      docBlocs.forEach((data) => {
        // console.log('data', data)
        delete data.code
      })
      // console.log('docBlocs', docBlocs)

      if (docBlocs.length === 0) {
        throw new Error('No docBlocs found')
      }

      // console.log(docBlocs.length)
      let updatedContent = ''
      docBlocs.forEach((data) => {
        if (data.type) {
          updatedContent += `#### \`${data.type}\`\n\n`
        }

        updatedContent += `${data.description.text}\n`

        if (data.tags.length) {
         let table =  '| Name | Type | Description |\n'
          table += '|:---------------------------|:---------------:|:-----------|\n'
          data.tags.filter((tag) => {
            if (tag.tagType === 'param') return true
            if (tag.tagType === 'property') return true
            return false
          }).forEach((tag) => {
            const optionalText = tag.isOptional ? ' (optional) ' : ' '
            const defaultValueText = (typeof tag.defaultValue !== 'undefined') ? ` Default: \`${tag.defaultValue}\` ` : ' '
            table += `| \`${tag.name}\`${optionalText}`
            table += `| \`${tag.type.replace('|', 'or')}\` `
            table += `| ${tag.description.replace(/\.\s?$/, '')}.${defaultValueText}|\n`
          })
          updatedContent+= `\n${table}\n`

          const returnValues = data.tags.filter((tag) => tag.tagType === 'returns')
          if (returnValues.length) {
            returnValues.forEach((returnValue) => {
              updatedContent += `**Returns**\n\n`
              updatedContent += `\`${returnValue.type}\`\n\n`
            })
          }

          const examples = data.tags.filter((tag) => tag.tagType === 'example')
          if (examples.length) {
            examples.forEach((example) => {
              updatedContent += `**Example**\n\n`
              updatedContent += `\`\`\`js\n${example.tagValue}\n\`\`\`\n\n`
            })
          }
        }
      })
      return updatedContent.replace(/^\s+|\s+$/g, '')
    },
    INLINE_EXAMPLE: () => {
      return '**⊂◉‿◉つ**'
    },
    lolz() {
      return `This section was generated by the cli config markdown.config.js file`
    },
    /* Match <!-- AUTO-GENERATED-CONTENT:START (pluginExample) --> */
    pluginExample: require('./plugin-example')({ addNewLine: true }),
    /* Include plugins from NPM */
    // count: require('markdown-magic-wordcount'),
    // github: require('markdown-magic-github-contributors')
  }
}

const markdownPath = path.join(__dirname, '..', 'README.md')
markdownMagic(markdownPath, config, () => {
  console.log('Docs ready')
})
<!-- ⛔️ MD-MAGIC-EXAMPLE:END -->

Plugin Example

Plugins must return a transform function with the following signature.

return function myCustomTransform (content, options)
<!-- ⛔️ MD-MAGIC-EXAMPLE:START (CODE:src=./examples/plugin-example.js) -->
/* Custom Transform Plugin example */
module.exports = function customPlugin(pluginOptions) {
  // set plugin defaults
  const defaultOptions = {
    addNewLine: false
  }
  const userOptions = pluginOptions || {}
  const pluginConfig = Object.assign(defaultOptions, userOptions)
  // return the transform function
  return function myCustomTransform ({ content, options }) {
    const newLine = (pluginConfig.addNewLine) ? '\n' : ''
    const updatedContent = content + newLine
    return updatedContent
  }
}
<!-- ⛔️ MD-MAGIC-EXAMPLE:END -->

View the raw file file and run npm run docs to see this plugin run

<!-- ⛔️ MD-MAGIC-EXAMPLE:START (pluginExample) ⛔️ -->

This content is altered by the pluginExample plugin registered in examples/generate-readme.js

<!-- ⛔️ MD-MAGIC-EXAMPLE:END -->

Other usage examples

Custom Transform Demo

View the raw source of this README.md file to see the comment block and see how the customTransform function in examples/generate-readme.js works

<!-- ⛔️ MD-MAGIC-EXAMPLE:START (customTransform:optionOne=hi&optionOne=DUDE) - Do not remove or modify this section -->

This will replace all the contents of inside the comment DUDE

<!-- ⛔️ MD-MAGIC-EXAMPLE:END - Do not remove or modify this section -->

Usage examples

Misc Markdown helpers

Prior Art

This was inspired by Kent C Dodds and jfmengels's all contributors cli project.

License

MIT © DavidWells