invoker.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. const fs = require('fs')
  2. const ospath = require('path')
  3. const asciidoctor = require('@asciidoctor/core')()
  4. const pkg = require('../package.json')
  5. const stdin = require('./stdin')
  6. const DOT_RELATIVE_RX = new RegExp(`^\\.{1,2}[/${ospath.sep.replace('/', '').replace('\\', '\\\\')}]`)
  7. class Invoker {
  8. constructor (options) {
  9. this.options = options
  10. }
  11. async invoke () {
  12. const processArgs = this.options.argv.slice(2)
  13. const { args } = this.options
  14. const { verbose, version, files } = args
  15. if (version || (verbose && processArgs.length === 1)) {
  16. this.showVersion()
  17. process.exit(0)
  18. }
  19. Invoker.prepareProcessor(args, asciidoctor)
  20. const options = this.options.options
  21. const failureLevel = options.failure_level
  22. if (this.options.stdin) {
  23. await Invoker.convertFromStdin(options, args)
  24. Invoker.exit(failureLevel)
  25. } else if (files && files.length > 0) {
  26. Invoker.processFiles(files, verbose, args.timings, options)
  27. Invoker.exit(failureLevel)
  28. } else {
  29. this.showHelp()
  30. process.exit(0)
  31. }
  32. }
  33. showHelp () {
  34. if (this.options.args.help === 'syntax') {
  35. console.log(fs.readFileSync(ospath.join(__dirname, '..', 'data', 'reference', 'syntax.adoc'), 'utf8'))
  36. } else {
  37. this.options.yargs.showHelp()
  38. }
  39. }
  40. showVersion () {
  41. console.log(this.version())
  42. }
  43. version () {
  44. const releaseName = process.release ? process.release.name : 'node'
  45. return `Asciidoctor.js ${asciidoctor.getVersion()} (Asciidoctor ${asciidoctor.getCoreVersion()}) [https://asciidoctor.org]
  46. Runtime Environment (${releaseName} ${process.version} on ${process.platform})
  47. CLI version ${pkg.version}`
  48. }
  49. /**
  50. * @deprecated Use {#showVersion}. Will be removed in version 4.0.
  51. */
  52. static printVersion () {
  53. console.log(new Invoker().version())
  54. }
  55. static async readFromStdin () {
  56. return stdin.read()
  57. }
  58. static async convertFromStdin (options, args) {
  59. const data = await Invoker.readFromStdin()
  60. if (args.timings) {
  61. const timings = asciidoctor.Timings.create()
  62. const instanceOptions = Object.assign({}, options, { timings })
  63. Invoker.convert(asciidoctor.convert, data, instanceOptions)
  64. timings.printReport(process.stderr, '-')
  65. } else {
  66. Invoker.convert(asciidoctor.convert, data, options)
  67. }
  68. }
  69. static convert (processorFn, input, options) {
  70. try {
  71. processorFn.apply(asciidoctor, [input, options])
  72. } catch (e) {
  73. if (e && e.name === 'NotImplementedError' && e.message === `asciidoctor: FAILED: missing converter for backend '${options.backend}'. Processing aborted.`) {
  74. console.error(`> Error: missing converter for backend '${options.backend}'. Processing aborted.`)
  75. console.error('> You might want to require a Node.js package with --require option to support this backend.')
  76. process.exit(1)
  77. }
  78. throw e
  79. }
  80. }
  81. static convertFile (file, options) {
  82. Invoker.convert(asciidoctor.convertFile, file, options)
  83. }
  84. static processFiles (files, verbose, timings, options) {
  85. files.forEach((file) => {
  86. if (verbose) {
  87. console.log(`converting file ${file}`)
  88. }
  89. if (timings) {
  90. const timings = asciidoctor.Timings.create()
  91. const instanceOptions = Object.assign({}, options, { timings })
  92. Invoker.convertFile(file, instanceOptions)
  93. timings.printReport(process.stderr, file)
  94. } else {
  95. Invoker.convertFile(file, options)
  96. }
  97. })
  98. }
  99. static requireLibrary (requirePath, cwd = process.cwd()) {
  100. if (requirePath.charAt(0) === '.' && DOT_RELATIVE_RX.test(requirePath)) {
  101. // NOTE require resolves a dot-relative path relative to current file; resolve relative to cwd instead
  102. requirePath = ospath.resolve(requirePath)
  103. } else if (!ospath.isAbsolute(requirePath)) {
  104. // NOTE appending node_modules prevents require from looking elsewhere before looking in these paths
  105. const paths = [cwd, ospath.dirname(__dirname)].map((start) => ospath.join(start, 'node_modules'))
  106. requirePath = require.resolve(requirePath, { paths })
  107. }
  108. return require(requirePath)
  109. }
  110. static prepareProcessor (argv, asciidoctor) {
  111. const requirePaths = argv.require
  112. if (requirePaths) {
  113. requirePaths.forEach((requirePath) => {
  114. const lib = Invoker.requireLibrary(requirePath)
  115. if (lib && typeof lib.register === 'function') {
  116. // REMIND: it could be an extension or a converter.
  117. // the register function on a converter does not take any argument
  118. // but the register function on an extension expects one argument (the extension registry)
  119. // Until we revisit the API for extension and converter, we pass the registry as the first argument
  120. lib.register(asciidoctor.Extensions)
  121. }
  122. })
  123. }
  124. }
  125. static exit (failureLevel) {
  126. let code = 0
  127. const logger = asciidoctor.LoggerManager.getLogger()
  128. if (logger && typeof logger.getMaxSeverity === 'function' && logger.getMaxSeverity() && logger.getMaxSeverity() >= failureLevel) {
  129. code = 1
  130. }
  131. process.exit(code)
  132. }
  133. }
  134. module.exports = Invoker
  135. module.exports.asciidoctor = asciidoctor