markshell 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/markshell.js CHANGED
@@ -36,6 +36,51 @@ if (args.length === 0 || args.indexOf('--help') !== -1) {
36
36
  printHelp();
37
37
  }
38
38
 
39
+ // Handle --example flag to run interactive demos
40
+ const exampleIdx = args.indexOf('--example');
41
+ if (exampleIdx !== -1) {
42
+ const path = require('path');
43
+ const exampleName = args[exampleIdx + 1];
44
+
45
+ const examples = {
46
+ 'dynamic-loading': path.join(__dirname, '../examples/dynamic-loading-demo.js'),
47
+ 'syntax-colors': path.join(__dirname, '../examples/syntax-colors-demo.js'),
48
+ 'demo': path.join(__dirname, '../DEMO.md')
49
+ };
50
+
51
+ if (!exampleName || !examples[exampleName]) {
52
+ console.log(`Markshell - Version ${pkg.version}
53
+
54
+ ${chalk.yellow.bold('Available examples:')}
55
+
56
+ ${chalk.cyan('markshell --example dynamic-loading')}
57
+ Interactive demo showing dynamic language loading with statistics
58
+
59
+ ${chalk.cyan('markshell --example syntax-colors')}
60
+ Visual demonstration of syntax highlighting colors
61
+
62
+ ${chalk.cyan('markshell --example demo')}
63
+ Comprehensive demo showcasing all markdown features and 25+ languages
64
+ `);
65
+ process.exit(0);
66
+ }
67
+
68
+ const examplePath = examples[exampleName];
69
+
70
+ if (exampleName === 'demo') {
71
+ // Render the DEMO.md file
72
+ try {
73
+ markshell.toConsole(examplePath);
74
+ } catch (e) {
75
+ printError(e);
76
+ }
77
+ } else {
78
+ // Execute the example script
79
+ require(examplePath);
80
+ }
81
+ process.exit(0);
82
+ }
83
+
39
84
  const theme = args.indexOf('--theme');
40
85
 
