Hljs to highlight.php json

JavaScript Guest 54 Views Size: 6.32 KB Posted on: Sep 7, 25 @ 10:30 AM
  1. 1#!/usr/bin/env node
  2. 2// convert-hljs-to-json.mjs
  3. 3//
  4. 4// Usage:
  5. 5// node convert-hljs-to-json.mjs input.js output.json [--id mirc] [--name "mIRC"] [--aliases mrc,mircs]
  6. 6
  7. 7import fs from 'fs';
  8. 8import vm from 'vm';
  9. 9import path from 'path';
  10. 10
  11. 11const argv = process.argv.slice(2);
  12. 12if (argv.length < 2) {
  13. 13 console.error('Usage: node convert-hljs-to-json.mjs input.js output.json [--id id] [--name "Name"] [--aliases a,b,c]');
  14. 14 process.exit(1);
  15. 15}
  16. 16const inFile = argv[0];
  17. 17const outFile = argv[1];
  18. 18
  19. 19function readOpt(flag) {
  20. 20 const i = argv.indexOf(flag);
  21. 21 return (i !== -1 && argv[i+1]) ? argv[i+1] : null;
  22. 22}
  23. 23const optId = readOpt('--id');
  24. 24const optName = readOpt('--name');
  25. 25const optAliasesS = readOpt('--aliases');
  26. 26const optAliases = optAliasesS ? optAliasesS.split(',').map(s => s.trim()).filter(Boolean) : null;
  27. 27
  28. 28// --- Minimal hljs stub with common modes & helpers
  29. 29function makeHLJS() {
  30. 30 const BACKSLASH_ESCAPE = { begin: /\\[\s\S]/ };
  31. 31
  32. 32 const APOS_STRING_MODE = {
  33. 33 className: 'string',
  34. 34 begin: /'/, end: /'/, illegal: /\n/,
  35. 35 contains: [BACKSLASH_ESCAPE]
  36. 36 };
  37. 37
  38. 38 const QUOTE_STRING_MODE = {
  39. 39 className: 'string',
  40. 40 begin: /"/, end: /"/, illegal: /\n/,
  41. 41 contains: [BACKSLASH_ESCAPE]
  42. 42 };
  43. 43
  44. 44 const NUMBER_MODE = {
  45. 45 className: 'number',
  46. 46 begin: /\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/,
  47. 47 relevance: 0
  48. 48 };
  49. 49
  50. 50 function inherit(obj, props={}) { return Object.assign({}, obj, props); }
  51. 51
  52. 52 let captured = null;
  53. 53 function registerLanguage(name, defOrFn) {
  54. 54 let def = defOrFn;
  55. 55 if (typeof defOrFn === 'function') def = defOrFn(hljs);
  56. 56 captured = { name, def };
  57. 57 }
  58. 58
  59. 59 const hljs = {
  60. 60 BACKSLASH_ESCAPE,
  61. 61 APOS_STRING_MODE,
  62. 62 QUOTE_STRING_MODE,
  63. 63 NUMBER_MODE,
  64. 64 inherit,
  65. 65 registerLanguage
  66. 66 };
  67. 67 Object.defineProperty(hljs, '__captured', { get() { return captured; } });
  68. 68 return hljs;
  69. 69}
  70. 70
  71. 71// --- Load & execute the language module in a sandbox
  72. 72async function loadLanguageDef(modulePath) {
  73. 73 let src = await fs.promises.readFile(modulePath, 'utf8');
  74. 74
  75. 75 // Simple ESM default export shims
  76. 76 if (/\bexport\s+default\s+function\b/.test(src)) {
  77. 77 src = src.replace(/\bexport\s+default\s+function\b/, 'module.exports = function');
  78. 78 } else if (/\bexport\s+default\b/.test(src)) {
  79. 79 src = src.replace(/\bexport\s+default\b/, 'module.exports =');
  80. 80 }
  81. 81
  82. 82 const hljs = makeHLJS();
  83. 83 const sandbox = {
  84. 84 module: { exports: {} },
  85. 85 exports: {},
  86. 86 hljs,
  87. 87 require: () => { throw new Error('require() disabled in sandbox'); },
  88. 88 __dirname: path.dirname(modulePath),
  89. 89 __filename: path.resolve(modulePath),
  90. 90 console
  91. 91 };
  92. 92
  93. 93 vm.createContext(sandbox, { name: 'hljs-lang-sandbox' });
  94. 94 try {
  95. 95 vm.runInContext(src, sandbox, {
  96. 96 filename: modulePath,
  97. 97 displayErrors: true,
  98. 98 timeout: 5000
  99. 99 });
  100. 100 } catch (e) {
  101. 101 throw new Error(`Execution error in ${modulePath}: ${e.message}`);
  102. 102 }
  103. 103
  104. 104 // Get a definition
  105. 105 let def = null;
  106. 106 let nameHint = null;
  107. 107
  108. 108 if (typeof sandbox.module.exports === 'function') {
  109. 109 const maybe = sandbox.module.exports(sandbox.hljs);
  110. 110 if (maybe && typeof maybe === 'object') def = maybe;
  111. 111 } else if (sandbox.module.exports && typeof sandbox.module.exports === 'object') {
  112. 112 def = sandbox.module.exports;
  113. 113 }
  114. 114 if (!def && sandbox.hljs.__captured) {
  115. 115 def = sandbox.hljs.__captured.def;
  116. 116 nameHint = sandbox.hljs.__captured.name || null;
  117. 117 }
  118. 118 if (!def || typeof def !== 'object') {
  119. 119 throw new Error('Could not obtain a language definition object from the module.');
  120. 120 }
  121. 121 return { def, nameHint };
  122. 122}
  123. 123
  124. 124// --- Helpers: robust RegExp detection across VM realms
  125. 125const toString = Object.prototype.toString;
  126. 126function isRegExp(x) { return toString.call(x) === '[object RegExp]'; }
  127. 127
  128. 128// Deep convert RegExp to strings
  129. 129function toPlain(obj) {
  130. 130 if (obj == null) return obj;
  131. 131 if (isRegExp(obj)) return obj.source;
  132. 132 if (Array.isArray(obj)) return obj.map(toPlain);
  133. 133 if (typeof obj !== 'object') return obj;
  134. 134
  135. 135 const out = {};
  136. 136 for (const [k, v] of Object.entries(obj)) {
  137. 137 out[k] = toPlain(v);
  138. 138 }
  139. 139 return out;
  140. 140}
  141. 141
  142. 142// Normalize classes & structure
  143. 143function normalizeClassNames(node) {
  144. 144 if (!node || typeof node !== 'object') return;
  145. 145 if (Array.isArray(node)) { node.forEach(normalizeClassNames); return; }
  146. 146
  147. 147 const map = { code: 'meta' };
  148. 148 if (node.className && map[node.className]) node.className = map[node.className];
  149. 149
  150. 150 if (!node.className && node.scope) { node.className = node.scope; delete node.scope; }
  151. 151
  152. 152 for (const key of ['contains','variants','starts']) {
  153. 153 if (node[key]) normalizeClassNames(node[key]);
  154. 154 }
  155. 155}
  156. 156
  157. 157function normalizeKeywords(kw) {
  158. 158 if (!kw) return kw;
  159. 159 if (typeof kw === 'string') return kw;
  160. 160 if (Array.isArray(kw)) return kw.join(' ');
  161. 161 if (typeof kw === 'object') {
  162. 162 return Object.values(kw)
  163. 163 .map(v => Array.isArray(v) ? v.join(' ') : String(v))
  164. 164 .join(' ')
  165. 165 .trim();
  166. 166 }
  167. 167 return String(kw);
  168. 168}
  169. 169
  170. 170function finalize(def, idOpt, nameOpt, nameHint, aliasesOpt) {
  171. 171 const plain = toPlain(def);
  172. 172 normalizeClassNames(plain);
  173. 173 if (plain.keywords) plain.keywords = normalizeKeywords(plain.keywords);
  174. 174
  175. 175 const out = {};
  176. 176 if (nameOpt) out.name = nameOpt;
  177. 177 else if (plain.name) out.name = plain.name;
  178. 178 else if (nameHint) out.name = nameHint;
  179. 179
  180. 180 if (idOpt) out.id = idOpt;
  181. 181 if (aliasesOpt) out.aliases = aliasesOpt;
  182. 182
  183. 183 // Copy common top-level fields
  184. 184 const copyKeys = new Set([
  185. 185 'aliases','case_insensitive','keywords','contains','illegal','variants',
  186. 186 'begin','end','className','relevance','lexemes','match','starts'
  187. 187 ]);
  188. 188 for (const [k, v] of Object.entries(plain)) {
  189. 189 if (k in out) continue;
  190. 190 if (copyKeys.has(k)) out[k] = v;
  191. 191 }
  192. 192
  193. 193 if (out.aliases && !Array.isArray(out.aliases)) {
  194. 194 out.aliases = String(out.aliases).split(',').map(s => s.trim()).filter(Boolean);
  195. 195 }
  196. 196 return out;
  197. 197}
  198. 198
  199. 199(async () => {
  200. 200 try {
  201. 201 const { def, nameHint } = await loadLanguageDef(inFile);
  202. 202 const result = finalize(def, optId, optName, nameHint, optAliases);
  203. 203 await fs.promises.writeFile(outFile, JSON.stringify(result, null, 2), 'utf8');
  204. 204 console.log(`Wrote ${path.basename(outFile)}${result.id ? ` (id: ${result.id}` : ''}${result.name ? `${result.id ? ', ' : ' ('}name: ${result.name}` : ''}${(result.id || result.name) ? ')' : ''}`);
  205. 205 } catch (e) {
  206. 206 console.error(e.message);
  207. 207 process.exit(2);
  208. 208 }
  209. 209})();

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.