diff --git a/README.md b/README.md index 335719c..c35e0e9 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ by github or other sites via a command line flag. - [Specifying a custom TOC title](#specifying-a-custom-toc-title) - [Specifying a minimum heading level for TOC entries](#specifying-a-minimum-heading-level-for-toc-entries) - [Specifying a maximum heading level for TOC entries](#specifying-a-maximum-heading-level-for-toc-entries) + - [Performing a dry run](#performing-a-dry-run) - [Printing to stdout](#printing-to-stdout) - [Only update existing ToC](#only-update-existing-toc) - [Usage as a `git` hook](#usage-as-a-git-hook) @@ -140,6 +141,11 @@ By default, - no limit is placed on Markdown-formatted headings, - whereas headings from embedded HTML are limited to 4 levels. +### Performing a dry run + +Use the `--dryrun` option to not write changes to files but instead return an exit code of 1 to indicates files are out of date and should be updated. +This is useful CI environments where you want to check if your docs are up to date as part of your build process. + ### Printing to stdout You can print to stdout by using the `-s` or `--stdout` option. diff --git a/doctoc.js b/doctoc.js index c3a85f8..d5247ec 100755 --- a/doctoc.js +++ b/doctoc.js @@ -15,7 +15,7 @@ function cleanPath(path) { return homeExpanded; } -function transformAndSave(files, mode, maxHeaderLevel, minHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly) { +function transformAndSave(files, mode, maxHeaderLevel, minHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly, dryRun) { if (processAll) { console.log('--all flag is enabled. Including headers before the TOC location.') } @@ -44,17 +44,28 @@ function transformAndSave(files, mode, maxHeaderLevel, minHeaderLevel, title, no } unchanged.forEach(function (x) { - console.log('"%s" is up to date', x.path); + if (stdOut) { + console.log('==================\n\n"%s" is up to date', x.path) + } + else { + console.log('"%s" is up to date', x.path); + } }); changed.forEach(function (x) { if (stdOut) { console.log('==================\n\n"%s" should be updated', x.path) - } else { + } else if (dryRun) { + console.log('"%s" should be updated but wasn\'t due to dry run.', x.path); + } + else { console.log('"%s" will be updated', x.path); fs.writeFileSync(x.path, x.data, 'utf8'); } }); + if (dryRun && changed.length > 0) { + process.exitCode = 1; + } } function printUsageAndExit(isErr) { @@ -82,7 +93,7 @@ var modes = { var mode = modes['github']; var argv = minimist(process.argv.slice(2) - , { boolean: [ 'h', 'help', 'T', 'notitle', 's', 'stdout', 'all' , 'u', 'update-only'].concat(Object.keys(modes)) + , { boolean: [ 'h', 'help', 'T', 'notitle', 's', 'stdout', 'all' , 'u', 'update-only', 'd', 'dryrun'].concat(Object.keys(modes)) , string: [ 'title', 't', 'maxlevel', 'm', 'minlevel', 'n', 'entryprefix' ] , unknown: function(a) { return (a[0] == '-' ? (console.error('Unknown option(s): ' + a), printUsageAndExit(true)) : true); } }); @@ -103,6 +114,7 @@ var entryPrefix = argv.entryprefix || '-'; var processAll = argv.all; var stdOut = argv.s || argv.stdout var updateOnly = argv.u || argv['update-only'] +var dryRun = argv.d || argv.dryrun || false; var maxHeaderLevel = argv.m || argv.maxlevel; if (maxHeaderLevel && isNaN(maxHeaderLevel)) { console.error('Max. heading level specified is not a number: ' + maxHeaderLevel), printUsageAndExit(true); } @@ -126,9 +138,14 @@ for (var i = 0; i < argv._.length; i++) { files = [{ path: target }]; } - transformAndSave(files, mode, maxHeaderLevel, minHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly); + transformAndSave(files, mode, maxHeaderLevel, minHeaderLevel, title, notitle, entryPrefix, processAll, stdOut, updateOnly, dryRun); - console.log('\nEverything is OK.'); + if (dryRun && process.exitCode === 1) { + console.log('\nDocumentation tables of contents are out of date.'); + } + else { + console.log('\nEverything is OK.'); + } } module.exports.transform = transform; diff --git a/lib/transform.js b/lib/transform.js index 3a29f60..e2fbd05 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -164,10 +164,11 @@ exports = module.exports = function transform(content, mode, maxHeaderLevel, min var wrappedToc = start + '\n' + toc + '\n' + end; - if (currentToc === toc) return { transformed: false }; - - var data = updateSection(lines.join('\n'), wrappedToc, matchesStart, matchesEnd, true); - return { transformed : true, data : data, toc: toc, wrappedToc: wrappedToc }; + var data; + if (currentToc != wrappedToc){ + data = updateSection(lines.join('\n'), wrappedToc, matchesStart, matchesEnd, true); + } + return { transformed : currentToc != wrappedToc, data : data, toc: toc, wrappedToc: wrappedToc }; }; exports.start = start; diff --git a/test/fixtures/readme-with-out_of_date_toc.md b/test/fixtures/readme-with-out_of_date_toc.md new file mode 100644 index 0000000..bf140c7 --- /dev/null +++ b/test/fixtures/readme-with-out_of_date_toc.md @@ -0,0 +1,19 @@ +# Hello, world! + +README to test doctoc with user-specified titles. + + + +## Table of Contents + +- [Installation](#installation) +- [API](#api) +- [License](#license) + + + + +## Installation +## API +## Contributing +## License diff --git a/test/fixtures/stdout.md b/test/fixtures/stdout.md index 884a74c..98f60d0 100644 --- a/test/fixtures/stdout.md +++ b/test/fixtures/stdout.md @@ -11,6 +11,6 @@ DocToccing single file "test/fixtures/readme-with-custom-title.md" for github.co ================== -"test/fixtures/readme-with-custom-title.md" should be updated +"test/fixtures/readme-with-custom-title.md" is up to date Everything is OK. diff --git a/test/transform-dryrun.js b/test/transform-dryrun.js new file mode 100644 index 0000000..40f089c --- /dev/null +++ b/test/transform-dryrun.js @@ -0,0 +1,55 @@ +'use strict'; +/*jshint asi: true */ + +var test = require('tap').test, + fs = require('fs'), + exec = require("child_process").exec; + +test('\nshould exit with a error code due to --dryrun option', function (t) { + + exec('node doctoc.js test/fixtures/readme-with-out_of_date_toc.md --dryrun', function (error, stdout, stderr) { + if (error) { + t.deepEqual(error.code, 1, 'process exited with error code 1 as expected'); + t.end('process did have an error'); + } else { + t.fail('process did not produce an error: ' + error); + t.end(); + } + }) +}) + +test('\nshould exit with no error code with --dryrun option', function (t) { + + exec('node doctoc.js test/fixtures/readme-with-custom-title.md --dryrun', function (error, stdout, stderr) { + if (error) { + t.fail('process produced an unexpected error: ' + error); + t.end() + } else { + t.end('process did not have an error'); + } + }) +}) + +test('\nshould exit no error code due to no --dryrun option for out of date toc', function (t) { + + exec('node doctoc.js test/fixtures/readme-with-out_of_date_toc.md --stdout', function (error, stdout, stderr) { + if (error) { + t.fail('process produced an unexpected error: ' + error); + t.end() + } else { + t.end('process did not have an error'); + } + }) +}) + +test('\nshould exit with no error code', function (t) { + + exec('node doctoc.js test/fixtures/readme-with-custom-title.md', function (error, stdout, stderr) { + if (error) { + t.fail('process produced an unexpected error: ' + error); + t.end() + } else { + t.end('process did not have an error'); + } + }) +})