Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,23 +264,23 @@ npm install -g markdown-pdf
### Usage

```
Usage: markdown-pdf [options] <markdown-file-path>

Options:

-h, --help output usage information
-V, --version output the version number
<markdown-file-path> Path of the markdown file to convert
-c, --cwd [path] Current working directory
-p, --phantom-path [path] Path to phantom binary
-h, --runnings-path [path] Path to runnings (header, footer)
-s, --css-path [path] Path to custom CSS file
-z, --highlight-css-path [path] Path to custom highlight-CSS file
-m, --remarkable-options [json] Options to pass to Remarkable
-f, --paper-format [format] 'A3', 'A4', 'A5', 'Legal', 'Letter' or 'Tabloid'
-r, --paper-orientation [orientation] 'portrait' or 'landscape'
-b, --paper-border [measurement] Supported dimension units are: 'mm', 'cm', 'in', 'px'
-d, --render-delay [millis] Delay before rendering the PDF
-t, --load-timeout [millis] Timeout before the page is rendered in case `page.onLoadFinished` isn't fired
-o, --out [path] Path of where to save the PDF
Usage: markdown-pdf [options] <markdown-file-paths ...>

Options:

-h, --help output usage information
-V, --version output the version number
<markdown-file-paths> Paths of the markdown files to convert
-c, --cwd [path] Current working directory
-p, --phantom-path [path] Path to phantom binary
-h, --runnings-path [path] Path to runnings (header, footer)
-s, --css-path [path] Path to custom CSS file
-z, --highlight-css-path [path] Path to custom highlight-CSS file
-m, --remarkable-options [json-options] Options to pass to remarkable
-f, --paper-format [format] "A3", "A4", "A5", "Legal", "Letter" or "Tabloid"
-r, --paper-orientation [orientation] "portrait" or "landscape"
-b, --paper-border [measurement] Supported dimension units are: "mm", "cm", "in", "px"
-d, --render-delay [millis] Delay before rendering the PDF
-t, --load-timeout [millis] Timeout before the page is rendered in case `page.onLoadFinished` isn't fired
-o, --out [path] Directory to save output PDFs
```
28 changes: 20 additions & 8 deletions bin/markdown-pdf
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

var markdownpdf = require('../')
var program = require('commander')
var path = require('path')
var fs = require('fs')
var async = require('async')

program.version(require('../package.json').version)
.usage('[options] <markdown-file-path>')
.option('<markdown-file-path>', 'Path of the markdown file to convert')
.usage('[options] <markdown-file-paths ...>')
.option('<markdown-file-paths>', 'Paths of the markdown files to convert')
.option('-c, --cwd [path]', 'Current working directory')
.option('-p, --phantom-path [path]', 'Path to phantom binary')
.option('-h, --runnings-path [path]', 'Path to runnings (header, footer)')
Expand All @@ -17,12 +20,12 @@ program.version(require('../package.json').version)
.option('-b, --paper-border [measurement]', 'Supported dimension units are: "mm", "cm", "in", "px"')
.option('-d, --render-delay [millis]', 'Delay before rendering the PDF')
.option('-t, --load-timeout [millis]', 'Timeout before the page is rendered in case `page.onLoadFinished` isn\'t fired')
.option('-o, --out [path]', 'Path of where to save the PDF')
.option('-o, --out [path]', 'Directory to save output PDFs')
.parse(process.argv)

if (program.args.length === 0) program.help()
var inputFileNames = program.args

program.out = program.out || program.args[0].replace(/\.m(ark)?d(own)?/gi, '') + '.pdf'
if (inputFileNames.length === 0) program.help()

