utility.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. 'use strict';
  2. let _ = require('lodash');
  3. let bluebird = require('bluebird');
  4. let glob = bluebird.promisify(require('glob'));
  5. let path = require('path');
  6. let Queue = require('gear').Queue;
  7. let regex = {},
  8. headerRegex = /^\s*\/\*((.|\r?\n)*?)\*/;
  9. const REPLACES = {
  10. 'case_insensitive': 'cI',
  11. 'lexemes': 'l',
  12. 'contains': 'c',
  13. 'keywords': 'k',
  14. 'subLanguage': 'sL',
  15. 'className': 'cN',
  16. 'begin': 'b',
  17. 'beginKeywords': 'bK',
  18. 'end': 'e',
  19. 'endsWithParent': 'eW',
  20. 'illegal': 'i',
  21. 'excludeBegin': 'eB',
  22. 'excludeEnd': 'eE',
  23. 'returnBegin': 'rB',
  24. 'returnEnd': 'rE',
  25. 'relevance': 'r',
  26. 'variants': 'v',
  27. 'IDENT_RE': 'IR',
  28. 'UNDERSCORE_IDENT_RE': 'UIR',
  29. 'NUMBER_RE': 'NR',
  30. 'C_NUMBER_RE': 'CNR',
  31. 'BINARY_NUMBER_RE': 'BNR',
  32. 'RE_STARTERS_RE': 'RSR',
  33. 'BACKSLASH_ESCAPE': 'BE',
  34. 'APOS_STRING_MODE': 'ASM',
  35. 'QUOTE_STRING_MODE': 'QSM',
  36. 'PHRASAL_WORDS_MODE': 'PWM',
  37. 'C_LINE_COMMENT_MODE': 'CLCM',
  38. 'C_BLOCK_COMMENT_MODE': 'CBCM',
  39. 'HASH_COMMENT_MODE': 'HCM',
  40. 'NUMBER_MODE': 'NM',
  41. 'C_NUMBER_MODE': 'CNM',
  42. 'BINARY_NUMBER_MODE': 'BNM',
  43. 'CSS_NUMBER_MODE': 'CSSNM',
  44. 'REGEXP_MODE': 'RM',
  45. 'TITLE_MODE': 'TM',
  46. 'UNDERSCORE_TITLE_MODE': 'UTM',
  47. 'COMMENT': 'C',
  48. 'beginRe': 'bR',
  49. 'endRe': 'eR',
  50. 'illegalRe': 'iR',
  51. 'lexemesRe': 'lR',
  52. 'terminators': 't',
  53. 'terminator_end': 'tE'
  54. };
  55. regex.replaces = new RegExp(
  56. `\\b(${Object.keys(REPLACES).join('|')})\\b`, 'g');
  57. regex.classname = /(block|parentNode)\.cN/g;
  58. regex.header = /^\s*(\/\*((.|\r?\n)*?)\*\/)?\s*/;
  59. function replace(from, to) {
  60. return { regex: from, replace: to };
  61. }
  62. function replaceClassNames(match) {
  63. return REPLACES[match];
  64. }
  65. // All meta data, for each language definition, it store within the headers
  66. // of each file in `src/languages`. `parseHeader` extracts that data and
  67. // turns it into a useful object -- mainly for categories and what language
  68. // this definition requires.
  69. function parseHeader(content) {
  70. let headers,
  71. match = content.match(headerRegex);
  72. if (!match) {
  73. return null;
  74. }
  75. headers = _.compact(match[1].split('\n'));
  76. return _.reduce(headers, function(result, header) {
  77. let keyVal = header.trim().split(': '),
  78. key = keyVal[0],
  79. value = keyVal[1] || '';
  80. if(key !== 'Description' && key !== 'Language') {
  81. value = value.split(/\s*,\s*/);
  82. }
  83. result[key] = value;
  84. return result;
  85. }, {});
  86. }
  87. function filterByQualifiers(blob, languages, categories) {
  88. if(_.isEmpty(languages) && _.isEmpty(categories)) return true;
  89. let language = path.basename(blob.name, '.js'),
  90. fileInfo = parseHeader(blob.result),
  91. fileCategories = fileInfo.Category || [],
  92. containsCategory = _.partial(_.includes, categories);
  93. if(!fileInfo) return false;
  94. return _.includes(languages, language) ||
  95. _.some(fileCategories, containsCategory);
  96. }
  97. // For the filter task in `tools/tasks.js`, this function will look for
  98. // categories and languages specificed from the CLI.
  99. function buildFilterCallback(qualifiers) {
  100. const result = _.partition(qualifiers, { 0: ':' }),
  101. languages = result[1],
  102. categories = _.map(result[0], category => category.slice(1));
  103. return blob => filterByQualifiers(blob, languages, categories);
  104. }
  105. function globDefaults(pattern, encoding) {
  106. encoding = encoding || 'utf8';
  107. // The limit option is a fix for issue #636 when the build script would
  108. // EMFILE error for those systems who had a limit of open files per
  109. // process.
  110. //
  111. // <https://github.com/isagalaev/highlight.js/issues/636>
  112. return { pattern: pattern, limit: 50, encoding: encoding };
  113. }
  114. function getStyleNames() {
  115. let stylesDir = 'src/styles/',
  116. options = { ignore: `${stylesDir}default.css` };
  117. return glob(`${stylesDir}*.css`, options)
  118. .map(function(style) {
  119. let basename = path.basename(style, '.css'),
  120. name = _.startCase(basename),
  121. pathName = path.relative('src', style);
  122. return { path: pathName, name: name };
  123. });
  124. }
  125. function toQueue(tasks, registry) {
  126. return _.map(tasks, task => new Queue({ registry }).tasks(task));
  127. }
  128. module.exports = {
  129. buildFilterCallback: buildFilterCallback,
  130. getStyleNames: getStyleNames,
  131. glob: globDefaults,
  132. parseHeader: parseHeader,
  133. regex: regex,
  134. replace: replace,
  135. replaceClassNames: replaceClassNames,
  136. toQueue: toQueue
  137. };