├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .pre-commit-hooks.yaml ├── LICENSE ├── README.md ├── doctoc.js ├── lib ├── file.js ├── get-html-headers.js └── transform.js ├── package-lock.json ├── package.json ├── tea.yaml └── test ├── fixtures ├── readme-benign-backticks.md ├── readme-emoji-headers.md ├── readme-nameless-table-headers.md ├── readme-with-code.md ├── readme-with-custom-title.md ├── readme-with-formatted-headers.md ├── readme-with-html.md ├── readme-with-nested-markdown.md ├── readme-with-skipTag.md ├── readme-with-weird-headers.md └── stdout.md ├── transform-html.js ├── transform-nested-markdown.js ├── transform-skipTag.js ├── transform-stdout.js ├── transform-title.js ├── transform-weird-headers.js └── transform.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x, 20.x, 22.x] 16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | 27 | - run: npm install 28 | 29 | - run: npm test 30 | 31 | 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | samples 17 | tmp_samples 18 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: doctoc 2 | name: doctoc 3 | language: node 4 | entry: doctoc 5 | types: [markdown] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Thorsten Lorenz. 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DocToc [![Node.js CI](https://github.com/thlorenz/doctoc/actions/workflows/node.js.yml/badge.svg)](https://github.com/thlorenz/doctoc/actions/workflows/node.js.yml) 2 | 3 | Generates table of contents for markdown files inside local git repository. Links are compatible with anchors generated 4 | by github or other sites via a command line flag. 5 | 6 | 7 | 8 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 9 | 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Adding toc to all files in a directory and sub directories](#adding-toc-to-all-files-in-a-directory-and-sub-directories) 13 | - [Update existing doctoc TOCs effortlessly](#update-existing-doctoc-tocs-effortlessly) 14 | - [Adding toc to individual files](#adding-toc-to-individual-files) 15 | - [Examples](#examples) 16 | - [Using doctoc to generate links compatible with other sites](#using-doctoc-to-generate-links-compatible-with-other-sites) 17 | - [Example](#example) 18 | - [Specifying location of toc](#specifying-location-of-toc) 19 | - [Specifying a custom TOC title](#specifying-a-custom-toc-title) 20 | - [Specifying a maximum heading level for TOC entries](#specifying-a-maximum-heading-level-for-toc-entries) 21 | - [Printing to stdout](#printing-to-stdout) 22 | - [Usage as a `git` hook](#usage-as-a-git-hook) 23 | - [Docker image](#docker-image) 24 | 25 | 26 | 27 | 28 | ## Installation 29 | 30 | npm install -g doctoc 31 | 32 | ## Usage 33 | 34 | In its simplest usage, you can pass one or more files or folders to the 35 | `doctoc` command. This will update the TOCs of each file specified as well as of 36 | each markdown file found by recursively searching each folder. Below are some 37 | examples. 38 | 39 | ### Adding toc to all files in a directory and sub directories 40 | 41 | Go into the directory that contains your local git project and type: 42 | 43 | doctoc . 44 | 45 | This will update all markdown files in the current directory and all its 46 | subdirectories with a table of content that will point at the anchors generated 47 | by the markdown parser. Doctoc defaults to using the GitHub parser, but other 48 | [modes can be 49 | specified](#using-doctoc-to-generate-links-compatible-with-other-sites). 50 | 51 | ### Ignoring individual files 52 | In order to ignore a specific file when running `doctoc` on an entire directory, just add `` to the top of the file you wish to ignore. 53 | 54 | ### Update existing doctoc TOCs effortlessly 55 | 56 | If you already have a TOC inserted by doctoc, it will automatically be updated by running the command (rather than inserting a duplicate toc). Doctoc locates the TOC by the `` and `` comments, so you can also move a generated TOC to any other portion of your document and it will be updated there. 57 | 58 | ### Adding toc to individual files 59 | 60 | If you want to convert only specific files, do: 61 | 62 | doctoc /path/to/file [...] 63 | 64 | #### Examples 65 | 66 | doctoc README.md 67 | 68 | doctoc CONTRIBUTING.md LICENSE.md 69 | 70 | ### Using doctoc to generate links compatible with other sites 71 | 72 | In order to add a table of contents whose links are compatible other sites add the appropriate mode flag: 73 | 74 | Available modes are: 75 | 76 | ``` 77 | --bitbucket bitbucket.org 78 | --nodejs nodejs.org 79 | --github github.com 80 | --gitlab gitlab.com 81 | --ghost ghost.org 82 | ``` 83 | 84 | #### Example 85 | 86 | doctoc README.md --bitbucket 87 | 88 | ### Specifying location of toc 89 | 90 | By default, doctoc places the toc at the top of the file. You can indicate to have it placed elsewhere with the following format: 91 | 92 | ```markdown 93 | 94 | 95 | ``` 96 | 97 | You place this code directly in your .md file. For example: 98 | 99 | ```markdown 100 | // my_new_post.md 101 | Here we are, introducing the post. It's going to be great! 102 | But first: a TOC for easy reference. 103 | 104 | 105 | 106 | 107 | # Section One 108 | 109 | Here we'll discuss... 110 | 111 | ``` 112 | 113 | Running doctoc will insert the toc at that location. 114 | 115 | ### Specifying a custom TOC title 116 | 117 | Use the `--title` option to specify a (Markdown-formatted) custom TOC title; e.g., `doctoc --title '**Contents**' .` From then on, you can simply run `doctoc ` and doctoc will keep the title you specified. 118 | 119 | Alternatively, to blank out the title, use the `--notitle` option. This will simply remove the title from the TOC. 120 | 121 | ### Specifying a maximum heading level for TOC entries 122 | 123 | Use the `--maxlevel` option to limit TOC entries to headings only up to the specified level; e.g., `doctoc --maxlevel 3 .` 124 | 125 | By default, 126 | 127 | - no limit is placed on Markdown-formatted headings, 128 | - whereas headings from embedded HTML are limited to 4 levels. 129 | 130 | ### Printing to stdout 131 | 132 | You can print to stdout by using the `-s` or `--stdout` option. 133 | 134 | [ack]: http://beyondgrep.com/ 135 | 136 | ### Only update existing ToC 137 | 138 | Use `--update-only` or `-u` to only update the existing ToC. That is, the Markdown files without ToC will be left untouched. It is good if you want to use `doctoc` with `lint-staged`. 139 | 140 | ### Usage as a `git` hook 141 | 142 | doctoc can be used as a [pre-commit](http://pre-commit.com) hook by using the 143 | following configuration: 144 | 145 | ```yaml 146 | repos: 147 | - repo: https://github.com/thlorenz/doctoc 148 | rev: ... # substitute a tagged version 149 | hooks: 150 | - id: doctoc 151 | ``` 152 | 153 | This will run `doctoc` against markdown files when committing to ensure the 154 | TOC stays up-to-date. 155 | 156 | ### Docker image 157 | 158 | There's an unofficial Docker image project for doctoc, if you'd like to use doctoc via Docker or other container based CI/CD pipeline, you can take a look at [PeterDaveHello/docker-doctoc](https://github.com/PeterDaveHello/docker-doctoc) 159 | -------------------------------------------------------------------------------- /doctoc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var path = require('path') 6 | , fs = require('fs') 7 | , minimist = require('minimist') 8 | , file = require('./lib/file') 9 | , transform = require('./lib/transform') 10 | , files; 11 | 12 | function cleanPath(path) { 13 | var homeExpanded = (path.indexOf('~') === 0) ? process.env.HOME + path.substr(1) : path; 14 | 15 | return homeExpanded; 16 | } 17 | 18 | function transformAndSave(files, mode, maxHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly) { 19 | if (processAll) { 20 | console.log('--all flag is enabled. Including headers before the TOC location.') 21 | } 22 | 23 | if (updateOnly) { 24 | console.log('--update-only flag is enabled. Only updating files that already have a TOC.') 25 | } 26 | 27 | console.log('\n==================\n'); 28 | 29 | var transformed = files 30 | .map(function (x) { 31 | var content = fs.readFileSync(x.path, 'utf8') 32 | , result = transform(content, mode, maxHeaderLevel, title, notitle, entryPrefix, processAll, updateOnly); 33 | result.path = x.path; 34 | return result; 35 | }); 36 | var changed = transformed.filter(function (x) { return x.transformed; }) 37 | , unchanged = transformed.filter(function (x) { return !x.transformed; }) 38 | , toc = transformed.filter(function (x) { return x.toc; }) 39 | 40 | if (stdOut) { 41 | toc.forEach(function (x) { 42 | console.log(x.toc) 43 | }) 44 | } 45 | 46 | unchanged.forEach(function (x) { 47 | console.log('"%s" is up to date', x.path); 48 | }); 49 | 50 | changed.forEach(function (x) { 51 | if (stdOut) { 52 | console.log('==================\n\n"%s" should be updated', x.path) 53 | } else { 54 | console.log('"%s" will be updated', x.path); 55 | fs.writeFileSync(x.path, x.data, 'utf8'); 56 | } 57 | }); 58 | } 59 | 60 | function printUsageAndExit(isErr) { 61 | 62 | var outputFunc = isErr ? console.error : console.info; 63 | 64 | outputFunc('Usage: doctoc [mode] [--entryprefix prefix] [--notitle | --title title] [--maxlevel level] [--all] [--update-only] (where path is some path to a directory (e.g., .) or a file (e.g., README.md))'); 65 | outputFunc('\nAvailable modes are:'); 66 | for (var key in modes) { 67 | outputFunc(' --%s\t%s', key, modes[key]); 68 | } 69 | outputFunc('Defaults to \'' + mode + '\'.'); 70 | 71 | process.exit(isErr ? 2 : 0); 72 | } 73 | 74 | var modes = { 75 | bitbucket : 'bitbucket.org' 76 | , nodejs : 'nodejs.org' 77 | , github : 'github.com' 78 | , gitlab : 'gitlab.com' 79 | , ghost : 'ghost.org' 80 | } 81 | 82 | var mode = modes['github']; 83 | 84 | var argv = minimist(process.argv.slice(2) 85 | , { boolean: [ 'h', 'help', 'T', 'notitle', 's', 'stdout', 'all' , 'u', 'update-only'].concat(Object.keys(modes)) 86 | , string: [ 'title', 't', 'maxlevel', 'm', 'entryprefix' ] 87 | , unknown: function(a) { return (a[0] == '-' ? (console.error('Unknown option(s): ' + a), printUsageAndExit(true)) : true); } 88 | }); 89 | 90 | if (argv.h || argv.help) { 91 | printUsageAndExit(); 92 | } 93 | 94 | for (var key in modes) { 95 | if (argv[key]) { 96 | mode = modes[key]; 97 | } 98 | } 99 | 100 | var title = argv.t || argv.title; 101 | var notitle = argv.T || argv.notitle; 102 | var entryPrefix = argv.entryprefix || '-'; 103 | var processAll = argv.all; 104 | var stdOut = argv.s || argv.stdout 105 | var updateOnly = argv.u || argv['update-only'] 106 | 107 | var maxHeaderLevel = argv.m || argv.maxlevel; 108 | if (maxHeaderLevel && isNaN(maxHeaderLevel) || maxHeaderLevel < 0) { console.error('Max. heading level specified is not a positive number: ' + maxHeaderLevel), printUsageAndExit(true); } 109 | 110 | for (var i = 0; i < argv._.length; i++) { 111 | var target = cleanPath(argv._[i]) 112 | , stat = fs.statSync(target) 113 | 114 | if (stat.isDirectory()) { 115 | console.log ('\nDocToccing "%s" and its sub directories for %s.', target, mode); 116 | files = file.findMarkdownFiles(target); 117 | } else { 118 | console.log ('\nDocToccing single file "%s" for %s.', target, mode); 119 | files = [{ path: target }]; 120 | } 121 | 122 | transformAndSave(files, mode, maxHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly); 123 | 124 | console.log('\nEverything is OK.'); 125 | } 126 | 127 | module.exports.transform = transform; 128 | -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , fs = require('fs') 3 | , _ = require('underscore'); 4 | 5 | var markdownExts = ['.md', '.markdown']; 6 | var ignoredDirs = ['.', '..', '.git', 'node_modules']; 7 | 8 | function separateFilesAndDirs(fileInfos) { 9 | return { 10 | directories : _(fileInfos).filter(function (x) { 11 | return x.isDirectory() && !_(ignoredDirs).include(x.name); 12 | }), 13 | markdownFiles : _(fileInfos).filter(function (x) { 14 | return x.isFile() && _(markdownExts).include(path.extname(x.name)); 15 | }) 16 | }; 17 | } 18 | 19 | function findRec(currentPath) { 20 | function getStat (entry) { 21 | var target = path.join(currentPath, entry), 22 | stat = fs.statSync(target); 23 | 24 | return _(stat).extend({ 25 | name: entry, 26 | path: target 27 | }); 28 | } 29 | 30 | function process (fileInfos) { 31 | var res = separateFilesAndDirs(fileInfos); 32 | var tgts = _(res.directories).pluck('path'); 33 | 34 | if (res.markdownFiles.length > 0) 35 | console.log('\nFound %s in "%s"', _(res.markdownFiles).pluck('name').join(', '), currentPath); 36 | else 37 | console.log('\nFound nothing in "%s"', currentPath); 38 | 39 | return { 40 | markdownFiles : res.markdownFiles, 41 | subdirs : tgts 42 | }; 43 | } 44 | 45 | var stats = _(fs.readdirSync(currentPath)).map(getStat) 46 | , res = process(stats) 47 | , markdownsInSubdirs = _(res.subdirs).map(findRec) 48 | , allMarkdownsHereAndSub = res.markdownFiles.concat(markdownsInSubdirs); 49 | 50 | return _(allMarkdownsHereAndSub).flatten(); 51 | } 52 | 53 | // Finds all markdown files in given directory and its sub-directories 54 | // @param {String } dir - the absolute directory to search in 55 | exports.findMarkdownFiles = function(dir) { 56 | return findRec(dir); 57 | }; 58 | 59 | /* Example: 60 | console.log('\033[2J'); // clear console 61 | 62 | var res = findRec(path.join(__dirname, '..', 'samples')); 63 | console.log('Result: ', res); 64 | */ 65 | -------------------------------------------------------------------------------- /lib/get-html-headers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var htmlparser = require('htmlparser2') 4 | , md = require('@textlint/markdown-to-ast'); 5 | 6 | function addLinenos(lines, headers) { 7 | var current = 0, line; 8 | 9 | return headers.map(function (x) { 10 | for (var lineno = current; lineno < lines.length; lineno++) { 11 | line = lines[lineno]; 12 | if (new RegExp(x.text[0]).test(line)) { 13 | current = lineno; 14 | x.line = lineno; 15 | x.name = x.text.join(''); 16 | return x 17 | } 18 | } 19 | 20 | // in case we didn't find a matching line, which is odd, 21 | // we'll have to assume it's right on the next line 22 | x.line = ++current; 23 | x.name = x.text.join(''); 24 | return x 25 | }) 26 | } 27 | 28 | function rankify(headers, max) { 29 | return headers 30 | .map(function (x) { 31 | x.rank = parseInt(x.tag.slice(1), 10); 32 | return x; 33 | }) 34 | .filter(function (x) { 35 | return x.rank <= max; 36 | }) 37 | } 38 | 39 | var go = module.exports = function (lines, maxHeaderLevel) { 40 | var source = md.parse(lines.join('\n')) 41 | .children 42 | .filter(function(node) { 43 | return node.type === md.Syntax.HtmlBlock || node.type === md.Syntax.Html; 44 | }) 45 | .map(function (node) { 46 | return node.raw; 47 | }) 48 | .join('\n'); 49 | 50 | //var headers = [], grabbing = null, text = []; 51 | var headers = [], grabbing = [], text = []; 52 | 53 | var parser = new htmlparser.Parser({ 54 | onopentag: function (name, attr) { 55 | // Short circuit if we're already inside a pre 56 | if (grabbing[grabbing.length - 1] === 'pre') return; 57 | 58 | if (name === 'pre' || (/h\d/).test(name)) { 59 | grabbing.push(name); 60 | } 61 | }, 62 | ontext: function (text_) { 63 | // Explicitly skip pre tags, and implicitly skip all others 64 | if (grabbing.length === 0 || 65 | grabbing[grabbing.length - 1] === 'pre') return; 66 | 67 | text.push(text_); 68 | }, 69 | onclosetag: function (name) { 70 | if (grabbing.length === 0) return; 71 | if (grabbing[grabbing.length - 1] === name) { 72 | var tag = grabbing.pop(); 73 | headers.push({ text: text, tag: tag }); 74 | text = []; 75 | } 76 | } 77 | }, 78 | { decodeEntities: true }) 79 | 80 | parser.write(source); 81 | parser.end(); 82 | 83 | headers = addLinenos(lines, headers) 84 | // consider anything past h4 to small to warrant a link, may be made configurable in the future 85 | headers = rankify(headers, maxHeaderLevel); 86 | return headers; 87 | } 88 | -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('underscore') 4 | , anchor = require('anchor-markdown-header') 5 | , updateSection = require('update-section') 6 | , getHtmlHeaders = require('./get-html-headers') 7 | , md = require('@textlint/markdown-to-ast'); 8 | 9 | var start = '\n' + 10 | '' 11 | , end = '' 12 | , skipTag = ''; 13 | 14 | 15 | function matchesStart(line) { 16 | return (/ 4 | 5 | 6 | # 🔴 or 🟡 - At Risk 7 | 8 | # 🔄 Still Need Updates 9 | 10 | ## ⏱ Past-Due Items 11 | 12 | # ➡ ETA Changes This Week 13 | 14 | # 🚀 Shipped this week 15 | 16 | # 🛠 Availability repair items 17 | 18 | # 🎟 Support Tickets 19 | 20 | # 🔬 Team-by-team Breakdown: Hello 21 | -------------------------------------------------------------------------------- /test/fixtures/readme-nameless-table-headers.md: -------------------------------------------------------------------------------- 1 | # Heading One 2 | 3 | | | | 4 | | --- | --- | 5 | | foo | bar | 6 | 7 | The columns above don't have titles; they're just empty. 8 | 9 | ## Subheading 2 10 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-code.md: -------------------------------------------------------------------------------- 1 | README to test doctoc with edge-case headers. 2 | 3 | 4 | 5 | ## Table of Contents 6 | 7 | - [Single Backticks](#single-backticks) 8 | - [Multiple Backticks](#multiple-backticks) 9 | - [code tag](#code-tag) 10 | - [pre tag](#pre-tag) 11 | 12 | 13 | 14 | 15 | ## Single Backticks 16 | `

not me single backticks

` 17 | `## nor me single backticks` 18 | 19 | ## Multiple Backticks 20 | ``` 21 |

not me fenced

22 | ## nor me fenced 23 | not even me fenced 24 | ------------------ 25 | ``` 26 | 27 | ## code tag 28 |

not me code

29 | ## nor me code 30 | 31 | ## pre tag 32 |
33 |     

not me pre

34 | ## nor me pre 35 | not even me pre 36 | ------- 37 |
38 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-custom-title.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | README to test doctoc with user-specified titles. 4 | 5 | 6 | 7 | ## Table of Contents 8 | 9 | - [Installation](#installation) 10 | - [API](#api) 11 | - [License](#license) 12 | 13 | 14 | 15 | 16 | ## Installation 17 | ## API 18 | ## License 19 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-formatted-headers.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## foo _bar_ 5 | ## foo **baz** 6 | ## foo ~baf~ 7 | ## bar_foo 8 | ## baz_foo_ 9 | ## _foo_bax_ 10 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-html.md: -------------------------------------------------------------------------------- 1 | # dockops [![build status](https://secure.travis-ci.org/thlorenz/dockops.png)](http://travis-ci.org/thlorenz/dockops) 2 | 3 | docker convenience functions on top of dockerode 4 | 5 | 6 | 7 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 8 | 9 | - [Installation](#installation) 10 | - [API](#api) 11 | - [License](#license) 12 | 13 | 14 | 15 | ```js 16 | var dockops = require('dockops') 17 | var docker = dockops.createDocker({ dockerhost: dockerhost }); 18 | 19 | var images = new dockops.Images(docker); 20 | dockops.logEvents(images, 'verbose'); 21 | 22 | var containers = new dockops.Containers(docker); 23 | dockops.logEvents(containers, 'verbose'); 24 | 25 | build('test:uno', testUnoTar, function () { 26 | build('toast:uno', toastUnoTar, function () { 27 | 28 | containers.run(.. // run test:uno and toast:uno containers 29 | 30 | containers.listRunning(function (err, res) { 31 | inspect(res); 32 | containers.stopRemoveGroup('test', function (err, res) { 33 | containers.listRunning(function (err, res) { 34 | inspect(res); 35 | http.request({ port: 49222 }, function (res) { 36 | console.log('--------------------------') 37 | inspect({ status: res.statusCode, headers: res.headers }) 38 | res.pipe(process.stdout) 39 | }).end() 40 | }) 41 | }) 42 | }) 43 | }) 44 | }) 45 | ``` 46 | 47 | [full example](https://github.com/thlorenz/dockops/blob/master/example/create-wipe.js) 48 | 49 | ![output](https://github.com/thlorenz/dockops/raw/master/assets/output.gif) 50 | 51 | ## Installation 52 | 53 | npm install dockops 54 | 55 | ## API 56 | 57 | 58 | 59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

dockops::Containers(docker) → {Object}

71 |
72 |
73 |
74 |

Creates a new containers instance that will use the given docker instance to communicate with docker.

75 |
76 |
Parameters:
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 91 | 92 | 93 | 94 |
NameTypeDescription
docker 89 | Object 90 |

dockerode instance to communicate with docker

95 |
96 |
Source:
97 |
104 |
105 |
Returns:
106 |
107 |

initialized containers

108 |
109 |
110 |
111 | Type 112 |
113 |
114 | Object 115 |
116 |
117 |
118 |
119 |

dockops::Containers::activePorts(cb)

120 |
121 |
122 |
123 |

Lists all running containers by the ports they expose.

124 |
125 |
Parameters:
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 140 | 141 | 142 | 143 |
NameTypeDescription
cb 138 | function 139 |

called back with list of containers hashed by their port number

144 |
145 |
Source:
146 |
153 |
154 |
155 |
156 |

dockops::Containers::clean(id, cb)

157 |
158 |
159 |
160 |

Stops and/or kills and then removes a container.

161 |

Heavy machinery clean operation. 162 | It was useful when running on arch with docker not always working as promised. 163 | This may not be needed anymore as docker got more stable.

164 |
165 |
Parameters:
166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 180 | 181 | 182 | 183 | 184 | 187 | 188 | 189 | 190 |
NameTypeDescription
id 178 | string 179 |

container id

cb 185 | function 186 |

called back after container was cleaned or maximum attempts were exceeded

191 |
192 |
Source:
193 |
200 |
201 |
202 |
203 |
204 |
205 | 206 | *generated with [docme](https://github.com/thlorenz/docme)* 207 |
208 | 209 | 210 | ## License 211 | 212 | MIT 213 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-nested-markdown.md: -------------------------------------------------------------------------------- 1 | # [CNN](https://www.cnn.com) 2 | 3 | This header's a link to another site. We want to just show the link's text, not 4 | the link URL. 5 | 6 | # Get Involved [![Gitter chat](https://badges.gitter.im/Cockatrice/Cockatrice.png)](https://gitter.im/Cockatrice/Cockatrice) 7 | 8 | Chat with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here. 9 | 10 | # Translation Status [![Cockatrice on Transifex](https://ds0k0en9abmn1.cloudfront.net/static/charts/images/tx-logo-micro.646b0065fce6.png)](https://www.transifex.com/projects/p/cockatrice/) 11 | 12 | Cockatrice uses Transifex for translations. You can help us bring Cockatrice/Oracle to your language or edit single wordings by clicking on the associated charts below.
13 | Our [project page](https://www.transifex.com/projects/p/cockatrice/) offers a detailed overview for contributors. 14 | 15 | # Building [![Build Status](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice) 16 | 17 | **Detailed installation instructions are on the Cockatrice wiki under [Installing Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Installing-Cockatrice)** 18 | 19 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-skipTag.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | README to test doctoc with skipTag. 4 | 5 | 6 | 7 | ## Installation 8 | ## API 9 | ## License 10 | -------------------------------------------------------------------------------- /test/fixtures/readme-with-weird-headers.md: -------------------------------------------------------------------------------- 1 | README to test doctoc with edge-case headers. 2 | 3 | 4 | 5 | ## Table of Contents 6 | 7 | - [hasOwnProperty](#hasownproperty) 8 | - [something else](#something-else) 9 | 10 | 11 | 12 | 13 | ## hasOwnProperty 14 | ## something else 15 | -------------------------------------------------------------------------------- /test/fixtures/stdout.md: -------------------------------------------------------------------------------- 1 | 2 | DocToccing single file "test/fixtures/readme-with-custom-title.md" for github.com. 3 | 4 | ================== 5 | 6 | ## Table of Contents 7 | 8 | - [Installation](#installation) 9 | - [API](#api) 10 | - [License](#license) 11 | 12 | ================== 13 | 14 | "test/fixtures/readme-with-custom-title.md" should be updated 15 | 16 | Everything is OK. 17 | -------------------------------------------------------------------------------- /test/transform-html.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform') 6 | 7 | test('\ngiven a file that includes html with header tags and maxHeaderLevel 8', function (t) { 8 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-html.md', 'utf8'); 9 | var headers = transform(content, 'github.com', 8); 10 | 11 | t.deepEqual( 12 | headers.toc.split('\n') 13 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 14 | '', 15 | '- [Installation](#installation)', 16 | '- [API](#api)', 17 | ' - [dockops::Containers(docker) → {Object}](#dockopscontainersdocker-%E2%86%92-object)', 18 | ' - [Parameters:](#parameters)', 19 | ' - [Returns:](#returns)', 20 | ' - [dockops::Containers::activePorts(cb)](#dockopscontainersactiveportscb)', 21 | ' - [Parameters:](#parameters-1)', 22 | ' - [dockops::Containers::clean(id, cb)](#dockopscontainerscleanid-cb)', 23 | ' - [Parameters:](#parameters-2)', 24 | '- [License](#license)', 25 | '' ] 26 | , 'generates correct toc for non html and html headers' 27 | ) 28 | 29 | t.end() 30 | }) 31 | 32 | test('\ngiven a file that includes html with header tags using default maxHeaderLevel', function (t) { 33 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-html.md', 'utf8'); 34 | var headers = transform(content); 35 | 36 | t.deepEqual( 37 | headers.toc.split('\n') 38 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 39 | '', 40 | '- [Installation](#installation)', 41 | '- [API](#api)', 42 | ' - [dockops::Containers(docker) → {Object}](#dockopscontainersdocker-%E2%86%92-object)', 43 | ' - [dockops::Containers::activePorts(cb)](#dockopscontainersactiveportscb)', 44 | ' - [dockops::Containers::clean(id, cb)](#dockopscontainerscleanid-cb)', 45 | '- [License](#license)', 46 | '' ] 47 | , 'generates correct toc for non html and html headers omitting headers larger than maxHeaderLevel' 48 | ) 49 | t.end() 50 | }) 51 | 52 | test('\ngiven a file with headers embedded in code', function (t) { 53 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-code.md', 'utf8'); 54 | var headers = transform(content); 55 | 56 | t.deepEqual( 57 | headers.toc.split('\n') 58 | , [ '## Table of Contents', 59 | '', 60 | '- [Single Backticks](#single-backticks)', 61 | '- [Multiple Backticks](#multiple-backticks)', 62 | '- [code tag](#code-tag)', 63 | '- [pre tag](#pre-tag)', 64 | '' ] 65 | , 'generates a correct toc when headers are embedded in code blocks' 66 | ) 67 | 68 | t.end() 69 | }) 70 | 71 | test('\ngiven a file with benign backticks', function (t) { 72 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-benign-backticks.md', 'utf8'); 73 | var headers = transform(content); 74 | 75 | t.deepEqual( 76 | headers.toc.split('\n') 77 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 78 | '', 79 | '- [Hello, world!](#hello-world)', 80 | '- [Add this header](#add-this-header)', 81 | '- [And also this one](#and-also-this-one)', 82 | '' ] 83 | , 'generates a correct toc when readme has benign backticks' 84 | ) 85 | 86 | t.end() 87 | }) 88 | -------------------------------------------------------------------------------- /test/transform-nested-markdown.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform'); 6 | 7 | test('\nhandle inline links and images', function (t) { 8 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-nested-markdown.md', 'utf8'); 9 | var headers = transform(content, null, null, '', false); 10 | 11 | t.deepEqual( 12 | headers.toc.split('\n') 13 | , [ 14 | '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 15 | '', 16 | '- [CNN](#cnn)', 17 | '- [Get Involved *](#get-involved-)', 18 | '- [Translation Status *](#translation-status-)', 19 | '- [Building *](#building-)', 20 | '', 21 | ] 22 | , 'generates correct toc for nested markdown (links and images)' 23 | ) 24 | t.end() 25 | }); 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/transform-skipTag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform'); 6 | 7 | test('\nskip file transform', function (t) { 8 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-skipTag.md', 'utf8'); 9 | var transformedContent = transform(content); 10 | 11 | t.deepEqual( 12 | transformedContent.toc 13 | , undefined 14 | , 'skip correct file' 15 | ) 16 | t.end() 17 | }); 18 | -------------------------------------------------------------------------------- /test/transform-stdout.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test, 5 | fs = require('fs'), 6 | exec = require("child_process").exec; 7 | 8 | test('\nshould print to stdout with --stdout option', function (t) { 9 | 10 | exec('node doctoc.js test/fixtures/readme-with-custom-title.md --stdout', function (error, stdout, stderr) { 11 | if (error) { 12 | console.error('exec error: ', error); 13 | return; 14 | } 15 | t.deepEqual(stdout 16 | , fs.readFileSync(__dirname + '/fixtures/stdout.md', 'utf8') 17 | , 'spits out the correct table of contents') 18 | 19 | t.end() 20 | }) 21 | }) 22 | 23 | test('\nshould print to stdout with -s option', function (t) { 24 | 25 | exec('node doctoc.js test/fixtures/readme-with-custom-title.md -s', function (error, stdout, stderr) { 26 | if (error) { 27 | console.error('exec error: ', error); 28 | return; 29 | } 30 | t.deepEqual(stdout 31 | , fs.readFileSync(__dirname + '/fixtures/stdout.md', 'utf8') 32 | , 'spits out the correct table of contents') 33 | 34 | t.end() 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/transform-title.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform'); 6 | 7 | test('\noverwrite existing title', function (t) { 8 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-custom-title.md', 'utf8'); 9 | var headers = transform(content, null, null, '## Table of Contents', false); 10 | 11 | t.deepEqual( 12 | headers.toc.split('\n') 13 | , [ '## Table of Contents', 14 | '', 15 | '- [Installation](#installation)', 16 | '- [API](#api)', 17 | '- [License](#license)', 18 | '' ] 19 | , 'generates correct toc for when custom --title was passed' 20 | ) 21 | t.end() 22 | }); 23 | 24 | test('\ndo not overwrite existing title', function (t) { 25 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-custom-title.md', 'utf8'); 26 | var headers = transform(content, null, null, null, false); 27 | 28 | t.deepEqual( 29 | headers.toc.split('\n') 30 | , [ '## Table of Contents', 31 | '', 32 | '- [Installation](#installation)', 33 | '- [API](#api)', 34 | '- [License](#license)', 35 | '' ] 36 | , 'generates correct toc for when custom title exists in README already' 37 | ) 38 | t.end() 39 | }); 40 | 41 | test('\nclobber existing title', function (t) { 42 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-custom-title.md', 'utf8'); 43 | var headers = transform(content, null, null, null, true); 44 | 45 | t.deepEqual( 46 | headers.toc.split('\n') 47 | , [ '', 48 | '- [Installation](#installation)', 49 | '- [API](#api)', 50 | '- [License](#license)', 51 | '' ] 52 | , 'generates correct toc for when --notitle was passed' 53 | ) 54 | t.end() 55 | }); 56 | -------------------------------------------------------------------------------- /test/transform-weird-headers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform'); 6 | 7 | test('\ngiven a file with edge-case header names', function (t) { 8 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-weird-headers.md', 'utf8'); 9 | var headers = transform(content); 10 | 11 | t.deepEqual( 12 | headers.toc.split('\n') 13 | , [ '## Table of Contents', 14 | '', 15 | '- [hasOwnProperty](#hasownproperty)', 16 | '- [something else](#something-else)', 17 | '' ] 18 | , 'generates a correct toc when headers are weird' 19 | ) 20 | 21 | t.end() 22 | }) 23 | 24 | test('\nnameless table headers', function (t) { 25 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-nameless-table-headers.md', 'utf8'); 26 | var headers = transform(content); 27 | 28 | t.deepEqual( 29 | headers.toc.split('\n') 30 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 31 | '', 32 | '- [Heading One](#heading-one)', 33 | ' - [Subheading 2](#subheading-2)', 34 | '' ] 35 | , 'generates a correct toc when readme has nameless table headers' 36 | ) 37 | 38 | t.end() 39 | }) 40 | 41 | test('\nemoji-first header names', function (t) { 42 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-emoji-headers.md', 'utf8'); 43 | var headers = transform(content); 44 | 45 | t.same( 46 | headers.toc.split('\n') 47 | , [ '', 48 | '- [🔴 or 🟡 - At Risk](#-or----at-risk)', 49 | '- [🔄 Still Need Updates](#-still-need-updates)', 50 | ' - [⏱ Past-Due Items](#-past-due-items)', 51 | '- [➡ ETA Changes This Week](#-eta-changes-this-week)', 52 | '- [🚀 Shipped this week](#-shipped-this-week)', 53 | '- [🛠 Availability repair items](#-availability-repair-items)', 54 | '- [🎟 Support Tickets](#-support-tickets)', 55 | '- [🔬 Team-by-team Breakdown: Hello](#-team-by-team-breakdown-hello)', 56 | '' ] 57 | , 'generates a correct toc when readme has emojis as the first character for headings' 58 | ) 59 | 60 | t.end() 61 | }) 62 | 63 | 64 | test('\nformatted headers', function (t) { 65 | var content = require('fs').readFileSync(__dirname + '/fixtures/readme-with-formatted-headers.md', 'utf8'); 66 | var headers = transform(content); 67 | 68 | t.same( 69 | headers.toc.split('\n') 70 | , [ '', 71 | '- [foo _bar_](#foo-bar)', 72 | '- [foo **baz**](#foo-baz)', 73 | '- [foo ~baf~](#foo-baf)', 74 | '- [bar_foo](#bar_foo)', 75 | '- [baz_foo_](#baz_foo_)', 76 | '- [_foo_bax_](#foo_bax)', 77 | '' ] 78 | , 'generates a correct toc when readme includes formatting in the heading title' 79 | ) 80 | 81 | t.end() 82 | }) 83 | -------------------------------------------------------------------------------- /test/transform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /*jshint asi: true */ 3 | 4 | var test = require('tap').test 5 | , transform = require('../lib/transform') 6 | 7 | function inspect(obj, depth) { 8 | console.log(require('util').inspect(obj, false, depth || 5, true)); 9 | } 10 | 11 | function check(md, anchors, mode, maxHeaderLevel, title, notitle, entryPrefix, processAll) { 12 | test('transforming', function (t) { 13 | var res = transform(md, mode, maxHeaderLevel, title, notitle, entryPrefix, processAll) 14 | 15 | // remove wrapper 16 | var data = res.data.split('\n'); 17 | 18 | // rig our expected value to include the wrapper 19 | var startLines = transform.start.split('\n') 20 | , anchorLines = anchors.split('\n') 21 | , endLines = transform.end.split('\n') 22 | , mdLines = md.split('\n') 23 | 24 | var rig = startLines 25 | .concat(anchorLines.slice(0, -2)) 26 | .concat(endLines) 27 | .concat('') 28 | .concat(mdLines); 29 | 30 | t.ok(res.transformed, 'transforms it'); 31 | t.deepEqual(data, rig, 'generates correct anchors') 32 | t.end() 33 | }) 34 | } 35 | //function check() {} 36 | 37 | check( 38 | [ '# My Module' 39 | , 'Some text here' 40 | , '## API' 41 | , '### Method One' 42 | , 'works like this' 43 | , '### Method Two' 44 | , '#### Main Usage' 45 | , 'some main usage here' 46 | ].join('\n') 47 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 48 | , '- [My Module](#my-module)\n' 49 | , ' - [API](#api)\n' 50 | , ' - [Method One](#method-one)\n' 51 | , ' - [Method Two](#method-two)\n' 52 | , ' - [Main Usage](#main-usage)\n\n\n' 53 | ].join('') 54 | ) 55 | 56 | check( 57 | [ '# My Module using \\r\\n line endings' 58 | , 'Some text here' 59 | , '## API' 60 | , '### Method One' 61 | , 'works like this' 62 | , '### Method Two' 63 | , '#### Main Usage' 64 | , 'some main usage here' 65 | ].join('\r\n') 66 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 67 | , '- [My Module using \\r\\n line endings](#my-module-using-%5Cr%5Cn-line-endings)\n' 68 | , ' - [API](#api)\n' 69 | , ' - [Method One](#method-one)\n' 70 | , ' - [Method Two](#method-two)\n' 71 | , ' - [Main Usage](#main-usage)\n\n\n' 72 | ].join('') 73 | ) 74 | 75 | check( 76 | [ 'My Module' 77 | , '=========' 78 | , 'Some text here' 79 | , '' 80 | , 'API' 81 | , '---------' 82 | ].join('\n') 83 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 84 | , '- [My Module](#my-module)\n' 85 | , ' - [API](#api)\n\n\n' 86 | ].join('') 87 | ) 88 | 89 | check( 90 | [ '# My Module #' 91 | , 'Some text here' 92 | , '## API ##' 93 | ].join('\n') 94 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 95 | , '- [My Module](#my-module)\n' 96 | , ' - [API](#api)\n\n\n' 97 | ].join('') 98 | ) 99 | 100 | check( 101 | [ '## Title should be included' 102 | , '' 103 | , '```js' 104 | , 'function foo () {' 105 | , ' // ## This title should be ignored' 106 | , '}' 107 | , '## This title should also be ignored' 108 | , '```' 109 | , '' 110 | , '## Title should also be included' 111 | ].join('\n') 112 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 113 | , '- [Title should be included](#title-should-be-included)\n' 114 | , '- [Title should also be included](#title-should-also-be-included)\n\n\n' 115 | ].join('') 116 | ) 117 | 118 | check( 119 | [ '# Repeating A Title' 120 | , '' 121 | , '# Repeating A Title' 122 | ].join('\n') 123 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 124 | , '- [Repeating A Title](#repeating-a-title)\n' 125 | , '- [Repeating A Title](#repeating-a-title-1)\n\n\n' 126 | ].join('') 127 | ) 128 | 129 | check( 130 | [ '## Header' 131 | , 'some content' 132 | , '-- preceded by two dashes but has content, therefore "some content" should not be header' 133 | ].join('\n') 134 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n', 135 | '- [Header](#header)\n\n\n', 136 | ].join('') 137 | ) 138 | 139 | check( 140 | [ '# Different Kinds' 141 | , '' 142 | , 'In the Right Order' 143 | , '==================' 144 | ].join('\n') 145 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 146 | , '- [Different Kinds](#different-kinds)\n' 147 | , '- [In the Right Order](#in-the-right-order)\n\n\n' 148 | ].join('') 149 | ) 150 | 151 | check( 152 | [ 'Different Kinds 2' 153 | , '===============' 154 | , '' 155 | , '# In the Right Order 2' 156 | ].join('\n') 157 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 158 | , '- [Different Kinds 2](#different-kinds-2)\n' 159 | , '- [In the Right Order 2](#in-the-right-order-2)\n\n\n' 160 | ].join('') 161 | ) 162 | 163 | check( 164 | [ '# Heading' 165 | , '' 166 | , 'Custom TOC title test' 167 | ].join('\n') 168 | , [ '**Contents**\n\n' 169 | , '- [Heading](#heading)\n\n\n' 170 | ].join('') 171 | , undefined 172 | , undefined 173 | , '**Contents**' 174 | ) 175 | 176 | check( 177 | [ '# H1h' 178 | , '## H2h' 179 | , '### H3h' 180 | , '' 181 | , 'Max. level test - hashed' 182 | ].join('\n') 183 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 184 | , '- [H1h](#h1h)\n' 185 | , ' - [H2h](#h2h)\n\n\n' 186 | ].join('') 187 | , undefined 188 | , 2 189 | ) 190 | 191 | check( 192 | [ 193 | 'H1u' 194 | , '===' 195 | , 'H2u' 196 | , '---' 197 | , '' 198 | , 'Max. level test - underlined' 199 | ].join('\n') 200 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 201 | , '- [H1u](#h1u)\n\n\n' 202 | ].join('') 203 | , undefined 204 | , 1 205 | ) 206 | 207 | check( 208 | [ 209 | '' 210 | , '

H1html

H2html

H3html

' 211 | , '' 212 | , 'Max. level test - HTML' 213 | ].join('\n') 214 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 215 | , '- [H1html](#h1html)\n' 216 | , ' - [H2html](#h2html)\n\n\n' 217 | ].join('') 218 | , undefined 219 | , 2 220 | ) 221 | 222 | 223 | test('transforming when old toc exists', function (t) { 224 | var md = [ 225 | '# Header above' 226 | , '' 227 | , 'The above header should be ignored since it is above the existing toc' 228 | , '' 229 | , '' 230 | , '' 231 | , '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*' 232 | , '' 233 | , '- [OldHeader](#oldheader)' 234 | , '' 235 | , '' 236 | , '## Header' 237 | , 'some content' 238 | , '' 239 | ].join('\n') 240 | 241 | var res = transform(md) 242 | 243 | t.ok(res.transformed, 'transforms it') 244 | 245 | t.deepEqual( 246 | res.toc.split('\n') 247 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 248 | '', 249 | '- [Header](#header)', 250 | '' ] 251 | , 'replaces old toc' 252 | ) 253 | 254 | t.deepEqual( 255 | res.wrappedToc.split('\n') 256 | , [ '', 257 | '', 258 | '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 259 | '', 260 | '- [Header](#header)', 261 | '', 262 | '' 263 | ] 264 | , 'wraps old toc' 265 | ) 266 | 267 | t.deepEqual( 268 | res.data.split('\n') 269 | , [ '# Header above', 270 | '', 271 | 'The above header should be ignored since it is above the existing toc', 272 | '', 273 | '', 274 | '', 275 | '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 276 | '', 277 | '- [Header](#header)', 278 | '', 279 | '', 280 | '## Header', 281 | 'some content', 282 | '' ] 283 | , 'updates the content with the new toc and ignores header before existing toc' 284 | ) 285 | t.end() 286 | }) 287 | 288 | test('transforming when old toc exists and --all flag is set', function (t) { 289 | var md = [ 290 | '# Header above' 291 | , '' 292 | , 'The above header should be ignored since it is above the existing toc' 293 | , '' 294 | , '' 295 | , '' 296 | , '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*' 297 | , '' 298 | , '- [OldHeader](#oldheader)' 299 | , '' 300 | , '' 301 | , '## Header' 302 | , 'some content' 303 | , '' 304 | ].join('\n') 305 | 306 | var res = transform(md, undefined, undefined, undefined, undefined, undefined, true) 307 | 308 | t.ok(res.transformed, 'transforms it') 309 | 310 | t.deepEqual( 311 | res.toc.split('\n') 312 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 313 | '', 314 | '- [Header above](#header-above)', 315 | ' - [Header](#header)', 316 | '' ] 317 | , 'replaces old toc' 318 | ) 319 | 320 | t.deepEqual( 321 | res.wrappedToc.split('\n') 322 | , [ '', 323 | '', 324 | '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 325 | '', 326 | '- [Header above](#header-above)', 327 | ' - [Header](#header)', 328 | '', 329 | '' 330 | ] 331 | , 'wraps old toc' 332 | ) 333 | 334 | t.deepEqual( 335 | res.data.split('\n') 336 | , [ '# Header above', 337 | '', 338 | 'The above header should be ignored since it is above the existing toc', 339 | '', 340 | '', 341 | '', 342 | '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', 343 | '', 344 | '- [Header above](#header-above)', 345 | ' - [Header](#header)', 346 | '', 347 | '', 348 | '## Header', 349 | 'some content', 350 | '' ] 351 | , 'updates the content with the new toc and ignores header before existing toc' 352 | ) 353 | t.end() 354 | }) 355 | 356 | 357 | // bitbucket.org 358 | check( 359 | [ '# My Module' 360 | , 'Some text here' 361 | , '## API' 362 | , '### Method One' 363 | , 'works like this' 364 | , '### Method Two' 365 | , '#### Main Usage' 366 | , 'some main usage here' 367 | ].join('\n') 368 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 369 | , '- [My Module](#markdown-header-my-module)\n' 370 | , ' - [API](#markdown-header-api)\n' 371 | , ' - [Method One](#markdown-header-method-one)\n' 372 | , ' - [Method Two](#markdown-header-method-two)\n' 373 | , ' - [Main Usage](#markdown-header-main-usage)\n\n\n' 374 | ].join('') 375 | , 'bitbucket.org' 376 | ) 377 | 378 | // gitlab (similar to bitbucket) both have 4-spaces indentation 379 | // however headers are note prefixed in bitbucket specific way 380 | check( 381 | [ '# My Module' 382 | , 'Some text here' 383 | , '## API' 384 | , '### Method One' 385 | , 'works like this' 386 | , '### Method Two' 387 | , '#### Main Usage' 388 | , 'some main usage here' 389 | ].join('\n') 390 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 391 | , '- [My Module](#my-module)\n' 392 | , ' - [API](#api)\n' 393 | , ' - [Method One](#method-one)\n' 394 | , ' - [Method Two](#method-two)\n' 395 | , ' - [Main Usage](#main-usage)\n\n\n' 396 | ].join('') 397 | 398 | , 'gitlab.com' 399 | ) 400 | 401 | // check the --entryprefix flag 402 | check( 403 | [ '# My Module' 404 | , 'Some text here' 405 | , '## API' 406 | , '### Method One' 407 | , 'works like this' 408 | , '### Method Two' 409 | , '#### Main Usage' 410 | , 'some main usage here' 411 | ].join('\n') 412 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 413 | , '* [My Module](#my-module)\n' 414 | , ' * [API](#api)\n' 415 | , ' * [Method One](#method-one)\n' 416 | , ' * [Method Two](#method-two)\n' 417 | , ' * [Main Usage](#main-usage)\n\n\n' 418 | ].join('') 419 | , undefined 420 | , undefined 421 | , undefined 422 | , undefined 423 | , '*' // pass '*' as the prefix for toc entries 424 | ) 425 | 426 | check( 427 | [ '# My Module' 428 | , 'Some text here' 429 | , '## API' 430 | , '### Method One' 431 | , 'works like this' 432 | , '### Method Two' 433 | , '#### Main Usage' 434 | , 'some main usage here' 435 | ].join('\n') 436 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 437 | , '>> [My Module](#my-module)\n' 438 | , ' >> [API](#api)\n' 439 | , ' >> [Method One](#method-one)\n' 440 | , ' >> [Method Two](#method-two)\n' 441 | , ' >> [Main Usage](#main-usage)\n\n\n' 442 | ].join('') 443 | , undefined 444 | , undefined 445 | , undefined 446 | , undefined 447 | , '>>' // pass '>>' as the prefix for toc entries) 448 | ) 449 | 450 | check( 451 | [ '# My Module' 452 | , 'Some text here' 453 | , '## API' 454 | , '### Method One' 455 | , 'works like this' 456 | , '### Method Two' 457 | , '#### Main Usage' 458 | , 'some main usage here' 459 | ].join('\n') 460 | , [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n' 461 | , '1. [My Module](#my-module)\n' 462 | , ' 1. [API](#api)\n' 463 | , ' 1. [Method One](#method-one)\n' 464 | , ' 1. [Method Two](#method-two)\n' 465 | , ' 1. [Main Usage](#main-usage)\n\n\n' 466 | ].join('') 467 | , undefined 468 | , undefined 469 | , undefined 470 | , undefined 471 | , '1.' // pass '1.' as the prefix for toc entries 472 | ) 473 | --------------------------------------------------------------------------------