DavidWells / markdown-magic

💫  Automatically format markdown files via comment blocks using source code, external data & custom transforms.
817 stars 227 forks source link
docs docs-generator dx markdown readme

Markdown Magic npm-version

✨ Add a little magic to your markdown ✨

About

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

Click to expand - [About](#about) - [Install](#install) - [Usage](#usage) - [Running via CLI](#running-via-cli) - [Running programmatically](#running-programmatically) - [Syntax Examples](#syntax-examples) - [Basic](#basic) - [Curly braces](#curly-braces) - [Square brackets](#square-brackets) - [Parentheses](#parentheses) - [Functions](#functions) - [API](#api) - [`MarkdownMagicOptions`](#markdownmagicoptions) - [`OutputConfig`](#outputconfig) - [`MarkdownMagicResult`](#markdownmagicresult) - [Transforms](#transforms) - [> TOC](#-toc) - [> CODE](#-code) - [> FILE](#-file) - [> REMOTE](#-remote) - [Inline transforms](#inline-transforms) - [Legacy v1 & v2 plugins](#legacy-v1--v2-plugins) - [Adding Custom Transforms](#adding-custom-transforms) - [Plugin Example](#plugin-example) - [Other usage examples](#other-usage-examples) - [Custom Transform Demo](#custom-transform-demo) - [Usage examples](#usage-examples) - [Misc Markdown helpers](#misc-markdown-helpers) - [Prior Art](#prior-art) - [License](#license)

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)

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

API

Markdown Magic Instance

markdownMagic(globOrOpts, options)
Name Type Description
globOrOpts FilePathsOrGlobs or MarkdownMagicOptions Files to process or config.
options (optional) MarkdownMagicOptions Markdown 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

Name Type Description
files (optional) FilePathsOrGlobs Files to process.
transforms (optional) Array Custom commands to transform block contents, see transforms & custom transforms sections below. Default: defaultTransforms
output (optional) OutputConfig Output configuration.
syntax (optional) SyntaxType Syntax to parse. Default: md
open (optional) string Opening match word. Default: doc-gen
close (optional) string Closing match word. If not defined will be same as opening word. Default: end-doc-gen
cwd (optional) string Current working directory. Default process.cwd(). Default: process.cwd()
outputFlatten (optional) boolean Flatten files that are output.
useGitGlob (optional) boolean Use git glob for LARGE file directories.
dryRun (optional) boolean See planned execution of matched blocks. Default: false
debug (optional) boolean See debug details. Default: false
silent (optional) boolean Silence all console output. Default: false
failOnMissingTransforms (optional) boolean Fail if transform functions are missing. Default skip blocks. Default: false

OutputConfig

Optional output configuration

Name Type Description
directory (optional) string Change output path of new content. Default behavior is replacing the original file.
removeComments (optional) boolean Remove comments from output. Default is false. Default: false
pathFormatter (optional) function Custom function for altering output paths.
applyTransformsToSource (optional) boolean Apply transforms to source file. Default is true. This is for when outputDir is set. Default: false

MarkdownMagicResult

Result of markdown processing

Name Type Description
errors Array Any errors encountered.
filesChanged Array<string> Modified files.
results Array md data.

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.

> 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


Name Type Description
content string The current content of the comment block.
options object The options passed in from the comment declaration.

> CODE

Get code from file or URL and put in markdown

Options:

Example:

<!-- doc-gen CODE src="https://github.com/DavidWells/markdown-magic/raw/master/relative/path/to/code.js" -->
This content will be dynamically replaced with code from the file
<!-- end-doc-gen -->
 <!-- doc-gen CODE src="https://github.com/DavidWells/markdown-magic/raw/master/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


Name Type Description
content string The current content of the comment block.
options object The 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


Name Type Description
content string The current content of the comment block.
options object The 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


Name Type Description
content string The current content of the comment block.
options object The options passed in from the comment declaration.

Inline transforms

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

The face symbol 👉 ⊂◉‿◉つ 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.

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')
})

Plugin Example

Plugins must return a transform function with the following signature.

return function myCustomTransform (content, options)
/* 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
  }
}

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

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

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

This will replace all the contents of inside the comment DUDE

Usage examples

Misc Markdown helpers

Prior Art

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

License

MIT © DavidWells