├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── lib ├── compile.js ├── file.js ├── help.js ├── stdio.js ├── validate.js ├── version-string.js └── watch.js ├── package.json ├── template ├── style.css └── template.html └── test ├── fixture ├── basic.md ├── firstHeaderIsATitle.md ├── script.js ├── style.css └── template.html ├── test.html ├── test.js └── test.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-html [![NPM version](https://badge.fury.io/js/markdown-html.svg)](http://badge.fury.io/js/markdown-html) [![Build Status](https://travis-ci.org/pwlmaciejewski/markdown-html.svg)](https://travis-ci.org/pwlmaciejewski/markdown-html) 2 | 3 | ## Information 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Packagemarkdown-html
DescriptionCommand line markdown to html conversion. You can supply your own template (mustache), styles and scripts.
Node Version>= 0.10
18 | 19 | ## Usage 20 | 21 | Basic usage: 22 | 23 | markdown-html in.md -o out.html 24 | 25 | 26 | Use in watch mode for maximum pleasure: 27 | 28 | markdown-html -w in.md -o out.html 29 | 30 | 31 | List of options: 32 | 33 | --title, -t Generated page title 34 | --style, -s Path to custom stylesheet 35 | --script, -j Path to custom javascript 36 | --template, -l Path to custom mustache template 37 | --help, -h This screen 38 | --output-file, -o Path to output file (stdout if not specified) 39 | --stdin, -i If set, stdin will be used instead of file 40 | --watch, -w Watch mode 41 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var templateDir = __dirname + '/template'; 5 | var optimist = require('optimist') 6 | .alias({ 7 | 't': 'title', 8 | 'l': 'template', 9 | 's': 'style', 10 | 'j': 'script', 11 | 'h': 'help', 12 | 'o': 'output-file', 13 | 'i': 'stdin', 14 | 'w': 'watch', 15 | 'v': 'version' 16 | }) 17 | .describe({ 18 | 'title': 'Generated page title', 19 | 'style': 'Path to custom stylesheet', 20 | 'script': 'Path to custom javascript', 21 | 'template': 'Path to custom mustache template', 22 | 'help': 'This screen', 23 | 'output-file': 'Path to output file (stdout if not specified)', 24 | 'stdin': 'If set, stdin will be used instead of file', 25 | 'watch': 'Watch mode' 26 | }) 27 | .boolean('watch') 28 | .boolean('version') 29 | .default({ 30 | 'style': path.resolve(templateDir + '/style.css'), 31 | 'template': path.resolve(templateDir + '/template.html') 32 | }); 33 | 34 | var argv = optimist.argv; 35 | if (argv.v) { 36 | console.log(require('./lib/version-string')()); 37 | } else if (argv.h) { 38 | console.log(require('./lib/help')(optimist)); 39 | } else if (argv.i) { 40 | require('./lib/stdio')(argv); 41 | } else if (argv.w) { 42 | require('./lib/watch')(argv); 43 | } else { 44 | require('./lib/file')(argv); 45 | } -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | var marked = require('marked'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var mustache = require('mu2'); 5 | 6 | module.exports = function (argv, md) { 7 | var tokens = marked.lexer(md); 8 | 9 | // Set title. 10 | var title = path.basename(argv._[0], path.extname(argv._[0])); 11 | for (var i = 0; i < tokens.length; i += 1) { 12 | if (tokens[i].type === 'heading') { 13 | title = tokens[i].text; 14 | break; 15 | } 16 | } 17 | if (argv.title) { 18 | title = argv.title; 19 | } 20 | 21 | content = marked.parser(tokens); 22 | 23 | // Load style. 24 | var style = fs.readFileSync(argv.style); 25 | 26 | // Load script 27 | var script = argv.script ? fs.readFileSync(argv.script) : ''; 28 | 29 | // Output 30 | var out = process.stdout; 31 | if (argv['output-file']) { 32 | out = fs.createWriteStream(path.resolve(argv['output-file'])); 33 | } 34 | 35 | // Compile template and pipe it out. 36 | mustache.compileAndRender(argv.template, { 37 | content: content, 38 | style: style, 39 | title: title, 40 | script: script 41 | }).pipe(out); 42 | }; -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var compile = require('./compile'); 3 | 4 | module.exports = function (argv) { 5 | require('../lib/validate')(argv); 6 | if (!argv._[0]) { 7 | return; 8 | } 9 | compile(argv, fs.readFileSync(argv._[0], 'utf-8')); 10 | } -------------------------------------------------------------------------------- /lib/help.js: -------------------------------------------------------------------------------- 1 | module.exports = function (optimist) { 2 | return require('./version-string.js')() + '\n\n' + optimist.help(); 3 | }; -------------------------------------------------------------------------------- /lib/stdio.js: -------------------------------------------------------------------------------- 1 | var compile = require('./compile'); 2 | 3 | module.exports = function (argv) { 4 | require('../lib/validate')(argv); 5 | process.stdin.resume(); 6 | process.stdin.setEncoding('utf8'); 7 | process.stdin.on('data', function (chunk) { 8 | compile(argv, chunk); 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function (argv) { 4 | if (!fs.existsSync(argv.template)) { 5 | throw new Error('Template does not exist.'); 6 | } 7 | if (!fs.existsSync(argv.style)) { 8 | throw new Error('Style does not exist.'); 9 | } 10 | if (argv.script && !fs.existsSync(argv.script)) { 11 | throw new Error('Script does not exist.'); 12 | } 13 | }; -------------------------------------------------------------------------------- /lib/version-string.js: -------------------------------------------------------------------------------- 1 | var pkg = require('../package'); 2 | module.exports = function () { 3 | return pkg.name + ' v' + pkg.version; 4 | } -------------------------------------------------------------------------------- /lib/watch.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | module.exports = function (argv) { 4 | require('./file')(argv); 5 | fs.watchFile(argv._[0], { interval: 100 }, function () { 6 | require('./compile')(argv, fs.readFileSync(argv._[0], 'utf-8')); 7 | }); 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Pawel Maciejewski (http://fragphace.pl)", 3 | "name": "markdown-html", 4 | "description": "Command line tool for markdown to html conversion.", 5 | "version": "0.0.8", 6 | "homepage": "https://github.com/fragphace/markdown-html", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/fragphace/markdown-html.git" 10 | }, 11 | "dependencies": { 12 | "marked": "^0.3.2", 13 | "mu2": "~0.5.14", 14 | "optimist": "~0.3.4" 15 | }, 16 | "engines": { 17 | "node": "*" 18 | }, 19 | "keywords": [ 20 | "markdown", 21 | "html", 22 | "shell" 23 | ], 24 | "bin": { 25 | "markdown-html": "./index.js" 26 | }, 27 | "devDependencies": { 28 | "mocha": "^2.0.1", 29 | "chai": "^1.9.2" 30 | }, 31 | "scripts": { 32 | "test": "mocha --ui tdd test/" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /template/style.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | sup { 50 | vertical-align: super; 51 | font-size: smaller; 52 | } 53 | 54 | sub { 55 | vertical-align: sub; 56 | font-size: smaller; 57 | } 58 | 59 | /* Generic styles */ 60 | body { 61 | background: #f5f5f5; 62 | color: #222; 63 | font-family: 'Palatino Linotype','Book Antiqua',Palatino,FreeSerif,serif; 64 | font-size: 16px; 65 | margin: 0; 66 | padding: 0; 67 | } 68 | 69 | h1, h2, h3, h4, h5, h6, 70 | ul, ol, 71 | p, 72 | code, pre, 73 | hr { 74 | line-height: 1.5em; 75 | margin: 1.5em 0 0.5em 0; 76 | } 77 | 78 | *:first-child { 79 | margin-top: 0; 80 | } 81 | 82 | /* Headers */ 83 | h1, h2, h3, h4, h5, h6 { 84 | font-weight: bold; 85 | } 86 | 87 | h1 { 88 | font-size: 3.5em; 89 | } 90 | 91 | h2 { 92 | font-size: 2.5em; 93 | } 94 | 95 | h3 { 96 | font-size: 2em; 97 | } 98 | 99 | h4 { 100 | font-size: 1.5em 101 | } 102 | 103 | h5 { 104 | font-size: 1.2em; 105 | } 106 | 107 | h6 { 108 | font-size: 1em; 109 | } 110 | 111 | /* Lists */ 112 | ul, ol { 113 | padding-left: 2em; 114 | } 115 | 116 | ul { 117 | list-style-type: disc; 118 | } 119 | 120 | ol { 121 | list-style-type: decimal; 122 | } 123 | 124 | /* Code and pre */ 125 | code, pre { 126 | font-family: "Bitstream Vera Sans Mono", "Courier", monospace; 127 | } 128 | code { 129 | background: none repeat scroll 0 0 #F8F8FF; 130 | border: 1px solid #DEDEDE; 131 | border-radius: 3px ; 132 | padding: 0 0.2em; 133 | } 134 | pre { 135 | border-left: 5px solid #eee; 136 | margin-left: 2em; 137 | padding-left: 1em; 138 | } 139 | pre > code { 140 | background: transparent; 141 | border: none; 142 | padding: 0; 143 | } 144 | 145 | /* Links */ 146 | a { 147 | color: #261A3B; 148 | } 149 | 150 | a:visited { 151 | color: #261A3B; 152 | } 153 | 154 | /* Inlines */ 155 | strong { 156 | font-weight: bold; 157 | } 158 | 159 | em { 160 | font-style: italic; 161 | } 162 | 163 | /* Container */ 164 | .container { 165 | background: #FFF; 166 | padding: 30px 50px; 167 | margin: 0 auto; 168 | width: 850px; 169 | } 170 | -------------------------------------------------------------------------------- /template/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 9 | 10 | 11 |
12 | {{{content}}} 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/fixture/basic.md: -------------------------------------------------------------------------------- 1 | This is basic markdown 2 | 3 | With: 4 | 5 | 1. Some 6 | 2. Lists 7 | 3. And 8 | 4. Stuff -------------------------------------------------------------------------------- /test/fixture/firstHeaderIsATitle.md: -------------------------------------------------------------------------------- 1 | ## XYZ 2 | 3 | foo bar baz 4 | 5 | # Lorem -------------------------------------------------------------------------------- /test/fixture/script.js: -------------------------------------------------------------------------------- 1 | alert('custom script'); -------------------------------------------------------------------------------- /test/fixture/style.css: -------------------------------------------------------------------------------- 1 | body { background: #000; } -------------------------------------------------------------------------------- /test/fixture/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | 9 | 10 | 11 | THIS IS A CUSTOM TEMPLATE 12 |
13 | {{{content}}} 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Header 1 6 | 178 | 179 | 180 |
181 |

Header 1

182 |

Header 2

183 |

Header 3

184 |

Header 4

185 |
Header 5
186 |
Header 6
187 | 193 |

Ordinary paragraph.

194 |
    195 |
  1. And
  2. 196 |
  3. unordered
  4. 197 |
  5. list
  6. 198 |
199 |

This is a normal paragraph.

200 |
This is a code block.
201 | You can write some fancy stuff here!
202 | 

203 |

This is an example of link.

204 |

You can also try some text styles like italics and bold typeface.

205 |
206 |

Now lets code...

207 | 208 |
209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('chai').assert; 2 | var childProcess = require('child_process'); 3 | var execFile = childProcess.execFile; 4 | 5 | var mdhtml = function () { 6 | var args = Array.prototype.slice.call(arguments); 7 | args.unshift(__dirname + '/../index.js'); 8 | return execFile.apply(this, args); 9 | }; 10 | 11 | test('Basic', function (done) { 12 | mdhtml([__dirname + '/fixture/basic.md'], function (err, stdout, stderr) { 13 | assert.notEqual(stdout.search(''), -1, 'It should return html'); 14 | assert.notEqual(stdout.search('
    '), -1, 'Output should contain an
      '); 15 | assert.notEqual(stdout.search('basic'), -1, 'Title should be "basic"'); 16 | done(); 17 | }); 18 | }); 19 | 20 | test('Template', function (done) { 21 | mdhtml(['--template', __dirname + '/fixture/template.html', __dirname + '/fixture/basic.md'], function (err, stdout, stderr) { 22 | assert.notEqual(stdout.search('THIS IS A CUSTOM TEMPLATE'), -1, 'It should use custom template'); 23 | done(); 24 | }); 25 | }); 26 | 27 | test('Title', function (done) { 28 | mdhtml(['--title', 'blabla', __dirname + '/fixture/basic.md'], function (err, stdout, stderr) { 29 | assert.notEqual(stdout.search('blabla'), -1, 'It should use user-specified title'); 30 | done(); 31 | }); 32 | }); 33 | 34 | test('Style', function (done) { 35 | mdhtml(['--style', __dirname + '/fixture/style.css', __dirname + '/fixture/basic.md'], function (err, stdout, stderr) { 36 | assert.notEqual(stdout.search('body { background: #000; }'), -1, 'It should use user-specified styles'); 37 | done(); 38 | }); 39 | }); 40 | 41 | test('Help', function (done) { 42 | mdhtml(['-h'], function (err, stdout, stderr) { 43 | assert.equal(stdout.search('<html>'), -1, 'Stdout should not contain html'); 44 | assert.ok(stdout.length, 'Stdout should not be empty'); 45 | done(); 46 | }); 47 | }); 48 | 49 | test('Script', function (done) { 50 | mdhtml(['--script', __dirname + '/fixture/script.js', __dirname + '/fixture/basic.md'], function (err, stdout, stderr) { 51 | assert.notEqual(stdout.search('custom script'), -1, 'It should use user-specified script'); 52 | done(); 53 | }); 54 | }); 55 | 56 | test('Stdin', function (done) { 57 | var io = childProcess.spawn(__dirname + '/../index.js', ['-i']); 58 | var out = ""; 59 | 60 | io.stdout.on('data', function (chunk) { 61 | out += chunk.toString(); 62 | }); 63 | 64 | io.stdout.on('end', function () { 65 | assert.ok(out.match(/<h2[^>]*>Test/)); 66 | done(); 67 | }); 68 | 69 | io.stdin.end('## Test'); 70 | }); 71 | 72 | test('First header is a title', function (done) { 73 | mdhtml([__dirname + '/fixture/firstHeaderIsATitle.md'], function (err, stdout, stderr) { 74 | assert.ok(stdout.match(/<title>XYZ<\/title>/)); 75 | done(); 76 | }); 77 | }); 78 | 79 | test('Title option takes precedence by first header', function (done) { 80 | mdhtml(['--title', 'foo',__dirname + '/fixture/firstHeaderIsATitle.md'], function (err, stdout, stderr) { 81 | assert.ok(stdout.match(/<title>foo<\/title>/)); 82 | done(); 83 | }); 84 | }); -------------------------------------------------------------------------------- /test/test.md: -------------------------------------------------------------------------------- 1 | # Header 1 2 | ## Header 2 3 | ### Header 3 4 | #### Header 4 5 | ##### Header 5 6 | ###### Header 6 7 | 8 | + Unordered 9 | + list 10 | + of 11 | + items 12 | 13 | Ordinary paragraph. 14 | 15 | 1. And 16 | 4. unordered 17 | 5. list 18 | 19 | This is a normal paragraph. 20 | 21 | This is a code block. 22 | You can write some fancy stuff here! 23 | 24 | *** 25 | 26 | This is [an example](http://google.com) of link. 27 | 28 | You can also try some text styles like *italics* and **bold typeface**. 29 | 30 | *** 31 | 32 | Now lets `code`... --------------------------------------------------------------------------------