diff --git a/.gitignore b/.gitignore index c2658d7..c07614a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ node_modules/ +output/* +!output/menu.html +!output/index.html + diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 0000000..a25cc12 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,3 @@ +node_modules/ +layouts/ +output/ diff --git a/Makefile b/Makefile index ee5f8a2..e1e7365 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ UNAME := $(shell uname) +GJSLINT := --nojsdoc --exclude_directories=node_modules,layouts,output --max_line_length=120 --disable=200,201,202,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,230,231,232,233,250,251,252 build: for name in `find ./layouts -mindepth 1 -maxdepth 1 -type d | sed -e 's/.\/layouts\///'` ; do \ @@ -34,3 +35,10 @@ get-fonts: node font-download.js .PHONY: build screenshots get-fonts + +lint: + fixjsstyle $(GJSLINT) -r . + gjslint $(GJSLINT) -r . + jshint . + +.PHONY: lint diff --git a/bin/generate-md b/bin/generate-md index 6440fe7..c4f2059 100755 --- a/bin/generate-md +++ b/bin/generate-md @@ -7,27 +7,29 @@ var fs = require('fs'), opts.options({ 'layouts': { }, + 'help': { }, 'layout': { }, 'input': { }, 'output': { }, - 'runner': { }, - 'command': { }, - 'asset-dir': { }, - 'partials': { }, - 'helpers': { }, - 'highlight': { } + 'v': { }, + 'version': { } }) - .boolean('layouts'); + .boolean('layouts').boolean('help').boolean('v').boolean('version'); var argv = opts.parse(process.argv); +if (argv.v || argv.version) { + console.log(require('../package.json').version); + process.exit(); +} + argv = resolveArgs(argv); var layoutDir = __dirname + '/../layouts/'; -if(argv.layouts || !fs.existsSync(argv.input)) { +if (argv.help || argv.layouts || !fs.existsSync(argv.input)) { if(!fs.existsSync(argv.input)) { - console.log(argv.input+' does not exist'); + console.log(argv.input + ' does not exist'); } // show layouts console.log('Available layouts:\n'+ @@ -40,7 +42,6 @@ if(argv.layouts || !fs.existsSync(argv.input)) { } console.log('Layout: ' + argv.layout); -console.log('Template file: ' + argv.template); console.log('Input: ' + argv.input); console.log('Output: ' + argv.output); diff --git a/font-download.js b/font-download.js index 215f2ab..8f2a40f 100644 --- a/font-download.js +++ b/font-download.js @@ -7,11 +7,11 @@ https.get({ path: '/repos/w0ng/googlefontdirectory/contents/fonts', headers: { 'user-agent': 'nodejs'} }, function(res) { - console.log("Got response: " + res.statusCode); + console.log('Got response: ' + res.statusCode); console.log(res.headers); var data = ''; res.setEncoding('utf8'); - res.on('data', function (chunk) { + res.on('data', function(chunk) { data += chunk; }); res.on('end', function() { @@ -23,12 +23,12 @@ https.get({ }); console.log('# Run the following commands to get the fonts: '); items.forEach(function(item) { - console.log('cd ~/.fonts && wget -N https://github.com/w0ng/googlefontdirectory/raw/master/'+item.path); + console.log('cd ~/.fonts && wget -N https://github.com/w0ng/googlefontdirectory/raw/master/' + item.path); }); - console.log('fc-cache -fv') + console.log('fc-cache -fv'); }); }); }).on('error', function(e) { - console.log("Got error: " + e.message); + console.log('Got error: ' + e.message); }); diff --git a/layouts/plain/page.html b/layouts/jasonm23-dark/page.html similarity index 100% rename from layouts/plain/page.html rename to layouts/jasonm23-dark/page.html diff --git a/layouts/jasonm23-foghorn/page.html b/layouts/jasonm23-foghorn/page.html new file mode 100644 index 0000000..fda2bba --- /dev/null +++ b/layouts/jasonm23-foghorn/page.html @@ -0,0 +1,12 @@ + + + + {{title}} + + + + + +{{~> content}} + + diff --git a/layouts/jasonm23-markdown/page.html b/layouts/jasonm23-markdown/page.html new file mode 100644 index 0000000..fda2bba --- /dev/null +++ b/layouts/jasonm23-markdown/page.html @@ -0,0 +1,12 @@ + + + + {{title}} + + + + + +{{~> content}} + + diff --git a/layouts/jasonm23-swiss/page.html b/layouts/jasonm23-swiss/page.html new file mode 100644 index 0000000..fda2bba --- /dev/null +++ b/layouts/jasonm23-swiss/page.html @@ -0,0 +1,12 @@ + + + + {{title}} + + + + + +{{~> content}} + + diff --git a/layouts/markedapp-byword/page.html b/layouts/markedapp-byword/page.html new file mode 100644 index 0000000..fda2bba --- /dev/null +++ b/layouts/markedapp-byword/page.html @@ -0,0 +1,12 @@ + + + + {{title}} + + + + + +{{~> content}} + + diff --git a/lib/apply-template.js b/lib/apply-template.js index 98ebb1e..d6168b4 100644 --- a/lib/apply-template.js +++ b/lib/apply-template.js @@ -29,14 +29,14 @@ function loadHelpers(dir) { module.exports = function(opts) { if (opts.partials && !Array.isArray(opts.partials)) { - opts.partials = [ opts.partials ]; + opts.partials = [opts.partials]; } if (!opts.partials) { opts.partials = []; } if (opts.helpers && !Array.isArray(opts.helpers)) { - opts.helpers = [ opts.helpers ]; + opts.helpers = [opts.helpers]; } if (!opts.helpers) { opts.helpers = []; @@ -53,7 +53,7 @@ module.exports = function(opts) { return pi.forEach(function(item, enc, done) { item.contents = item.contents.replace(/<(ul|ol)>/g, '<$1 class="list">') - .replace("{{>", "{{>") + .replace('{{>', '{{>') .replace(/
]*>([\s\S]*?)<\/code><\/pre>/mg, '
$1
') .replace(/

]*)>\s*<\/p>/g, '

'); diff --git a/lib/index.js b/lib/index.js index 210a5d7..82b82eb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,4 +2,4 @@ module.exports = { resolveArgs: require('./resolve-args'), render: require('./render'), pipeline: require('./pipeline') -} +}; diff --git a/lib/merge-meta.js b/lib/merge-meta.js index acdb3f6..ba68149 100644 --- a/lib/merge-meta.js +++ b/lib/merge-meta.js @@ -4,7 +4,7 @@ var pi = require('pipe-iterators'), module.exports = function(meta) { return pi.map(function(item) { - var item = xtend(item, meta[item.projectName] ? meta[item.projectName] : {}); + item = xtend(item, meta[item.projectName] ? meta[item.projectName] : {}); if (!item.title && item.headings && item.headings[0]) { item.title = item.headings[0].text; diff --git a/lib/pipeline.js b/lib/pipeline.js index ef1c83c..4a6ae77 100644 --- a/lib/pipeline.js +++ b/lib/pipeline.js @@ -16,8 +16,7 @@ module.exports = function(argv) { // map paths setOutputPath({ input: argv.input, - output: argv.output, - assetDir: argv.assetDir || argv.output + '/assets/' + output: argv.output }), // merge metadata now that projectName is set @@ -30,9 +29,9 @@ module.exports = function(argv) { // apply handlebars templates applyTemplate({ // read the template - template: fs.readFileSync(argv.template, 'utf8'), - partials: argv.partials || [], - helpers: argv.helpers || [] + template: fs.readFileSync(argv.layout + '/page.html', 'utf8'), + partials: fs.existsSync(argv.layout + '/partials') ? argv.layout + '/partials' : [], + helpers: fs.existsSync(argv.layout + '/helpers') ? argv.layout + '/helpers' : [] }) ]); }; diff --git a/lib/render.js b/lib/render.js index 1efe06d..6fbe9fc 100644 --- a/lib/render.js +++ b/lib/render.js @@ -6,8 +6,30 @@ var fs = require('fs'), stream = require('./stream'); module.exports = function(argv, onDone) { - glob.stream(fs.statSync(argv.input).isFile() ? argv.input : argv.input + '/**') - .pipe(pi.head([ + // --export + if (argv['export']) { + pi.fromArray( + glob.sync(path.normalize(argv['export']).replace(/\/$/, '') + '/**') + ).pipe(pi.filter(function(filename) { + var stat = fs.statSync(filename); + return stat.isFile(); + })) + .pipe(stream.copy(function(filename) { + var target = path.normalize(filename.replace(argv['export'], argv.output + '/')); + console.log('Copy layout file', filename, '=>', target); + return target; + })) + .pipe(pi.devnull().once('finish', function() { + if (onDone) { + onDone(); + } + })); + return; + } + + pi.fromArray( + glob.sync(fs.statSync(argv.input).isFile() ? argv.input : argv.input.replace(/\/$/, '') + '/**') + ).pipe(pi.head([ pi.filter(function(filename) { var stat = fs.statSync(filename); @@ -50,23 +72,20 @@ module.exports = function(argv, onDone) { // copy assets - if(argv.assetDir && fs.existsSync(argv.assetDir)) { - glob.stream(argv.assetDir + '/**') + var assetDir = path.normalize(argv.layout + '/assets').replace(/\/$/, ''); + if (fs.existsSync(assetDir)) { + pi.fromArray(glob.sync(assetDir + '/**')) .pipe(pi.filter(function(filename) { var stat = fs.statSync(filename); return stat.isFile(); })) .pipe(stream.copy(function(filename) { - var target = path.normalize(filename.replace(argv.assetDir, argv.output + '/assets/')); + var target = path.normalize(filename.replace(assetDir, argv.output + '/assets/')); console.log('Copy asset file', filename, '=>', target); return target; })) .pipe(pi.devnull()); } else { - if (argv.assetDir) { - console.log('Assets path does not exist: ' + argv.assetDir + ', so no assets were copied.'); - } else { - console.log('No asset directory was found, so no assets were copied.'); - } + console.log('Assets path does not exist: ' + assetDir + ', so no assets were copied.'); } }; diff --git a/lib/resolve-args.js b/lib/resolve-args.js index 12f9c89..db8cd49 100644 --- a/lib/resolve-args.js +++ b/lib/resolve-args.js @@ -7,26 +7,46 @@ module.exports = function(argv) { // defaults argv.input = path.resolve(process.cwd(), argv.input || './input/'); argv.output = path.resolve(process.cwd(), argv.output || './output/'); - if(!argv.layout) { - argv.layout = 'github'; + if (!argv.layout) { + if (argv['export']) { + argv.layout = argv['export']; + } else { + argv.layout = 'github'; + } } - // template is one of: - if(fs.existsSync(process.cwd() + '/' + argv.layout)) { - // 1) the supplied argument (normalized) - argv.template = path.normalize(process.cwd() + '/' + argv.layout); - } else if(fs.existsSync(layoutDir + argv.layout + '/page.html')) { - // 2) a preset layout from the layout dir - argv.template = path.normalize(layoutDir + argv.layout + '/page.html'); - } else { - // 3) the default layout - argv.template = path.normalize(layoutDir + 'plain/page.html'); + if (argv.template) { + throw new Error('--template is deprecated in v2.0, please point --layout to ' + + 'the layout directory with ./page.html in it.'); + } + if (argv['asset-dir'] || argv.assetDir) { + throw new Error('--asset-dir is deprecated in v2.0, please point --layout to ' + + 'the layout directory with ./page.html and ./assets in it.'); + } + if (argv.command) { + throw new Error('--command is deprecated in v2.0'); + } + if (argv.runner) { + throw new Error('--runner is deprecated in v2.0'); } - if(argv.layouts || !fs.existsSync(argv.input)) { - return argv; + // we only accept a single layout argument, --layout + if (fs.existsSync(argv.layout)) { + // 1) it can be an absolute path to a folder with ./page.html + argv.layout = path.normalize(argv.layout); + } else if (fs.existsSync(process.cwd() + '/' + argv.layout)) { + // 2) it can be a relative path + argv.layout = path.normalize(process.cwd() + '/' + argv.layout); + } else if (fs.existsSync(layoutDir + argv.layout + '/')) { + // 3c) it can be the name of a builtin layout + argv.layout = path.normalize(layoutDir + argv.layout + '/'); } + if (argv['export']) { + argv['export'] = argv.layout; + } + + // set up partials and helpers directories var layoutBase = path.dirname(argv.template); ['partials', 'helpers'].forEach(function(name) { if (argv[name]) { @@ -37,38 +57,21 @@ module.exports = function(argv) { } }); - if(argv['command']) { - argv['command'] = argv['command'].split(' '); - } - - // parse --highlight- var hl = {}; Object.keys(argv).forEach(function(name) { var matched = (typeof name === 'string' ? name.match(/highlight\-(.*)/) : false); - if(name == 'highlight') { - argv[name] = findModule(argv[name], [ process.cwd, __dirname ]); + if (name == 'highlight') { + argv[name] = findModule(argv[name], [process.cwd, __dirname]); hl['default'] = require(argv[name]); - } else if(matched) { + } else if (matched) { var ext = matched[1]; - argv[name] = findModule(argv[name], [ process.cwd, __dirname ]); + argv[name] = findModule(argv[name], [process.cwd, __dirname]); hl[ext] = require(argv[name]); } }); - argv['highlight'] = hl; - - // 1) lookup from the dir in which the template is - if(argv['asset-dir']) { - argv.assetDir = path.resolve(process.cwd(), argv['asset-dir']); - } else if (fs.existsSync(path.dirname(argv.template) + '/assets/')) { - argv.assetDir = path.normalize(path.dirname(argv.template) + '/assets/'); - } else if (argv.layout) { - // 2) try the builtin template dir - argv.assetDir = path.normalize(__dirname + '/../layouts/' + argv.layout + '/assets/'); - } - return argv; }; diff --git a/lib/set-output-path.js b/lib/set-output-path.js index 26a06fd..e249065 100644 --- a/lib/set-output-path.js +++ b/lib/set-output-path.js @@ -6,7 +6,7 @@ module.exports = function(opts) { var relative = item.path.replace(opts.input + '/', ''); var outputDir = path.normalize(path.dirname(item.path).replace(opts.input, opts.output + path.sep)); // path: full path to the output file - item.path = path.normalize(outputDir + path.sep + path.basename(item.path, '.md') + '.html' ); + item.path = path.normalize(outputDir + path.sep + path.basename(item.path, '.md') + '.html'); // projectName: either the file name, or the folder name of this file var isDirectory = (relative.indexOf('/') > -1); item.projectName = (isDirectory ? path.dirname(relative) : path.basename(relative, '.md')); diff --git a/lib/stream/copy.js b/lib/stream/copy.js index 3182795..a947bb8 100644 --- a/lib/stream/copy.js +++ b/lib/stream/copy.js @@ -19,8 +19,10 @@ module.exports = function(targetFn) { seen[copyDir] = true; fs.createReadStream(filename) .pipe(fs.createWriteStream(target)) - .once('finish', done) + .once('finish', function() { + done(); + }) .once('error', done); }); }); -} +}; diff --git a/lib/stream/write.js b/lib/stream/write.js index 734a9b3..9f8ba70 100644 --- a/lib/stream/write.js +++ b/lib/stream/write.js @@ -1,6 +1,6 @@ var path = require('path'), fs = require('fs'), - pi = require('pipe-iterators') + pi = require('pipe-iterators'), mkdirp = require('mkdirp'); function dest() { diff --git a/output/index.html b/output/index.html new file mode 100644 index 0000000..c1b1d53 --- /dev/null +++ b/output/index.html @@ -0,0 +1,17 @@ + + + +Markdown CSS + + + + + + + + + <p>Yeah, old school frames!</p> + + + + diff --git a/output/menu.html b/output/menu.html new file mode 100644 index 0000000..2cf60d2 --- /dev/null +++ b/output/menu.html @@ -0,0 +1,51 @@ + + +Menu + + + +

+ mixu-book + + mixu-page + + mixu-radar + + mixu-bootstrap + + mixu-bootstrap-2col + + mixu-gray + + github + + + jasonm23-markdown + + jasonm23-foghorn + + jasonm23-dark + + jasonm23-swiss + + markedapp-byword + + thomasf-solarizedcsslight + + thomasf-solarizedcssdark + + bootstrap3 +

+ + + diff --git a/package.json b/package.json index 45e0839..597e155 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "yargs": "~1.1.3" }, "devDependencies": { - "file-fixture": "0.0.2" + "file-fixture": "0.0.2", + "mds-csv": "0.0.0" } } diff --git a/test/api.test.js b/test/api.test.js index 15566a5..9f60a69 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -1,12 +1,109 @@ var fs = require('fs'), path = require('path'), + assert = require('assert'), fixture = require('file-fixture'), mds = require('../'); var layouts = fs.readdirSync(__dirname + '/../layouts'); +var layoutDir = path.normalize(__dirname + '/../layouts/'); +var customLayout; +var sampleInput; +describe('api tests', function() { + + before(function() { + customLayout = fixture.dir({ + 'assets/css/style.css': 'style', + 'partials/foo.hdbs': '

foo

', + 'helpers/bar.js': [ + 'module.exports = function(context, opts) {', + ' return "barHelper " + context;', + '};' + ], + 'page.html': '{{> foo}}{{bar "baz"}}{{~> content}}' + }); + sampleInput = fixture.dir({ + 'index.md': [ + 'title: Hello world', + 'author: Anonymous', + '----', + '# Test', + '```js', + 'var foo = "bar";', + '```' + ].join('\n') + }); + + }); + + it('--input defaults to process.cwd() + ./input', function() { + assert.equal(mds.resolveArgs({ }).input, process.cwd() + '/input'); + assert.equal(mds.resolveArgs({ input: 'foo' }).input, process.cwd() + '/foo'); + assert.equal(mds.resolveArgs({ input: './foo' }).input, process.cwd() + '/foo'); + }); + + it('--output defaults to process.cwd() + ./output', function() { + assert.equal(mds.resolveArgs({ }).output, process.cwd() + '/output'); + assert.equal(mds.resolveArgs({ output: 'foo' }).output, process.cwd() + '/foo'); + assert.equal(mds.resolveArgs({ output: './foo' }).output, process.cwd() + '/foo'); + }); + + it('--layout specifies the layout to use', function() { + assert.equal(mds.resolveArgs({ }).layout, layoutDir + 'github/'); + assert.equal(mds.resolveArgs({ layout: 'roryg-ghostwriter' }).layout, layoutDir + 'roryg-ghostwriter/'); + }); + + it('loads page.html, assets, partials and helpers given --layout', function(done) { + var out = fixture.dirname(); + sampleInput = fixture.dir({ + 'index.md': [ + 'title: Hello world', + 'author: Anonymous', + '----', + '# Test', + '```js', + 'var foo = "bar";', + '```' + ].join('\n') + }); + mds.render(mds.resolveArgs({ + input: sampleInput, + output: out, + layout: customLayout + }), function() { + // check that the helper ran + // check that the partial ran + // check that the assets were copied + // check that the output is as expected + console.log('custom layout', customLayout, out); + done(); + }); + }); + + it('--export exports the layout to a directory', function(done) { + var out = fixture.dirname(); + mds.render(mds.resolveArgs({ + 'export': 'github', + output: out + }), function() { + // check that the assets, helpers and partials were copied + done(); + }); + }); + + it('--highlight-lang module enables a highlighter', function(done) { + var out = fixture.dirname(); + mds.render(mds.resolveArgs({ + input: sampleInput, + output: out, + layout: customLayout + }), function() { + // check that the custom js highlighter ran + console.log('--highlight-lang', customLayout, out); + done(); + }); + }); -/* it('has a render(opts, onDone) function that works like bin/generate-md', function(done) { // params: @@ -17,7 +114,7 @@ var layouts = fs.readdirSync(__dirname + '/../layouts'); // helpers // layout // assetDir - + done(); }); it('has a pipeline(opts) function that returns a pipeline that accepts items', function(done) { @@ -28,11 +125,52 @@ var layouts = fs.readdirSync(__dirname + '/../layouts'); // template // partials // helpers - + done(); }); -*/ +/* + it('supports custom syntax highlighters for specific languages', function(done) { + var dir = fixture.dir({ + 'foo.md': [ + '# foo', + '', + '```csv', + 'abc,def,ghi', + '```' + ].join('\n') + }); + var out = fixture.dirname(); + + mds.render({ + input: dir, + output: out, + template: templateDir + '/page.html' + }, function() { + assert.equal(fs.readFileSync(out + '/foo.html', 'utf8'), [ + '"Some title" by ', + '

Some title

', + '

abcdef

\n' + ].join('\n')); + done(); + }); + + +function(code, lang) { + if (lang === 'csv') { + return mdsCsv(code, lang); + } + return false; + } + + done(); + }); + + }); +*/ +}); + +/* describe('can render every sample layout', function() { var dir; @@ -60,13 +198,6 @@ describe('can render every sample layout', function() { layout: layout }), function() { // console.log(layout, out); - /* - assert.equal(fs.readFileSync(out + '/index.html', 'utf8'), [ - '"Hello world" by Anonymous', - '

Test

', - '

abcdef

\n' - ].join('\n')); - */ done(); }); } @@ -76,3 +207,4 @@ describe('can render every sample layout', function() { }); }); +*/ diff --git a/test/integration.test.js b/test/integration.test.js index 28d94c4..bd3e82b 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -66,6 +66,8 @@ describe('integration tests', function() { }); }); + it('If there is no first heading, the file name is used (without the file extension)'); + it('reads and scopes the meta.json based on the path relative to target directory', function(done) { var dir = fixture.dir({ 'meta.json': JSON.stringify({ @@ -74,7 +76,7 @@ describe('integration tests', function() { }), 'foo.md': 'foo', // projectName foo 'foo/bar.md': 'bar', // projectName foo - 'abc/bar/baz.md': 'baz', // projectName abc/bar + 'abc/bar/baz.md': 'baz' // projectName abc/bar }); var templateDir = fixture.dir({ @@ -229,17 +231,10 @@ describe('integration tests', function() { 'a

a

', '

foo

', '
' +
-          'var foo = bar;
b', + 'var foo = bar;
b' ].join('\n')); done(); }); }); - - it('supports custom syntax highlighters for specific languages', function(done) { - done(); - }); - }); - - }); diff --git a/test/set-output-path.test.js b/test/set-output-path.test.js index a8e0b79..788f081 100644 --- a/test/set-output-path.test.js +++ b/test/set-output-path.test.js @@ -5,7 +5,7 @@ var assert = require('assert'), describe('set output path', function() { it('can set a basic output path', function(done) { - pi.fromArray([ { path: '/input/bar.md' }, { path: '/input/baz.md' } ]) + pi.fromArray([{ path: '/input/bar.md' }, { path: '/input/baz.md' }]) .pipe(setOutputPath({ input: '/input', output: '/output',