diff --git a/.gitignore b/.gitignore index ab96a23..56ba81a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ .DS_Store node_modules npm-debug.log -localtest.html +/*.test.* diff --git a/.jshintignore b/.jshintignore index a76422d..54c5705 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,3 +1,5 @@ Gruntfile.js dist/**/*.js build/**/*.js +src/options.js +bin/* diff --git a/Gruntfile.js b/Gruntfile.js index 5b5eee7..10f0729 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -20,6 +20,7 @@ module.exports = function (grunt) { }, dist: { src: [ + 'src/options.js', 'src/showdown.js', 'src/helpers.js', 'src/converter.js', diff --git a/bin/showdown.js b/bin/showdown.js new file mode 100644 index 0000000..4bacdaa Binary files /dev/null and b/bin/showdown.js differ diff --git a/dist/showdown.js b/dist/showdown.js index 4e3bac8..c0b4286 100644 Binary files a/dist/showdown.js and b/dist/showdown.js differ diff --git a/dist/showdown.js.map b/dist/showdown.js.map index f7627e7..d3c0eb2 100644 Binary files a/dist/showdown.js.map and b/dist/showdown.js.map differ diff --git a/dist/showdown.min.js b/dist/showdown.min.js index 82dd554..c1bbb78 100644 Binary files a/dist/showdown.min.js and b/dist/showdown.min.js differ diff --git a/dist/showdown.min.js.map b/dist/showdown.min.js.map index c0b8fa1..549457e 100644 Binary files a/dist/showdown.min.js.map and b/dist/showdown.min.js.map differ diff --git a/package.json b/package.json index 71675e6..cf4093b 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "test": "grunt test" }, "bin": { - "showdown": "src/cli.js" + "showdown": "bin/showdown.js" }, "devDependencies": { "chai": "^1.10.0", @@ -53,5 +53,8 @@ "quiet-grunt": "^0.2.3", "sinon": "^1.14.1", "source-map-support": "^0.2.9" + }, + "dependencies": { + "yargs": "^3.15.0" } } diff --git a/src/cli.js b/src/cli.js deleted file mode 100644 index 0b78b77..0000000 --- a/src/cli.js +++ /dev/null @@ -1,23 +0,0 @@ -var Showdown = require('./showdown'); -var cli = require('cli'); -var fs = require('fs'); - -var converter = new Showdown.converter(); - -/* -If an argument is given, treat it as the file to be read. -Otherwise, read from stdin. -*/ -if(process.argv.length > 2) { - fs.readFile(process.argv[2], function(err, data) { - if(err) { - console.error("Error: Invalid file"); - } else { - console.log(converter.makeHtml(data.toString())); - } - }) -} else { - cli.withStdin(function(data) { - this.output(converter.makeHtml(data.toString())); - }) -} diff --git a/src/cli/cli.js b/src/cli/cli.js new file mode 100644 index 0000000..7cd8dc4 --- /dev/null +++ b/src/cli/cli.js @@ -0,0 +1,30 @@ +'use strict'; + +var version = require('../../package.json').version, + yargs = require('yargs'); + +yargs + .version(version, 'v') + .alias('v', 'version') + .option('h', { + alias: 'help', + description: 'Show help' + }) + .usage('Usage: showdown [options]') + .demand(1, 'You must provide a valid command') + .command('makehtml', 'Converts markdown into html') + .example('showdown makehtml -i foo.md -o bar.html', 'Converts \'foo.md\' to \'bar.html\'') + .wrap(yargs.terminalWidth()); + +var argv = yargs.argv, + command = argv._[0]; +if (command === 'makehtml') { + require('./makehtml.cmd.js').run(); +} else { + yargs.showHelp(); +} + +if (argv.help) { + yargs.showHelp(); +} +process.exit(0); diff --git a/src/cli/errorexit.js b/src/cli/errorexit.js new file mode 100644 index 0000000..ea25eff --- /dev/null +++ b/src/cli/errorexit.js @@ -0,0 +1,6 @@ +module.exports = exports = function errorExit(e) { + 'use strict'; + console.error('ERROR: ' + e.message); + console.error('Run \'showdown -h\' for help'); + process.exit(1); +}; diff --git a/src/cli/makehtml.cmd.js b/src/cli/makehtml.cmd.js new file mode 100644 index 0000000..acaf359 --- /dev/null +++ b/src/cli/makehtml.cmd.js @@ -0,0 +1,123 @@ +var yargs = require('yargs'), + fs = require('fs'), + errorExit = require('./errorexit.js'), + showdown = require('../../dist/showdown'); + +yargs.reset() + .usage('Usage: showdown makehtml [options]') + .example('showdown makehtml -i', 'Reads from stdin and outputs to stdout') + .example('showdown makehtml -i foo.md -o bar.html', 'Reads \'foo.md\' and writes to \'bar.html\'') + //.demand(['i']) + .option('i', { + alias : 'input', + describe: 'Input source. Usually a md file. If omitted or empty, reads from stdin', + type: 'string' + }) + .option('o', { + alias : 'output', + describe: 'Output target. Usually a html file. If omitted or empty, writes to stdout', + type: 'string', + default: false + }) + .option('u', { + alias : 'encoding', + describe: 'Input encoding', + type: 'string' + }) + .option('a', { + alias : 'append', + describe: 'Append data to output instead of overwriting', + type: 'string' + }) + .option('e', { + alias : 'extensions', + describe: 'Load the specified extensions. Should be valid paths to node compatible extensions', + type: 'array' + }) + .config('c') + .alias('c', 'config') + .help('h') + .alias('h', 'help'); + +yargs.options(showdown.getDefaultOptions(false)); +argv = yargs.argv; + +function run() { + 'use strict'; + var input = '', + enc = 'utf8', + output; + + if (argv.encoding) { + enc = argv.encoding; + } + + // to avoid passing extensions to converter + delete argv.extensions; + var converter = new showdown.Converter(argv); + + // Load extensions + if (argv.e) { + for (var i = 0; i < argv.e.length; ++i) { + loadExtension(argv.e[i], converter); + } + } + + if (!argv.i || argv.i === '') { + // 'i' is undefined or empty, read from stdin + try { + var size = fs.fstatSync(process.stdin.fd).size; + input = size > 0 ? fs.readSync(process.stdin.fd, size)[0] : ''; + } catch (e) { + var err = new Error('Could not read from stdin, reason: ' + e.message); + errorExit(err); + } + } else { + // 'i' has a value, read from file + try { + input = fs.readFileSync(argv.i, enc); + } catch (err) { + errorExit(err); + } + } + + // parse and convert file + output = converter.makeHtml(input); + + // Write output + if (!argv.o || argv.o === '') { + // o is undefined or empty, write to stdout + process.stdout.write(output); + // we won't print anything since it would conspurcate stdout and, + // consequently, the outputted file + } else { + // o is has a value, presumably a file, write to it. + + // If a flag is passed, it means we should append instead of overwriting. + // Only works with files, obviously + var write = (argv.a) ? fs.appendFileSync : fs.writeFileSync; + + try { + write(argv.o, output); + } catch (err) { + errorExit(err); + } + console.error('DONE!'); + } +} + +function loadExtension(path, converter) { + 'use strict'; + var ext; + try { + ext = require(path); + converter.addExtension(ext, path); + } catch (e) { + console.error('Could not load extension ' + path + '. Reason:'); + console.error(e.message); + } +} + +module.exports = exports = { + run: run +}; diff --git a/src/converter.js b/src/converter.js index 551aeb2..55ecb44 100644 --- a/src/converter.js +++ b/src/converter.js @@ -86,13 +86,16 @@ showdown.Converter = function (converterOptions) { /** * Parse extension * @param {*} ext + * @param {string} [name=''] * @private */ - function _parseExtension(ext) { + function _parseExtension(ext, name) { + name = name || null; // If it's a string, the extension was previously loaded if (showdown.helper.isString(ext)) { ext = showdown.helper.stdExtName(ext); + name = ext; // LEGACY_SUPPORT CODE if (showdown.extensions[ext]) { @@ -118,8 +121,9 @@ showdown.Converter = function (converterOptions) { ext = [ext]; } - if (!showdown.validateExtension(ext)) { - return; + var validExt = validate(ext, name); + if (!validExt.valid) { + throw Error(validExt.error); } for (var i = 0; i < ext.length; ++i) { @@ -272,9 +276,11 @@ showdown.Converter = function (converterOptions) { /** * Add extension to THIS converter * @param {{}} extension + * @param {string} [name=null] */ - this.addExtension = function (extension) { - _parseExtension(extension); + this.addExtension = function (extension, name) { + name = name || null; + _parseExtension(extension, name); }; /** diff --git a/src/options.js b/src/options.js new file mode 100644 index 0000000..0f11c72 --- /dev/null +++ b/src/options.js @@ -0,0 +1,80 @@ +/** + * Created by Tivie on 13-07-2015. + */ + +function getDefaultOpts(simple) { + 'use strict'; + + var defaultOptions = { + omitExtraWLInCodeBlocks: { + default: false, + describe: 'Omit the default extra whiteline added to code blocks', + type: 'boolean' + }, + noHeaderId: { + default: false, + describe: 'Turn on/off generated header id', + type: 'boolean' + }, + prefixHeaderId: { + default: false, + describe: 'Specify a prefix to generated header ids', + type: 'string' + }, + headerLevelStart: { + default: false, + describe: 'The header blocks level start', + type: 'integer' + }, + parseImgDimensions: { + default: false, + describe: 'Turn on/off image dimension parsing', + type: 'boolean' + }, + simplifiedAutoLink: { + default: false, + describe: 'Turn on/off GFM autolink style', + type: 'boolean' + }, + literalMidWordUnderscores: { + default: false, + describe: 'Parse midword underscores as literal underscores', + type: 'boolean' + }, + strikethrough: { + default: false, + describe: 'Turn on/off strikethrough support', + type: 'boolean' + }, + tables: { + default: false, + describe: 'Turn on/off tables support', + type: 'boolean' + }, + tablesHeaderId: { + default: false, + describe: 'Add an id to table headers', + type: 'boolean' + }, + ghCodeBlocks: { + default: true, + describe: 'Turn on/off GFM fenced code blocks support', + type: 'boolean' + }, + tasklists: { + default: false, + describe: 'Turn on/off GFM tasklist support', + type: 'boolean' + } + }; + if (simple === false) { + return JSON.parse(JSON.stringify(defaultOptions)); + } + var ret = {}; + for (var opt in defaultOptions) { + if (defaultOptions.hasOwnProperty(opt)) { + ret[opt] = defaultOptions[opt].default; + } + } + return ret; +} diff --git a/src/showdown.js b/src/showdown.js index 9d1880c..1ee2ee0 100644 --- a/src/showdown.js +++ b/src/showdown.js @@ -6,21 +6,7 @@ var showdown = {}, parsers = {}, extensions = {}, - defaultOptions = { - omitExtraWLInCodeBlocks: false, - prefixHeaderId: false, - noHeaderId: false, - headerLevelStart: 1, - parseImgDimensions: false, - simplifiedAutoLink: false, - literalMidWordUnderscores: false, - strikethrough: false, - tables: false, - tablesHeaderId: false, - ghCodeBlocks: true, // true due to historical reasons - tasklists: false - }, - globalOptions = JSON.parse(JSON.stringify(defaultOptions)), + globalOptions = getDefaultOpts(true), flavor = { github: { omitExtraWLInCodeBlocks: true, @@ -33,7 +19,7 @@ var showdown = {}, ghCodeBlocks: true, tasklists: true }, - vanilla: JSON.parse(JSON.stringify(defaultOptions)) + vanilla: getDefaultOpts(true) }; /** @@ -88,7 +74,7 @@ showdown.getOptions = function () { */ showdown.resetOptions = function () { 'use strict'; - globalOptions = JSON.parse(JSON.stringify(defaultOptions)); + globalOptions = getDefaultOpts(true); }; /** @@ -110,11 +96,12 @@ showdown.setFlavor = function (name) { /** * Get the default options * @static + * @param {boolean} [simple=true] * @returns {{}} */ -showdown.getDefaultOptions = function () { +showdown.getDefaultOptions = function (simple) { 'use strict'; - return defaultOptions; + return getDefaultOpts(simple); }; /** @@ -233,7 +220,7 @@ function validate(extension, name) { } for (var i = 0; i < extension.length; ++i) { - var baseMsg = errMsg + 'sub-extension ' + i + ': ', + var baseMsg = errMsg + ' sub-extension ' + i + ': ', ext = extension[i]; if (typeof ext !== 'object') { ret.valid = false; diff --git a/src/subParsers/headers.js b/src/subParsers/headers.js index a564d1c..811a493 100644 --- a/src/subParsers/headers.js +++ b/src/subParsers/headers.js @@ -1,7 +1,8 @@ showdown.subParser('headers', function (text, options, globals) { 'use strict'; - var prefixHeader = options.prefixHeaderId; + var prefixHeader = options.prefixHeaderId, + headerLevelStart = (isNaN(parseInt(options.headerLevelStart))) ? 1 : parseInt(options.headerLevelStart); // Set text-style headers: // Header 1 @@ -14,7 +15,7 @@ showdown.subParser('headers', function (text, options, globals) { var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', - hLevel = parseInt(options.headerLevelStart), + hLevel = headerLevelStart, hashBlock = '' + spanGamut + ''; return showdown.subParser('hashBlock')(hashBlock, options, globals); }); @@ -22,7 +23,7 @@ showdown.subParser('headers', function (text, options, globals) { text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, function (matchFound, m1) { var spanGamut = showdown.subParser('spanGamut')(m1, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m1) + '"', - hLevel = parseInt(options.headerLevelStart) + 1, + hLevel = headerLevelStart + 1, hashBlock = '' + spanGamut + ''; return showdown.subParser('hashBlock')(hashBlock, options, globals); }); @@ -49,7 +50,7 @@ showdown.subParser('headers', function (text, options, globals) { text = text.replace(/^(#{1,6})[ \t]*(.+?)[ \t]*#*\n+/gm, function (wholeMatch, m1, m2) { var span = showdown.subParser('spanGamut')(m2, options, globals), hID = (options.noHeaderId) ? '' : ' id="' + headerId(m2) + '"', - hLevel = parseInt(options.headerLevelStart) - 1 + m1.length, + hLevel = headerLevelStart - 1 + m1.length, header = '' + span + ''; return showdown.subParser('hashBlock')(header, options, globals); diff --git a/test/cli/basic.html b/test/cli/basic.html new file mode 100644 index 0000000..a014504 --- /dev/null +++ b/test/cli/basic.html @@ -0,0 +1,3 @@ +

