diff --git a/lib/cli.js b/lib/cli.js index 46bfc71..821ba57 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -3,10 +3,12 @@ const yargs = require('yargs') const fs = require('fs') -const path = require('path') const asciidoctor = require('@asciidoctor/core')() const pkg = require('../package.json') const stdin = require('./stdin') +const ospath = require('path') + +const DOT_RELATIVE_RX = new RegExp(`^\\.{1,2}[/${ospath.sep.replace('/', '').replace('\\', '\\\\')}]`) function convertOptions (args) { const backend = args['backend'] @@ -47,7 +49,14 @@ function convertOptions (args) { console.log('destination-dir ' + destinationDir) } if (requireLib) { - require(requireLib) + const lib = require(requireLib) + if (lib && typeof lib.register === 'function') { + // REMIND: it could be an extension or a converter. + // the register function on a converter does not take any argument + // but the register function on an extension expects one argument (the extension registry) + // Until we revisit the API for extension and converter, we pass the registry as the first argument + lib.register(asciidoctor.Extensions) + } } const verboseMode = quiet ? 0 : verbose ? 2 : 1 const attributes = [] @@ -271,6 +280,35 @@ function processFiles (files, verbose, timings, options) { process.exit(code) } +function requireLibrary (requirePath, cwd = process.cwd()) { + if (requirePath.charAt(0) === '.' && DOT_RELATIVE_RX.test(requirePath)) { + // NOTE require resolves a dot-relative path relative to current file; resolve relative to cwd instead + requirePath = ospath.resolve(requirePath) + } else if (!ospath.isAbsolute(requirePath)) { + // NOTE appending node_modules prevents require from looking elsewhere before looking in these paths + const paths = [cwd, ospath.dirname(__dirname)].map((start) => ospath.join(start, 'node_modules')) + requirePath = require.resolve(requirePath, { paths }) + } + return require(requirePath) +} + +function prepareProcessor (argv, asciidoctor) { + const requirePaths = argv['require'] + if (requirePaths) { + requirePaths.forEach(function (requirePath) { + const lib = requireLibrary(requirePath) + if (lib && typeof lib.register === 'function') { + // REMIND: it could be an extension or a converter. + // the register function on a converter does not take any argument + // but the register function on an extension expects one argument (the extension registry) + // Until we revisit the API for extension and converter, we pass the registry as the first argument + lib.register(asciidoctor.Extensions) + } + }) + } +} + + function run (argv) { const processArgs = argv.slice(2) const args = argsParser().parse(processArgs) @@ -282,6 +320,7 @@ function run (argv) { args['out-file'] = args['out-file'] || '-' } const options = convertOptions(args) + prepareProcessor(args, asciidoctor) if (stdin) { convertFromStdin(options, args) } else if (version || (verbose && files && files.length === 0)) { @@ -293,7 +332,7 @@ function run (argv) { processFiles(files, verbose, args['timings'], options) } else { if (args['help'] === 'syntax') { - console.log(fs.readFileSync(path.join(__dirname, '..', 'data', 'reference', 'syntax.adoc'), 'utf8')) + console.log(fs.readFileSync(ospath.join(__dirname, '..', 'data', 'reference', 'syntax.adoc'), 'utf8')) } else { yargs.showHelp() } @@ -305,5 +344,6 @@ module.exports = { argsParser: argsParser, convertOptions: convertOptions, processFiles: processFiles, - processor: asciidoctor + processor: asciidoctor, + prepareProcessor: prepareProcessor } diff --git a/test/fixtures/blog-converter.js b/test/fixtures/blog-converter.js new file mode 100644 index 0000000..cc224d6 --- /dev/null +++ b/test/fixtures/blog-converter.js @@ -0,0 +1,9 @@ +class BlogConverter { + convert () { + return ''; + } +} + +module.exports.register = function register () { + Opal.Asciidoctor.ConverterFactory.register(new BlogConverter(), ['blog']); +} diff --git a/test/fixtures/shout-ext.js b/test/fixtures/shout-ext.js new file mode 100644 index 0000000..207a74c --- /dev/null +++ b/test/fixtures/shout-ext.js @@ -0,0 +1,20 @@ +const shoutBlock = function () { + const self = this + self.named('shout') + self.onContext('paragraph') + self.process(function (parent, reader) { + const lines = reader.getLines().map(function (l) { return l.toUpperCase() }) + return self.createBlock(parent, 'paragraph', lines) + }) +} + +module.exports.register = function register (registry) { + if (typeof registry.register === 'function') { + registry.register(function () { + this.block(shoutBlock) + }) + } else if (typeof registry.block === 'function') { + registry.block(shoutBlock) + } + return registry +} diff --git a/test/test.js b/test/test.js index 05d6f09..c3e74f9 100644 --- a/test/test.js +++ b/test/test.js @@ -7,7 +7,7 @@ const dirtyChai = require('dirty-chai') chai.use(dirtyChai) const stdin = require('../lib/stdin') -const { run, processor, convertOptions, processFiles } = require('../lib/cli.js') +const { run, processor, convertOptions, processFiles, prepareProcessor } = require('../lib/cli.js') const argsParser = require('../lib/cli.js').argsParser() describe('Arguments parser', () => { @@ -182,3 +182,30 @@ describe('Process files', () => { } }) }) + +describe('Require option', () => { + it('should require an extension library', () => { + const asciidoctor = require('@asciidoctor/core')() + try { + expect(Object.keys(asciidoctor.Extensions.getGroups()).length).to.equal(0) + prepareProcessor(argsParser.parse('one.adoc -r ./test/fixtures/shout-ext.js'), asciidoctor) + expect(Object.keys(asciidoctor.Extensions.getGroups()).length).to.equal(1) + } finally { + asciidoctor.Extensions.unregisterAll() + } + }) + + it('should require a converter library', () => { + const asciidoctor = require('@asciidoctor/core')() + try { + asciidoctor.convert('Hello', { backend: 'blog' }) + expect.fail('blog backend should be missing') + } catch (e) { + expect(e.message).to.equal('asciidoctor: FAILED: missing converter for backend \'blog\'. Processing aborted.') + } + prepareProcessor(argsParser.parse('one.adoc -r ./test/fixtures/blog-converter.js'), asciidoctor) + const html = asciidoctor.convert('Hello', { backend: 'blog' }) + expect(html).to.equal('') + }) +}) +