mepcli 1.0.1 → 1.1.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.
@@ -1 +1,7 @@
1
1
  export declare function highlightJson(json: string): string;
2
+ export declare function highlightEnv(env: string): string;
3
+ export declare function highlightToml(toml: string): string;
4
+ export declare function highlightCsv(csv: string): string;
5
+ export declare function highlightShell(script: string): string;
6
+ export declare function highlightProperties(props: string): string;
7
+ export declare function highlight(code: string, language: string): string;
package/dist/highlight.js CHANGED
@@ -1,39 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.highlightJson = highlightJson;
4
+ exports.highlightEnv = highlightEnv;
5
+ exports.highlightToml = highlightToml;
6
+ exports.highlightCsv = highlightCsv;
7
+ exports.highlightShell = highlightShell;
8
+ exports.highlightProperties = highlightProperties;
9
+ exports.highlight = highlight;
4
10
  const ansi_1 = require("./ansi");
5
11
  const theme_1 = require("./theme");
12
+ // --- 1. JSON Highlighter ---
6
13
  function highlightJson(json) {
7
14
  if (!json)
8
15
  return '';
9
- // Updated Regex for better partial matching and separation
10
- // 1. Strings (keys or values), allowing unclosed quotes for partial typing
11
- // 2. Numbers
12
- // 3. Booleans/Null
13
- // 4. Punctuation (separated colon from keys logic)
14
- // Regex explanation:
15
- // ("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"?) -> Captures strings, optionally unclosed at the end
16
- // (-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?) -> Captures numbers
17
- // (true|false|null) -> Captures keywords
18
- // ([{}[\],:]) -> Captures punctuation
19
16
  const tokenRegex = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"?)|(-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)|(true|false|null)|([{}[\],:])/g;
20
17
  let result = '';
21
18
  let lastIndex = 0;
22
19
  let match;
23
20
  while ((match = tokenRegex.exec(json)) !== null) {
24
- // Add any non-matching text (whitespace/invalid) as-is
25
21
  if (match.index > lastIndex) {
26
22
  result += json.substring(lastIndex, match.index);
27
23
  }
28
24
  const token = match[0];
29
- // 1. Strings (Key or Value determination is context-based, but here we do simple check)
30
- // Note: In strict JSON, keys are strings followed by :, but since we separated colon,
31
- // we can't easily distinguish keys just by regex lookahead safely without lookbehind support.
32
- // However, for visual highlighting, colouring all strings consistently is often acceptable
33
- // or we can try to peek ahead.
34
25
  if (token.startsWith('"')) {
35
- // Simple heuristic: If the NEXT non-whitespace char is ':', treat as key
36
26
  const remaining = json.substring(tokenRegex.lastIndex);
27
+ // Heuristic for keys: followed by optional whitespace and a colon
37
28
  if (/^\s*:/.test(remaining)) {
38
29
  result += `${theme_1.theme.syntax.key}${token}${ansi_1.ANSI.RESET}`;
39
30
  }
@@ -41,32 +32,146 @@ function highlightJson(json) {
41
32
  result += `${theme_1.theme.syntax.string}${token}${ansi_1.ANSI.RESET}`;
42
33
  }
43
34
  }
44
- // 2. Numbers
45
35
  else if (/^-?\d/.test(token)) {
46
36
  result += `${theme_1.theme.syntax.number}${token}${ansi_1.ANSI.RESET}`;
47
37
  }
48
- // 3. Booleans/Null
49
38
  else if (/^(true|false|null)$/.test(token)) {
50
- if (token === 'null') {
51
- result += `${theme_1.theme.syntax.null}${token}${ansi_1.ANSI.RESET}`;
52
- }
53
- else {
54
- result += `${theme_1.theme.syntax.boolean}${token}${ansi_1.ANSI.RESET}`;
55
- }
39
+ result += (token === 'null')
40
+ ? `${theme_1.theme.syntax.null}${token}${ansi_1.ANSI.RESET}`
41
+ : `${theme_1.theme.syntax.boolean}${token}${ansi_1.ANSI.RESET}`;
56
42
  }
57
- // 4. Punctuation
58
43
  else if (/^[{}[\],:]$/.test(token)) {
59
44
  result += `${theme_1.theme.syntax.punctuation}${token}${ansi_1.ANSI.RESET}`;
60
45
  }
61
- // Fallback
62
46
  else {
63
47
  result += token;
64
48
  }
65
49
  lastIndex = tokenRegex.lastIndex;
66
50
  }
67
- // Append remaining text
68
51
  if (lastIndex < json.length) {
69
52
  result += json.substring(lastIndex);
70
53
  }
71
54
  return result;
72
55
  }