some title

+ +

Test bold and italic

diff --git a/test/cli/basic.md b/test/cli/basic.md new file mode 100644 index 0000000..a5256ad --- /dev/null +++ b/test/cli/basic.md @@ -0,0 +1,3 @@ +# some title + +Test **bold** and _italic_ diff --git a/test/node/cli.js b/test/node/cli.js new file mode 100644 index 0000000..84bae88 --- /dev/null +++ b/test/node/cli.js @@ -0,0 +1,13 @@ +var execSync = require('child_process').execSync; + +describe('showdown cli', function () { + 'use strict'; + it('basic stdin stdout', function () { + var otp = execSync('showdown makehtml', { + encoding: 'utf8', + input: '**foo**' + }); + otp.trim().should.equal('

foo

'); + }); +}); + diff --git a/test/node/showdown.js b/test/node/showdown.js index db28ea7..3d89766 100644 --- a/test/node/showdown.js +++ b/test/node/showdown.js @@ -17,6 +17,8 @@ describe('showdown.options', function () { describe('getDefaultOptions()', function () { it('should get default options', function () { + var opts = require('../optionswp').getDefaultOpts(true); + /* var opts = { omitExtraWLInCodeBlocks: false, prefixHeaderId: false, @@ -31,6 +33,7 @@ describe('showdown.options', function () { ghCodeBlocks: true, tasklists: false }; + */ expect(showdown.getDefaultOptions()).to.be.eql(opts); }); }); diff --git a/test/optionswp.js b/test/optionswp.js new file mode 100644 index 0000000..dd20264 --- /dev/null +++ b/test/optionswp.js @@ -0,0 +1,9 @@ +/* jshint ignore:start */ +var fs = require('fs'), + filedata; +filedata = fs.readFileSync('src/options.js', 'utf8'); +eval(filedata); +module.exports = { + getDefaultOpts: getDefaultOpts +}; +/* jshint ignore:end */ diff --git a/test/single.test.wrapper.js b/test/single.test.wrapper.js deleted file mode 100644 index 7a6a418..0000000 --- a/test/single.test.wrapper.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by Estevao on 10-07-2015. - */ -/* -var showdown = require('../dist/showdown.js'), - bootstrap = require('./bootstrap.js'), - assertion = bootstrap.assertion, - testsuite = bootstrap.getTestSuite('test/features/'); - -describe('makeHtml() single test', function () { - 'use strict'; -}); -*/