41
86
  if (theme !== 1) {
package/docs/usage.md CHANGED
@@ -1,16 +1,34 @@
1
1
  # Usage
2
2
 
3
3
  ```bash
4
- markshell [filenpath | -f [filepath] | --filepath [filepath]]
4
+ markshell [filepath | -f [filepath] | --filepath [filepath]]
5
+ markshell --example [example-name]
6
+ markshell --help
5
7
  ```
6
8
 
7
- **filepath or -f **
9
+ ## Options
10
+
11
+ **filepath or -f**
8
12
  : path to markdown file
13
+
14
+ **--example [name]**
15
+ : run interactive examples and demos
16
+ - `dynamic-loading` - Shows dynamic language loading with statistics
17
+ - `syntax-colors` - Visual demonstration of syntax highlighting colors
18
+ - `demo` - Comprehensive showcase of all markdown features
19
+
9
20
  **--help**
10
- : what you see now
21
+ : display usage information
11
22
 
12
23
  ## Examples
13
24
 
14
25
  ```sh
26
+ # Render a markdown file
27
+ markshell ./my/markdownfile.md
15
28
  markshell --filepath './my/markdownfile.md'
16
- ```
29
+
30
+ # Run interactive examples
31
+ markshell --example dynamic-loading
32
+ markshell --example syntax-colors
33
+ markshell --example demo
34
+ ```
package/lib/index.js CHANGED
@@ -230,6 +230,9 @@ const _codeBlock = (content) => {
230
230
  * Formats source code blocks using PrismJS
231
231
  * @param {string} content of MarkDown file
232
232
  */
233
+ // Module-level storage for code block placeholders
234
+ let _codeBlockPlaceholders = [];
235
+
233
236
  const _highlightedCodeBlock = (content) => {
234
237
 
235
238
  let codeRegex = new RegExp(/(\`\`\`)(.*?)(\`\`\`)/igs);
@@ -239,6 +242,10 @@ const _highlightedCodeBlock = (content) => {
239
242
  return content;
240
243
  }
241
244
 
245
+ // Reset and store highlighted code blocks with placeholders to protect from inline code processing
246
+ _codeBlockPlaceholders = [];
247
+ let placeholderIndex = 0;
248
+
242
249
  newContent.forEach((element) => {
243
250
 
244
251
  let langRegex = new RegExp(/(\`\`\`)(.*?)(\r?\n)/igs);
@@ -259,7 +266,12 @@ const _highlightedCodeBlock = (content) => {
259
266
  let hlSource = syntaxHighlighter.highlight(source, lang, this._theme.sourceCodeTheme);
260
267
  this._theme.sourceCodeTheme;
261
268
 
262
- content = content.replace(element, hlSource)
269
+ // Use a placeholder that won't be matched by inline code regex
270
+ const placeholder = `__CODEBLOCK_${placeholderIndex}__`;
271
+ _codeBlockPlaceholders.push({ placeholder, code: hlSource });
272
+ placeholderIndex++;
273
+
274
+ content = content.replace(element, placeholder)
263
275
 
264
276
  } catch (e) {
265
277
 
@@ -275,6 +287,14 @@ const _highlightedCodeBlock = (content) => {
275
287
 
276
288
  }
277
289
 
290
+ const _restoreCodeBlocks = (content) => {
291
+ // Restore code blocks from placeholders
292
+ _codeBlockPlaceholders.forEach(({ placeholder, code }) => {
293
+ content = content.replace(placeholder, code);
294
+ });
295
+ return content;
296
+ }
297
+
278
298
  /**
279
299
  * Formats Blockquote
280
300
  * @param {string} content of markdown file
@@ -457,13 +477,23 @@ const _addBold = (content) => {
457
477
 
458
478
  /**
459
479
  * Formats italic elements in MarkDown
480
+ * Supports both underscore (_text_) and asterisk (*text*) emphasis markers
460
481
  * @param {string} content of markdown file
461
482
  */
462
483
  const _addItalic = (content) => {
463
484
 
464
- let regExp = new RegExp(/(?<!\w)_([^_\n]+?)_(?!\w)/g);
485
+ // First pass: Handle underscore emphasis
486
+ // Pattern: _text_ where underscores are not adjacent to word characters
487
+ let underscoreRegex = new RegExp(/(?<!\w)_([^_\n]+?)_(?!\w)/g);
488
+ content = _highlightText(content, underscoreRegex, this._theme.italic);
465
489
 
466
- return _highlightText(content, regExp, this._theme.italic);
490
+ // Second pass: Handle asterisk emphasis (but not bold **)
491
+ // Pattern: *text* where asterisks are not adjacent to other asterisks (to avoid matching **bold**)
492
+ // Also not adjacent to word characters on the outside to avoid matching mid-word asterisks
493
+ let asteriskRegex = new RegExp(/(?<!\*)(?<!\w)\*([^*\n]+?)\*(?!\*)(?!\w)/g);
494
+ content = _highlightText(content, asteriskRegex, this._theme.italic);
495
+
496
+ return content;
467
497
 
468
498
  }
469
499
 
@@ -493,8 +523,10 @@ const _addCode = (content) => {
493
523
  */
494
524
  const _addInlineCode = (content) => {
495
525
 
496
- // return _highlightText(content, /\`/ig, this._theme.inlineCode);
497
- return _highlightText(content, /`([^`]*)`/g, this._theme.inlineCode);
526
+ // Use negative lookbehind/lookahead to avoid matching backticks that are part of triple-backtick code blocks
527
+ // (?<!`) = not preceded by backtick, (?!`) = not followed by backtick
528
+ // This ensures we only match single backticks for inline code, not triple backticks for code blocks
529
+ return _highlightText(content, /(?<!`)`([^`]+)`(?!`)/g, this._theme.inlineCode);
498
530
 
499
531
  }
500
532
 
@@ -682,17 +714,26 @@ const _toRawContent = (filepath) => {
682
714
 
683
715
 
684
716
  try {
685
- // console.log('Inline code')
717
+ // Process code blocks FIRST - replaces them with placeholders
718
+ // This prevents inline code regex from matching backticks inside code blocks
719
+ content = _highlightedCodeBlock(content);
720
+ } catch (e) {
721
+ throw `Error in Highlighted Code: ${e}`;
722
+ }
723
+
724
+ try {
725
+ // Process inline code AFTER code blocks are replaced with placeholders
726
+ // At this point, code blocks are hidden as __CODEBLOCK_N__ so inline code won't match them
686
727
  content = _addInlineCode(content);
687
728
  } catch (e) {
688
729
  throw `Error in addInlineCode: ${e}`;
689
730
  }
690
731
 
691
732
  try {
692
- // console.log('Highlight code block');
693
- content = _highlightedCodeBlock(content);
733
+ // Restore code blocks from placeholders
734
+ content = _restoreCodeBlocks(content);
694
735
  } catch (e) {
695
- throw `Error in Highlighted Code: ${e}`;
736
+ throw `Error in restoring code blocks: ${e}`;
696
737
  }
697
738
 
698
739
  try {
@@ -1,5 +1,14 @@
1
1
  export var theme: any;
2
2
  export var themeTokenKeys: string[];
3
+
4
+ /**
5
+ * Information about loaded languages
6
+ */
7
+ export interface LoadedLanguagesInfo {
8
+ count: number;
9
+ languages: string[];
10
+ }
11
+
3
12
  /**
4
13
  *
5
14
  * @param {string} source
@@ -7,6 +16,20 @@ export var themeTokenKeys: string[];
7
16
  * @param {sourceTheme} outTheme
8
17
  */
9
18
  declare function _highlight(source: string, language: string, outTheme: sourceTheme): string;
19
+
20
+ /**
21
+ * Resolves language aliases to canonical names
22
+ * @param language - Language identifier (may be an alias)
23
+ * @returns Canonical language name or null
24
+ */
25
+ declare function _resolveLanguageAlias(language: string): string | null;
26
+
27
+ /**
28
+ * Get information about currently loaded languages
29
+ * @returns Statistics about loaded languages
30
+ */
31
+ declare function _getLoadedLanguagesInfo(): LoadedLanguagesInfo;
32
+
10
33
  type sourceTheme = string;
11
34
  declare namespace sourceTheme {
12
35
  const COY: string;
@@ -18,4 +41,11 @@ declare namespace sourceTheme {
18
41
  const TOMORROW: string;
19
42
  const TWILIGHT: string;
20
43
  }
21
- export { _highlight as highlight, sourceTheme as themes, sourceTheme as availableThemes };
44
+
45
+ export {
46
+ _highlight as highlight,
47
+ sourceTheme as themes,
48
+ sourceTheme as availableThemes,
49
+ _getLoadedLanguagesInfo as getLoadedLanguagesInfo,
50
+ _resolveLanguageAlias as resolveLanguageAlias
51
+ };
@@ -17,7 +17,75 @@ const prismjs = require('prismjs/prism');
17
17
  // load all supported languages
18
18
  // @ts-ignore
19
19
  const loadLanguages = require('prismjs/components/');
20
- loadLanguages();
20
+ // Languages will be loaded on-demand for better performance
21
+
22
+ /**
23
+ * Cache of loaded languages to avoid redundant loading
24
+ * @type {Set<string>}
25
+ */
26
+ const loadedLanguagesCache = new Set();
27
+
28
+ /**
29
+ * Comprehensive language alias mapping based on PrismJS components
30
+ * Maps common aliases to their canonical language names
31
+ * @type {Object<string, string>}
32
+ */
33
+ const languageAliases = {
34
+ // JavaScript ecosystem
35
+ 'js': 'javascript',
36
+ 'mjs': 'javascript',
37
+ 'cjs': 'javascript',
38
+ 'jsx': 'javascript',
39
+ 'ts': 'typescript',
40
+ 'tsx': 'typescript',
41
+
42
+ // Shell/Bash variants
43
+ 'sh': 'bash',
44
+ 'shell': 'bash',
45
+ 'console': 'bash',
46
+ 'command': 'bash',
47
+ 'bash session': 'bash',
48
+ 'zsh': 'bash',
49
+
50
+ // Python
51
+ 'py': 'python',
52
+ 'py3': 'python',
53
+ 'python3': 'python',
54
+
55
+ // Ruby
56
+ 'rb': 'ruby',
57
+
58
+ // Markup languages
59
+ 'html': 'markup',
60
+ 'xml': 'markup',
61
+ 'svg': 'markup',
62
+ 'mathml': 'markup',
63
+ 'ssml': 'markup',
64
+
65
+ // C family
66
+ 'c++': 'cpp',
67
+ 'c#': 'csharp',
68
+ 'cs': 'csharp',
69
+
70
+ // Other common aliases
71
+ 'yml': 'yaml',
72
+ 'md': 'markdown',
73
+ 'dockerfile': 'docker',
74
+ 'objc': 'objectivec',
75
+ 'hs': 'haskell',
76
+ 'rs': 'rust',
77
+ 'kt': 'kotlin',
78
+ 'proto': 'protobuf',
79
+ 'coffee': 'coffeescript',
80
+ 'gawk': 'awk',
81
+ 'hbs': 'handlebars',
82
+ 'mustache': 'handlebars',
83
+ 'g4': 'antlr4',
84
+ 'ino': 'arduino',
85
+ 'adoc': 'asciidoc',
86
+ 'idr': 'idris',
87
+ 'eta': 'ejs',
88
+ };
21
89
 
22
90
  /**
23
91
  * @readonly
@@ -50,9 +118,9 @@ const getHighlightToken = (tokens) => {
50
118
 
51
119
  for (let i = 0; i < tokens.length; i++) {
52
120
 
53
- if (this.themeTokenKeys.indexOf(tokens[i]) !== -1) {
121
+ if (themeTokenKeys.indexOf(tokens[i]) !== -1) {
54
122
 
55
- tokenFound = this.theme.token[tokens[i]];
123
+ tokenFound = theme.token[tokens[i]];
56
124
  break;
57
125
  }
58
126
 
@@ -132,7 +200,7 @@ const _addBackground = (source, originalSource) => {
132
200
  }
133
201
 
134
202
  bgAddedSource.push(
135
- this.theme.background(sourceLines[i]) + this.theme.toEOL((" ").repeat(fill2end))
203
+ theme.background(sourceLines[i]) + theme.toEOL((" ").repeat(fill2end))
136
204
  );
137
205
 
138
206
  }
@@ -142,14 +210,88 @@ const _addBackground = (source, originalSource) => {
142
210
  }
143
211
 
144
212
  /**
145
- *
146
- * @param {string} source
147
- * @param {string} language
148
- * @param {sourceTheme} outTheme
213
+ * Resolves language aliases to canonical names
214
+ * @param {string} language - Language identifier (may be an alias)
215
+ * @returns {string|null} - Canonical language name or null if no language provided
216
+ */
217
+ const resolveLanguageAlias = (language) => {
218
+ if (!language) return null;
219
+
220
+ const normalized = language.toLowerCase().trim();
221
+ return languageAliases[normalized] || normalized;
222
+ };
223
+
224
+ /**
225
+ * Dynamically loads a language component if not already loaded
226
+ * @param {string} language - Language to load (canonical name or alias)
227
+ * @returns {boolean} - True if language is available, false otherwise
228
+ */
229
+ const ensureLanguageLoaded = (language) => {
230
+ if (!language) return false;
231
+
232
+ // Resolve alias to canonical name
233
+ const canonicalLang = resolveLanguageAlias(language);
234
+ if (!canonicalLang) return false;
235
+
236
+ // Check if already loaded in our cache
237
+ if (loadedLanguagesCache.has(canonicalLang)) {
238
+ return true;
239
+ }
240
+
241
+ // Check if language exists in Prism.languages (might be pre-loaded)
242
+ // @ts-ignore
243
+ if (Prism.languages[canonicalLang] !== undefined) {
244
+ loadedLanguagesCache.add(canonicalLang);
245
+ return true;
246
+ }
247
+
248
+ // Attempt to load the language
249
+ try {
250
+ // Suppress PrismJS warnings for this operation
251
+ const originalSilent = loadLanguages.silent;
252
+ loadLanguages.silent = true;
253
+
254
+ // Load the language (PrismJS handles dependencies automatically)
255
+ loadLanguages([canonicalLang]);
256
+
257
+ // Restore original silent setting
258
+ loadLanguages.silent = originalSilent;
259
+
260
+ // Verify it loaded successfully
261
+ // @ts-ignore
262
+ if (Prism.languages[canonicalLang] !== undefined) {
263
+ loadedLanguagesCache.add(canonicalLang);
264
+ return true;
265
+ }
266
+
267
+ return false;
268
+
269
+ } catch (error) {
270
+ // Language loading failed
271
+ return false;
272
+ }
273
+ };
274
+
275
+ /**
276
+ * Get information about loaded languages (for debugging/monitoring)
277
+ * @returns {{count: number, languages: string[]}} - Statistics about loaded languages
278
+ */
279
+ const getLoadedLanguagesInfo = () => {
280
+ return {
281
+ count: loadedLanguagesCache.size,
282
+ languages: Array.from(loadedLanguagesCache).sort()
283
+ };
284
+ };
285
+
286
+ /**
287
+ *
288
+ * @param {string} source
289
+ * @param {string} language
290
+ * @param {sourceTheme} outTheme
149
291
  */
150
292
  const _highlight = (source, language, outTheme) => {
151
293
 
152
- // Detect if theme value is supported - otherwise just use default Okaida theme
294
+ // Detect if theme value is supported - otherwise just use default Okaidia theme
153
295
  if (outTheme !== undefined) {
154
296
 
155
297
  let themePath = path.join(__dirname, './themes/', outTheme + '.theme'),
@@ -157,48 +299,51 @@ const _highlight = (source, language, outTheme) => {
157
299
 
158
300
  if (fs.existsSync(filePath)) {
159
301
 
160
- this.theme = require(filePath);
161
- this.themeTokenKeys = Object.keys(theme.token);
162
-
302
+ theme = require(filePath);
303
+ themeTokenKeys = Object.keys(theme.token);
163
304
 
164
305
  } else {
165
306
 
166
- throw `Theme '${outTheme}' do not exists`
307
+ throw `Theme '${outTheme}' do not exist`
167
308
 
168
309
  }
169
310
 
170
311
  }
171
312
 
172
- // Parse source code and return HTML from PrismJS output
173
- // console.log(language, Prism.languages[language]);
174
- // @ts-ignore
175
- Prism.languages['sh'] = Prism.languages['shell'];
176
- // @ts-ignore
177
- Prism.languages['console'] = Prism.languages['shell'];
178
- // @ts-ignore
179
- Prism.languages['command'] = Prism.languages['shell'];
180
- // @ts-ignore
181
- Prism.languages['bash session'] = Prism.languages['shell'];
313
+ // If no language specified, render as plain text
314
+ if (!language) {
315
+ return _addBackground(source, source);
316
+ }
182
317
 
183
- // @ts-ignore
184
- if (Prism.languages[language] !== undefined) {
318
+ // Dynamically load the language if needed
319
+ const languageLoaded = ensureLanguageLoaded(language);
320
+
321
+ if (!languageLoaded) {
322
+ // Language doesn't exist or failed to load - fallback to plain text
323
+ // Optionally log a warning (can be configured via environment variable)
324
+ if (process.env.MARKSHELL_WARN_UNKNOWN_LANG === 'true') {
325
+ console.warn(`[markshell] Unknown language '${language}' - rendering as plain text`);
326
+ }
327
+ return _addBackground(source, source);
328
+ }
329
+
330
+ // Get the canonical language name for highlighting
331
+ const canonicalLang = resolveLanguageAlias(language);
332
+
333
+ // @ts-ignore - Prism is loaded globally
334
+ if (Prism.languages[canonicalLang] !== undefined) {
185
335
  // @ts-ignore
186
- const prismCode = prismjs.highlight(source, Prism.languages[language], language);
336
+ const prismCode = prismjs.highlight(source, Prism.languages[canonicalLang], canonicalLang);
187
337
  // load HTML fragment
188
338
  const dom = JSDOM.fragment(prismCode);
189
339
 
190
- // console.log('Original Source:\n', source);
191
-
192
340
  var highlightedSource = parseFormatedContent(dom.childNodes, 0);
193
341
 
194
342
  return _addBackground(highlightedSource, source);
195
343
 
196
344
  } else {
197
-
345
+ // Shouldn't reach here if ensureLanguageLoaded worked correctly
198
346
  return _addBackground(source, source);
199
-
200
- // throw `Language '${language}' couldn't be found`;
201
-
202
347
  }
203
348
 
204
349
  }
@@ -206,5 +351,7 @@ const _highlight = (source, language, outTheme) => {
206
351
  module.exports = {
207
352
  highlight: _highlight,
208
353
  themes: sourceTheme,
209
- availableThemes: sourceTheme
354
+ availableThemes: sourceTheme,
355
+ getLoadedLanguagesInfo: getLoadedLanguagesInfo,
356
+ resolveLanguageAlias: resolveLanguageAlias
210
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markshell",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "markshell allows you to output any markdown file formatted and style to the console",
5
5
  "keywords": [
6
6
  "markdown",