index.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. var Prism = require('prismjs');
  2. var languages = require('prismjs').languages;
  3. var path = require('path');
  4. var fs = require('fs');
  5. var cheerio = require('cheerio');
  6. var mkdirp = require('mkdirp');
  7. var DEFAULT_LANGUAGE = 'markup';
  8. var MAP_LANGUAGES = {
  9. 'py': 'python',
  10. 'js': 'javascript',
  11. 'rb': 'ruby',
  12. 'cs': 'csharp',
  13. 'sh': 'bash',
  14. 'html': 'markup'
  15. };
  16. // Base languages syntaxes (as of prism@1.6.0), extended by other syntaxes.
  17. // They need to be required before the others.
  18. var PRELUDE = [
  19. 'markup-templating', 'clike', 'javascript', 'markup', 'c', 'ruby', 'css',
  20. // The following depends on previous ones
  21. 'java', 'php'
  22. ];
  23. PRELUDE.map(requireSyntax);
  24. /**
  25. * Load the syntax definition for a language id
  26. */
  27. function requireSyntax(lang) {
  28. require('prismjs/components/prism-' + lang + '.js');
  29. }
  30. function getConfig(context, property, defaultValue) {
  31. var config = context.config ? /* 3.x */ context.config : /* 2.x */ context.book.config;
  32. return config.get(property, defaultValue);
  33. }
  34. function isEbook(book) {
  35. // 2.x
  36. if (book.options && book.options.generator) {
  37. return book.options.generator === 'ebook';
  38. }
  39. // 3.x
  40. return book.output.name === 'ebook';
  41. }
  42. function getAssets() {
  43. var cssFiles = getConfig(this, 'pluginsConfig.prism.css', []);
  44. var cssFolder = null;
  45. var cssNames = [];
  46. var cssName = null;
  47. if (cssFiles.length === 0) {
  48. cssFiles.push('prismjs/themes/prism.css');
  49. }
  50. cssFiles.forEach(function(cssFile) {
  51. var cssPath = require.resolve(cssFile);
  52. cssFolder = path.dirname(cssPath);
  53. cssName = path.basename(cssPath);
  54. cssNames.push(cssName);
  55. });
  56. return {
  57. assets: cssFolder,
  58. css: cssNames
  59. };
  60. }
  61. module.exports = {
  62. book: getAssets,
  63. ebook: function() {
  64. // Adding prism-ebook.css to the CSS collection forces Gitbook
  65. // reference to it in the html markup that is converted into a PDF.
  66. var assets = getAssets.call(this);
  67. assets.css.push('prism-ebook.css');
  68. return assets;
  69. },
  70. blocks: {
  71. code: function(block) {
  72. var highlighted = '';
  73. var userDefined = getConfig(this, 'pluginsConfig.prism.lang', {});
  74. var userIgnored = getConfig(this, 'pluginsConfig.prism.ignore', []);
  75. // Normalize language id
  76. var lang = block.kwargs.language || DEFAULT_LANGUAGE;
  77. lang = userDefined[lang] || MAP_LANGUAGES[lang] || lang;
  78. // Check to see if the lang is ignored
  79. if (userIgnored.indexOf(lang) > -1) {
  80. return block.body;
  81. }
  82. // Try and find the language definition in components folder
  83. if (!languages[lang]) {
  84. try {
  85. requireSyntax(lang);
  86. } catch (e) {
  87. console.warn('Failed to load prism syntax: ' + lang);
  88. console.warn(e);
  89. }
  90. }
  91. if (!languages[lang]) lang = DEFAULT_LANGUAGE;
  92. // Check against html, prism "markup" works for this
  93. if (lang === 'html') {
  94. lang = 'markup';
  95. }
  96. try {
  97. // The process can fail (failed to parse)
  98. highlighted = Prism.highlight(block.body, languages[lang]);
  99. } catch (e) {
  100. console.warn('Failed to highlight:');
  101. console.warn(e);
  102. highlighted = block.body;
  103. }
  104. return highlighted;
  105. }
  106. },
  107. hooks: {
  108. // Manually copy prism-ebook.css into the temporary directory that Gitbook uses for inlining
  109. // styles from this plugin. The getAssets() (above) function can't be leveraged because
  110. // ebook-prism.css lives outside the folder referenced by this plugin's config.
  111. //
  112. // @Inspiration https://github.com/GitbookIO/plugin-styles-less/blob/master/index.js#L8
  113. init: function() {
  114. var book = this;
  115. if (!isEbook(book)) {
  116. return;
  117. }
  118. var outputDirectory = path.join(book.output.root(), '/gitbook/gitbook-plugin-prism');
  119. var outputFile = path.resolve(outputDirectory, 'prism-ebook.css');
  120. var inputFile = path.resolve(__dirname, './prism-ebook.css');
  121. mkdirp.sync(outputDirectory);
  122. try {
  123. fs.writeFileSync(outputFile, fs.readFileSync(inputFile));
  124. } catch (e) {
  125. console.warn('Failed to write prism-ebook.css. See https://git.io/v1LHY for side effects.');
  126. console.warn(e);
  127. }
  128. },
  129. page: function(page) {
  130. var highlighted = false;
  131. var $ = cheerio.load(page.content);
  132. // Prism css styles target the <code> and <pre> blocks using
  133. // a substring CSS selector:
  134. //
  135. // code[class*="language-"], pre[class*="language-"]
  136. //
  137. // Adding "language-" to <pre> element should be sufficient to trigger
  138. // correct color theme.
  139. $('pre').each(function() {
  140. highlighted = true;
  141. const $this = $(this);
  142. $this.addClass('language-');
  143. });
  144. if (highlighted) {
  145. page.content = $.html();
  146. }
  147. return page;
  148. }
  149. }
  150. };