56
+ // --- 2. ENV Highlighter ---
57
+ function highlightEnv(env) {
58
+ if (!env)
59
+ return '';
60
+ return env.split('\n').map(line => {
61
+ if (line.trim().startsWith('#')) {
62
+ return `${theme_1.theme.muted}${line}${ansi_1.ANSI.RESET}`;
63
+ }
64
+ const match = line.match(/^([^=]+)=(.*)$/);
65
+ if (match) {
66
+ const [, key, value] = match;
67
+ return `${theme_1.theme.syntax.key}${key}${theme_1.theme.syntax.punctuation}=${theme_1.theme.syntax.string}${value}${ansi_1.ANSI.RESET}`;
68
+ }
69
+ return line;
70
+ }).join('\n');
71
+ }
72
+ // --- 3. TOML Highlighter ---
73
+ function highlightToml(toml) {
74
+ if (!toml)
75
+ return '';
76
+ return toml.split('\n').map(line => {
77
+ const trimmed = line.trim();
78
+ if (trimmed.startsWith('#')) {
79
+ return `${theme_1.theme.muted}${line}${ansi_1.ANSI.RESET}`;
80
+ }
81
+ // [section]
82
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
83
+ return `${theme_1.theme.syntax.key}${line}${ansi_1.ANSI.RESET}`;
84
+ }
85
+ // Key = Value
86
+ const match = line.match(/^(\s*[\w\d_-]+)(\s*=)/);
87
+ if (match) {
88
+ const fullMatch = match[0];
89
+ const keyPart = match[1];
90
+ const eqPart = match[2];
91
+ const rest = line.substring(fullMatch.length);
92
+ return `${theme_1.theme.syntax.key}${keyPart}${ansi_1.ANSI.RESET}${theme_1.theme.syntax.punctuation}${eqPart}${theme_1.theme.syntax.string}${rest}${ansi_1.ANSI.RESET}`;
93
+ }
94
+ return line;
95
+ }).join('\n');
96
+ }
97
+ // --- 4. CSV Highlighter ---
98
+ function highlightCsv(csv) {
99
+ if (!csv)
100
+ return '';
101
+ // Cycle through colors to differentiate columns
102
+ const colors = [theme_1.theme.syntax.string, theme_1.theme.syntax.key, theme_1.theme.syntax.number, theme_1.theme.syntax.boolean];
103
+ return csv.split('\n').map(line => {
104
+ if (!line.trim())
105
+ return line;
106
+ // Split by comma, ignoring commas inside double quotes
107
+ // Regex explanation: Match comma only if followed by an even number of quotes (or 0) until end of line
108
+ const parts = line.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
109
+ return parts.map((part, index) => {
110
+ const color = colors[index % colors.length];
111
+ return `${color}${part}${ansi_1.ANSI.RESET}`;
112
+ }).join(`${theme_1.theme.syntax.punctuation},${ansi_1.ANSI.RESET}`);
113
+ }).join('\n');
114
+ }
115
+ // --- 5. Shell/Bash Highlighter ---
116
+ function highlightShell(script) {
117
+ if (!script)
118
+ return '';
119
+ // Simple replacements for common shell patterns
120
+ // 1. Comments
121
+ // 2. Keywords
122
+ // 3. Variables
123
+ const keywords = /\b(if|then|else|elif|fi|for|in|do|done|while|case|esac|return|exit|export|source|echo|printf)\b/g;
124
+ return script.split('\n').map(line => {
125
+ if (line.trim().startsWith('#')) {
126
+ return `${theme_1.theme.muted}${line}${ansi_1.ANSI.RESET}`;
127
+ }
128
+ // Temporarily hide strings properly is hard with regex replace,
129
+ // so we just do best-effort highlights for keywords and vars outside of checking quote context.
130
+ const processed = line
131
+ .replace(keywords, match => `${theme_1.theme.syntax.boolean}${match}${ansi_1.ANSI.RESET}`)
132
+ .replace(/(\$[\w\d_]+|\$\{[^}]+\})/g, match => `${theme_1.theme.syntax.key}${match}${ansi_1.ANSI.RESET}`)
133
+ .replace(/(\s|^)(-{1,2}[a-zA-Z0-9_-]+)/g, (_match, prefix, flag) => `${prefix}${theme_1.theme.syntax.number}${flag}${ansi_1.ANSI.RESET}`);
134
+ return processed;
135
+ }).join('\n');
136
+ }
137
+ // --- 6. Properties Highlighter ---
138
+ function highlightProperties(props) {
139
+ if (!props)
140
+ return '';
141
+ return props.split('\n').map(line => {
142
+ const trimmed = line.trim();
143
+ // Supports # or ! for comments
144
+ if (trimmed.startsWith('#') || trimmed.startsWith('!')) {
145
+ return `${theme_1.theme.muted}${line}${ansi_1.ANSI.RESET}`;
146
+ }
147
+ // Keys can be separated by = or :
148
+ const match = line.match(/^([^=:]+)([=:])(.*)$/);
149
+ if (match) {
150
+ const [, key, sep, value] = match;
151
+ return `${theme_1.theme.syntax.key}${key}${theme_1.theme.syntax.punctuation}${sep}${theme_1.theme.syntax.string}${value}${ansi_1.ANSI.RESET}`;
152
+ }
153
+ return line;
154
+ }).join('\n');
155
+ }
156
+ // --- Main Mapping ---
157
+ const highlighters = {
158
+ 'json': highlightJson,
159
+ 'env': highlightEnv,
160
+ 'toml': highlightToml,
161
+ 'csv': highlightCsv,
162
+ 'sh': highlightShell,
163
+ 'bash': highlightShell,
164
+ 'zsh': highlightShell,
165
+ 'properties': highlightProperties,
166
+ 'props': highlightProperties,
167
+ 'conf': highlightProperties // loose convention
168
+ };
169
+ function highlight(code, language) {
170
+ const lang = language.toLowerCase();
171
+ const highlightFunc = highlighters[lang];
172
+ if (highlightFunc) {
173
+ return highlightFunc(code);
174
+ }
175
+ // Fallback: If no highlighter found, return original code
176
+ return code;
177
+ }
@@ -5,6 +5,7 @@ const ansi_1 = require("../ansi");
5
5
  const base_1 = require("../base");
