From 99a9ab8b86b3a6ef965d97cca55ec82dfaad694f Mon Sep 17 00:00:00 2001 From: Brian Terlson Date: Thu, 27 Aug 2015 15:13:06 -0700 Subject: [PATCH] Add --watch option --- bin/args.js | 1 + bin/ecmarkup.js | 72 ++++++++++++++++++++++++++++++++++++++----------- lib/Import.js | 1 + lib/Spec.js | 11 ++++---- lib/Xref.js | 14 +++++----- lib/utils.js | 11 ++++++++ package.json | 6 +++-- spec/index.html | 18 +++++++++---- 8 files changed, 100 insertions(+), 34 deletions(-) diff --git a/bin/args.js b/bin/args.js index ab3a1bd4..38a3105d 100644 --- a/bin/args.js +++ b/bin/args.js @@ -6,6 +6,7 @@ module.exports = require('nomnom') .help('Compile ecmarkup documents to html by passing your input file and output file.') .options({ help: { abbr: 'h', flag: true, help: 'Display this help message' }, + watch: { abbr: 'w', flag: true, help: 'Rebuild when files change' }, biblio: { abbr: 'b', metavar: 'FILE', help: 'Write a biblio file to FILE' }, css: { metavar: 'FILE', help: 'Write Emu CSS dependencies to FILE' }, js: { metavar: 'FILE', help: 'Write Emu JS dependencies to FILE' }, diff --git a/bin/ecmarkup.js b/bin/ecmarkup.js index b211c094..00a5ea1f 100644 --- a/bin/ecmarkup.js +++ b/bin/ecmarkup.js @@ -9,27 +9,69 @@ const Promise = require('bluebird'); const Path = require('path'); const fs = require('fs'); const readFile = Promise.promisify(fs.readFile); +const utils = require('../lib/utils'); +const debounce = require('promise-debounce'); function fetch(path) { return readFile(path, 'utf8'); } -ecmarkup.build(args.infile, fetch, args).then(function (spec) { - if (args.biblio) { - fs.writeFileSync(args.biblio, JSON.stringify(spec.exportBiblio()), 'utf8'); - } +const build = debounce(function build() { + return ecmarkup.build(args.infile, fetch, args).then(function (spec) { + if (args.biblio) { + if (args.verbose) { + utils.logVerbose('Writing biblio file to ' + args.biblio); + } + fs.writeFileSync(args.biblio, JSON.stringify(spec.exportBiblio()), 'utf8'); + } - if (args.outfile) { - fs.writeFileSync(args.outfile, spec.toHTML(), 'utf8'); - } else { - process.stdout.write(spec.toHTML()); - } + if (args.outfile) { + if (args.verbose) { + utils.logVerbose('Writing output to ' + args.outfile); + } + fs.writeFileSync(args.outfile, spec.toHTML(), 'utf8'); + } else { + process.stdout.write(spec.toHTML()); + } - if (args.css) { - fs.writeFileSync(args.css, fs.readFileSync(Path.join(__dirname, '../css/elements.css'))); - } + if (args.css) { + if (args.verbose) { + utils.logVerbose('Writing css file to ' + args.css); + } + fs.writeFileSync(args.css, fs.readFileSync(Path.join(__dirname, '../css/elements.css'))); + } - if (args.js) { - fs.writeFileSync(args.js, fs.readFileSync(Path.join(__dirname, '../js/menu.js'))); - } + if (args.js) { + if (args.verbose) { + utils.logVerbose('Writing css file to ' + args.css); + } + fs.writeFileSync(args.js, fs.readFileSync(Path.join(__dirname, '../js/menu.js'))); + } + + return spec; + }); }); + +if (args.watch) { + let watching = {}; + build().then(function(spec) { + let toWatch = spec.imports.concat(args.infile); + + // remove any files that we're no longer watching + Object.keys(watching).forEach(function(file) { + if (toWatch.indexOf(file) === -1) { + watching[file].close(); + delete watching[file]; + } + }); + + // watch any new files + toWatch.forEach(function(file) { + if (!watching[file]) { + watching[file] = fs.watch(file, build); + } + }); + }); +} else { + build(); +} diff --git a/lib/Import.js b/lib/Import.js index c4dcf870..d6b44ab9 100644 --- a/lib/Import.js +++ b/lib/Import.js @@ -8,6 +8,7 @@ module.exports = class Import extends Builder { build(rootDir) { const href = this.node.getAttribute('href'); const importPath = Path.join(rootDir || this.spec.rootDir, href); + this.spec.imports.push(importPath); return this.spec.fetch(importPath) .then(utils.htmlToDoc) diff --git a/lib/Spec.js b/lib/Spec.js index 3b92e4bf..3b81759a 100644 --- a/lib/Spec.js +++ b/lib/Spec.js @@ -38,6 +38,7 @@ module.exports = class Spec { this.doc = doc; this.fetch = fetch; this.subclauses = []; + this.imports = []; this._numberer = ClauseNumbers.iterator(); this._figureCounts = { table: 0, @@ -77,10 +78,10 @@ module.exports = class Spec { buildAll(selector, Builder, opts) { opts = opts || {}; - this._log('Building ' + selector + '...'); let elems; if (typeof selector === 'string') { + this._log('Building ' + selector + '...'); elems = this.doc.querySelectorAll(selector); } else { elems = selector; @@ -157,7 +158,7 @@ module.exports = class Spec { try { data = yaml.safeLoad(block.textContent); } catch (e) { - console.log('Warning: metadata block failed to parse'); + utils.logWarning('metadata block failed to parse'); return; } finally { block.parentNode.removeChild(block); @@ -194,7 +195,7 @@ module.exports = class Spec { exportBiblio() { if (!this.opts.location) { - console.log('Warning: no spec location specified. Biblio not generated. Try --location or setting the location in the document\'s metadata block.'); + utils.logWarning('no spec location specified. Biblio not generated. Try --location or setting the location in the document\'s metadata block.'); return {}; } @@ -277,9 +278,9 @@ module.exports = class Spec { return null; } - _log() { + _log(str) { if (!this.opts.verbose) return; - console.log.apply(console, arguments); + utils.logVerbose(str); } }; diff --git a/lib/Xref.js b/lib/Xref.js index 8313d1e4..0311cfbe 100644 --- a/lib/Xref.js +++ b/lib/Xref.js @@ -9,18 +9,18 @@ module.exports = class Xref extends Builder { const aoid = xref.getAttribute('aoid'); if (href && aoid) { - console.log('Warning: xref can\'t have both href and aoid.'); + utils.logWarning('xref can\'t have both href and aoid.'); return; } if (!href && !aoid) { - console.log('Warning: xref has no href or aoid.'); + utils.logWarning('xref has no href or aoid.'); return; } if (href) { if (href[0] !== '#') { - console.log('Warning: xref to anything other than a fragment id is not supported (is ' + href + '). Try href="#sec-id" instead.'); + utils.logWarning('xref to anything other than a fragment id is not supported (is ' + href + '). Try href="#sec-id" instead.'); return; } @@ -28,7 +28,7 @@ module.exports = class Xref extends Builder { const entry = this.spec.lookupBiblioEntryById(id); if (!entry) { - console.log('Warning: can\'t find clause, production, note or example with id ' + href); + utils.logWarning('can\'t find clause, production, note or example with id ' + href); return; } @@ -52,7 +52,7 @@ module.exports = class Xref extends Builder { buildFigureLink(this.spec, xref, entry.entry, 'Figure'); break; default: - console.log('Warning: found unknown biblio entry (this is a bug, please file it)'); + utils.logWarning('found unknown biblio entry (this is a bug, please file it)'); } } else if (aoid) { const entry = this.spec.biblio.ops[aoid] || this.spec.externalBiblio.ops[aoid]; @@ -62,7 +62,7 @@ module.exports = class Xref extends Builder { return; } - console.log('Warning: can\'t find abstract op with aoid ' + aoid); + utils.logWarning('can\'t find abstract op with aoid ' + aoid); } } @@ -102,7 +102,7 @@ function buildFigureLink(spec, xref, entry, type) { // first need to find the associated clause const clauseEntry = spec.lookupBiblioEntryById(entry.clauseId); if (clauseEntry.type !== 'clause') { - console.log('Warning: could not find parent clause for ' + type + ' id ' + entry.id); + utils.logWarning('could not find parent clause for ' + type + ' id ' + entry.id); } const parentClause = utils.parent(xref, ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']); diff --git a/lib/utils.js b/lib/utils.js index 76b4ad78..12a24678 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -2,6 +2,7 @@ const jsdom = require('jsdom'); const Promise = require('bluebird'); +const chalk = require('chalk'); exports.htmlToDoc = function (html) { return new Promise(function (res, rej) { @@ -56,3 +57,13 @@ exports.parent = function parent(node, types) { return parent(node.parentNode, types); }; + +exports.logVerbose = function(str) { + let dateString = (new Date()).toISOString(); + console.log(chalk.gray('[' + dateString + '] ') + str); +}; + +exports.logWarning = function(str) { + let dateString = (new Date()).toISOString(); + console.log(chalk.gray('[' + dateString + '] ') + chalk.red('Warning: ' + str)); +}; diff --git a/package.json b/package.json index 5af6e6cd..6cc8fb99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecmarkup", - "version": "2.0.3", + "version": "2.1.0", "description": "Custom element definitions and core utilities for markup that specifies ECMAScript and related technologies.", "main": "lib/ecmarkup.js", "scripts": { @@ -33,13 +33,15 @@ "homepage": "https://github.com/bterlson/ecmarkup", "dependencies": { "bluebird": "^2.6.4", + "chalk": "^1.1.1", "ecmarkdown": "^3.0.1", "grammarkdown": "^0.1.3", "highlight.js": "^8.6.0", "html-escape": "^1.0.2", "js-yaml": "^3.3.1", "jsdom": "^5.0.1", - "nomnom": "^1.8.1" + "nomnom": "^1.8.1", + "promise-debounce": "^1.0.1" }, "devDependencies": { "diff": "^1.4.0", diff --git a/spec/index.html b/spec/index.html index b2b026d9..9073bb35 100644 --- a/spec/index.html +++ b/spec/index.html @@ -35,6 +35,18 @@

Getting Started

ecmarkup spec.html out.html +

Useful Options

+ + + + + + + + + +
OptionDescription
watchRebuild when files change
verbosePrint verbose logging info
biblioEmit a biblio file to the specified location
cssEmit the Ecmarkup CSS file to the specified location
jsEmit the Ecmarkup JS file to the specified location
tocEmit table of contents. Boolean, default true.
old-tocEmit the old style of table of contents. Boolean, default false.
+

Stylesheet

Styles are provided by `elements.css` under the `css` directory. The --css command line option will emit the CSS file to a location of your choosing. You may also link to https://bterlson.github.io/ecmarkup/elements.css which will contain the latest styles for the forseeable future (though I don't promise this won't break you if you're not tracking the latest version).

@@ -42,11 +54,7 @@

Stylesheet

Metadata

There are a number of settings that allow customizations or enable generation of boilerplate. Metadata can be included in the document and passed on the command line, for example `--no-toc --title "Document 1"`. Metadata given on the command line takes precedence.

To add metadata to your document, use yaml syntax inside a `<pre class=metadata>` element somewhere in the root of your document.

-

The following table lists the currently supported metadata:

- - - -
OptionDescription
tocEmit table of contents. Boolean, default true.
+

All of the command line options can be provided in the metadata block. See the table above for a list (or consult `--help`).

Example document with metadata


 <pre class=metadata>