var opts = {
cwd: program.cwd,
Expand All @@ -38,6 +41,15 @@ var opts = {
loadTimeout: program.loadTimeout
}

markdownpdf(opts).concat.from(program.args).to(program.out, function (err) {
if (err) throw err
})
async.each(inputFileNames, generateFromFile)

function generateFromFile (inputPath) {
var inputPathAsPdf = inputPath.replace(/\.m(ark)?d(own)?/gi, '') + '.pdf'
var outputPath = program.out
? path.join(program.out, path.basename(inputPathAsPdf))
: inputPathAsPdf

fs.createReadStream(inputPath)
.pipe(markdownpdf(opts))
.pipe(fs.createWriteStream(outputPath))
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Markdown to PDF converter",
"main": "index.js",
"scripts": {
"test": "standard && istanbul cover node_modules/.bin/tape test/*.js",
"test": "standard && standard bin/markdown-pdf && istanbul cover node_modules/.bin/tape test/*.js",
"coveralls": "cat ./coverage/lcov.info | coveralls"
},
"repository": {
Expand All @@ -21,6 +21,7 @@
"homepage": "https://github.com/alanshaw/markdown-pdf",
"license": "MIT",
"dependencies": {
"async": "^1.5.2",
"commander": "^2.2.0",
"duplexer": "^0.1.1",
"extend": "^3.0.0",
Expand All @@ -32,6 +33,7 @@
"tmp": "0.0.28"
},
"devDependencies": {
"concat-stream": "^1.5.1",
"coveralls": "^2.10.0",
"istanbul": "^0.4.0",
"pdf-text": "^0.4.0",
Expand Down
113 changes: 113 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
var spawn = require('child_process').spawn
var path = require('path')
var test = require('tape')
var fs = require('fs')
var tmp = require('tmp')
var concat = require('concat-stream')
var async = require('async')

tmp.setGracefulCleanup()

// Helpers for running the command-line program and creating input files

var executablePath = path.join(__dirname, '..', 'bin', 'markdown-pdf')

var execute = function (args, options, callback) {
if ((typeof options) === 'function') {
callback = options
options = {}
}
var process = spawn(executablePath, args, options)
process.stdout.pipe(concat(function (output) {
callback(output.toString('utf8'))
}))
}

// Tests start here

test('CLI: --help emits a usage text', function (t) {
t.plan(2)
;[ ['-h'], ['--help'] ].forEach(function (argList) {
execute(argList, function (output) {
t.ok(output.match(/^\s+Usage.*/), 'contains usage with ' + argList)
})
})
})

test('CLI: --version emits correct version number', function (t) {
t.plan(2)
;[ ['-V'], ['--version'] ].forEach(function (argList) {
execute(argList, function (output) {
t.equals(output, require('../package.json').version + '\n')
})
})
})

test('CLI: compiles 1 file', function (t) {
t.plan(5)
tmp.dir({ unsafeCleanup: true }, function (er, tmpDir, cleanup) {
t.ifError(er, 'tmp dir created')

var content = '[test](http://example.com)'
var inputFileName = 'test.md'
var expectedOutputFileName = 'test.pdf'
var inputFile = path.join(tmpDir, inputFileName)
var expectedOutputFile = path.join(tmpDir, expectedOutputFileName)

fs.writeFile(inputFile, content, function (er) {
t.ifError(er, 'wrote to ' + inputFile)

execute([ inputFileName ], { cwd: tmpDir }, function (output) {
t.equals(output, '')
fs.stat(expectedOutputFile, function (er, stat) {
t.ifError(er, expectedOutputFile + ' exists')
t.ok(stat.size)

cleanup()
})
})
})
})
})

test('CLI: compiles 3 files', function (t) {
t.plan(3)
tmp.dir({ unsafeCleanup: true }, function (er, tmpDir, cleanup) {
t.ifError(er, 'tmp dir created')

var content = '[test](http://example.com)' // written to each test file

var names = [ 'test1', 'test2', 'test3' ]
var namesMd = names.map(function (n) { return n + '.md' })
var namesPdf = names.map(function (n) { return n + '.pdf' })

var createInputFiles = function (callback) {
async.each(
namesMd.map(function (n) { return path.join(tmpDir, n) }),
function (name, cb) { fs.writeFile(name, content, cb) },
callback)
}

var convertFiles = function (callback) {
execute(namesMd, { cwd: tmpDir }, function (output) {
t.equals(output, '')
callback()
})
}

var checkOutputFiles = function (callback) {
var filesPdf = namesPdf.map(function (n) { return path.join(tmpDir, n) })
async.every(filesPdf, fs.exists, function (result, cb) {
t.ok(result, 'all output files exist')
callback()
})
}

async.series([
createInputFiles,
convertFiles,
checkOutputFiles,
cleanup
])
})
})
54 changes: 27 additions & 27 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ test('generate a nonempty PDF from ipsum.md', function (t) {
t.plan(4)

tmp.file({postfix: '.pdf'}, function (er, tmpPdfPath, tmpPdfFd) {
t.ifError(er)
t.ifError(er, 'created ' + tmpPdfPath)
fs.close(tmpPdfFd)

markdownpdf().from(__dirname + '/fixtures/ipsum.md').to(tmpPdfPath, function (er) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Read the file
fs.readFile(tmpPdfPath, {encoding: 'utf8'}, function (er, data) {
t.ifError(er)
t.ifError(er, 'read ' + tmpPdfPath)
// Test not empty
t.ok(data.length > 0)
t.ok(data.length > 0, 'nonempty ' + tmpPdfPath)
t.end()
})
})
Expand All @@ -32,24 +32,24 @@ test('output should have a header and footer', function (t) {
t.plan(7)

tmp.file({postfix: '.pdf'}, function (er, tmpPdfPath, tmpPdfFd) {
t.ifError(er)
t.ifError(er, 'created ' + tmpPdfPath)
fs.close(tmpPdfFd)

markdownpdf({runningsPath: __dirname + '/fixtures/runnings.js'}).from(__dirname + '/fixtures/ipsum.md').to(tmpPdfPath, function (er) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Read the file
fs.readFile(tmpPdfPath, {encoding: 'utf8'}, function (er, data) {
t.ifError(er)
t.ifError(er, 'read ' + tmpPdfPath)
// Test not empty
t.ok(data.length > 0)
t.ok(data.length > 0, 'nonempty ' + tmpPdfPath)

// Header and footer included?
pdfText(tmpPdfPath, function (er, chunks) {
t.ifError(er)
t.ifError(er, 'ran pdfText')

t.ok(/Some\s?Header/.test(chunks.join('')))
t.ok(/Some\s?Footer/.test(chunks.join('')))
t.ok(/Some\s?Header/.test(chunks.join('')), 'header included')
t.ok(/Some\s?Footer/.test(chunks.join('')), 'footer included')
t.end()
})
})
Expand All @@ -64,10 +64,10 @@ test('should call preProcessMd hook', function (t) {
var preProcessMd = function () { return through(function (data, enc, cb) { writeCount++; this.push(data); cb() }) }

markdownpdf({preProcessMd: preProcessMd}).from(__dirname + '/fixtures/ipsum.md').to.string(function (er, pdfStr) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Test not empty
t.ok(pdfStr.length > 0)
t.ok(pdfStr.length > 0, 'nonempty')
t.ok(writeCount > 0, 'Write count expected to be > 0')
t.end()
})
Expand All @@ -80,10 +80,10 @@ test('should call preProcessHtml hook', function (t) {
var preProcessHtml = function () { return through(function (data, enc, cb) { writeCount++; this.push(data); cb() }) }

markdownpdf({preProcessHtml: preProcessHtml}).from(__dirname + '/fixtures/ipsum.md').to.string(function (er, pdfStr) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Test not empty
t.ok(pdfStr.length > 0)
t.ok(pdfStr.length > 0, 'nonempty')
t.ok(writeCount > 0, 'Write count expected to be > 0')
t.end()
})
Expand All @@ -98,17 +98,17 @@ test('should concatenate source files', function (t) {
]

tmp.file({postfix: '.pdf'}, function (er, tmpPdfPath, tmpPdfFd) {
t.ifError(er)
t.ifError(er, 'created ' + tmpPdfPath)
fs.close(tmpPdfFd)

markdownpdf().concat.from(files).to(tmpPdfPath, function (er) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Read the file
fs.readFile(tmpPdfPath, {encoding: 'utf8'}, function (er, data) {
t.ifError(er)
t.ifError(er, 'read ' + tmpPdfPath)
// Test not empty
t.ok(data.length > 0)
t.ok(data.length > 0, 'nonempty ' + tmpPdfPath)
t.end()
})
})
Expand All @@ -124,23 +124,23 @@ test('should write to multiple paths when converting multiple files', function (
]

tmp.file({postfix: '.pdf'}, function (er, tmpPdfPath0, tmpPdfFd0) {
t.ifError(er)
t.ifError(er, 'created ' + tmpPdfPath0)
fs.close(tmpPdfFd0)

tmp.file({postfix: '.pdf'}, function (er, tmpPdfPath1, tmpPdfFd1) {
t.ifError(er)
t.ifError(er, 'created ' + tmpPdfPath1)
fs.close(tmpPdfFd1)

markdownpdf().from.paths(files).to.paths([tmpPdfPath0, tmpPdfPath1], function (er) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

// Read the file
var content0 = fs.readFileSync(tmpPdfPath0, {encoding: 'utf8'})
var content1 = fs.readFileSync(tmpPdfPath1, {encoding: 'utf8'})

t.ok(content0.length > 0)
t.ok(content1.length > 0)
t.ok(content0.length !== content1.length)
t.ok(content0.length > 0, 'nonempty ' + tmpPdfPath0)
t.ok(content1.length > 0, 'nonempty ' + tmpPdfPath1)
t.ok(content0.length !== content1.length, 'sizes differ')

t.end()
})
Expand Down Expand Up @@ -171,7 +171,7 @@ test('should accept remarkable preset', function (t) {

// Preset 'full' - expecting <sup>-tag in html
markdownpdf(opts).from.string('1^st^ of January').to.string(function (er, pdfStr) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')
t.end()
})
})
Expand All @@ -193,7 +193,7 @@ test('should initialize remarkable plugins', function (t) {
}

markdownpdf({remarkable: remarkableOpts}).from(__dirname + '/fixtures/ipsum.md').to.string(function (er, pdfStr) {
t.ifError(er)
t.ifError(er, 'ran markdownpdf')

t.assert(pluginInit, 'check plugin init')
t.end()
Expand Down