6
6
  const theme_1 = require("../theme");
7
7
  const utils_1 = require("../utils");
8
+ const highlight_1 = require("../highlight");
8
9
  class CodePrompt extends base_1.Prompt {
9
10
  constructor(options) {
10
11
  super(options);
@@ -76,44 +77,58 @@ class CodePrompt extends base_1.Prompt {
76
77
  let highlighted = '';
77
78
  const shouldHighlight = this.options.highlight !== false;
78
79
  if (shouldHighlight) {
79
- const jsonTokenRegex = /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"?)|(-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)|(true|false|null)|([{}[\],:])/g;
80
- let match;
81
- let lastIndex = 0;
82
- while ((match = jsonTokenRegex.exec(fullRawText)) !== null) {
83
- if (match.index > lastIndex) {
84
- const gap = fullRawText.substring(lastIndex, match.index);
85
- this.appendSegment(gap, lastIndex, activeVarStart, activeVarEnd, ansi_1.ANSI.FG_WHITE, (s) => highlighted += s);
86
- }
87
- const tokenText = match[0];
88
- const tokenStart = match.index;
89
- let color = theme_1.theme.syntax?.punctuation || ansi_1.ANSI.FG_WHITE;
90
- if (tokenText.startsWith('"')) {
91
- const remaining = fullRawText.substring(jsonTokenRegex.lastIndex);
92
- if (/^\s*:/.test(remaining)) {
93
- color = theme_1.theme.syntax?.key || ansi_1.ANSI.FG_CYAN;
80
+ const lang = this.options.language || 'json';
81
+ const highlightedText = (0, highlight_1.highlight)(fullRawText, lang);
82
+ let visibleIdx = 0;
83
+ let activeColor = ''; // Tracks the last set color
84
+ for (let i = 0; i < highlightedText.length; i++) {
85
+ const char = highlightedText[i];
86
+ if (char === '\x1b') {
87
+ // Start of ANSI sequence
88
+ let sequence = char;
89
+ i++;
90
+ while (i < highlightedText.length && highlightedText[i] !== 'm') {
91
+ sequence += highlightedText[i];
92
+ i++;
93
+ }
94
+ if (i < highlightedText.length)
95
+ sequence += 'm'; // Append terminator
96
+ // Interpret sequence (naive)
97
+ if (sequence === ansi_1.ANSI.RESET || sequence === '\x1b[0m') {
98
+ activeColor = '';
94
99
  }
95
100
  else {
96
- color = theme_1.theme.syntax?.string || ansi_1.ANSI.FG_GREEN;
101
+ // Assuming it's a color code.
102
+ activeColor = sequence;
97
103
  }
104
+ // If we are INSIDE the active range, and we encounter a color change/reset,
105
+ // we must ensure UNDERLINE is kept/restored.
106
+ if (visibleIdx >= activeVarStart && visibleIdx < activeVarEnd) {
107
+ // Output the sequence (e.g. a color change)
108
+ highlighted += sequence;
109
+ // Then re-assert underline
110
+ highlighted += ansi_1.ANSI.UNDERLINE;
111
+ }
112
+ else {
113
+ highlighted += sequence;
114
+ }
115
+ continue;
98
116
  }
99
- else if (/^-?\d/.test(tokenText)) {
100
- color = theme_1.theme.syntax?.number || ansi_1.ANSI.FG_YELLOW;
101
- }
102
- else if (/^(true|false|null)$/.test(tokenText)) {
103
- color = (tokenText === 'null')
104
- ? (theme_1.theme.syntax?.null || ansi_1.ANSI.FG_RED)
105
- : (theme_1.theme.syntax?.boolean || ansi_1.ANSI.FG_MAGENTA);
117
+ // Normal char
118
+ if (visibleIdx === activeVarStart) {
119
+ highlighted += `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}`;
106
120
  }
107
- else if (/^[{}[\],:]$/.test(tokenText)) {
108
- color = theme_1.theme.syntax?.punctuation || ansi_1.ANSI.FG_WHITE;
121
+ highlighted += char;
122
+ visibleIdx++;
123
+ if (visibleIdx === activeVarEnd) {
124
+ highlighted += ansi_1.ANSI.RESET;
125
+ // Restore previous color if any
126
+ if (activeColor) {
127
+ highlighted += activeColor;
128
+ }
109
129
  }
110
- this.appendSegment(tokenText, tokenStart, activeVarStart, activeVarEnd, color, (s) => highlighted += s);
111
- lastIndex = jsonTokenRegex.lastIndex;
112
- }
113
- if (lastIndex < fullRawText.length) {
114
- const tail = fullRawText.substring(lastIndex);
115
- this.appendSegment(tail, lastIndex, activeVarStart, activeVarEnd, ansi_1.ANSI.FG_WHITE, (s) => highlighted += s);
116
130
  }
131
+ highlighted += ansi_1.ANSI.RESET;
117
132
  }
118
133
  else {
119
134
  this.appendSegment(fullRawText, 0, activeVarStart, activeVarEnd, ansi_1.ANSI.RESET, (s) => highlighted += s);
package/dist/types.d.ts CHANGED
@@ -190,7 +190,7 @@ export interface WaitOptions extends BaseOptions {
190
190
  }
191
191
  export interface CodeOptions extends BaseOptions {
192
192
  template: string;
193
- language?: 'json';
193
+ language?: 'json' | 'env' | 'toml' | 'csv' | 'sh' | 'bash' | 'zsh' | 'properties' | 'props' | 'conf';
194
194
  /**
195
195
  * Enable syntax highlighting (Experimental).
196
196
  * @default true
package/dist/utils.js CHANGED
@@ -20,7 +20,7 @@ function detectCapabilities() {
20
20
  // Check if it is a TTY
21
21
  const isTTY = process.stdout.isTTY;
22
22
  const isWindows = process.platform === 'win32';
23
- // Logic detect Unicode xịn hơn
23
+ // Better Unicode detection logic
24
24
  const isUnicodeSupported = () => {
25
25
  // 1. Windows: Check specific environmental variables
26
26
  if (isWindows) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mepcli",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Zero-dependency, interactive CLI prompt",
5
5
  "repository": {
6
6
  "type": "git",