mepcli 1.0.0 → 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
+ }
@@ -12,5 +12,6 @@ export declare class CalculatorPrompt extends Prompt<number, CalculatorOptions>
12
12
  private evaluate;
13
13
  protected render(firstRender: boolean): void;
14
14
  private getVisualCursorPosition;
15
+ protected cleanup(): void;
15
16
  protected handleInput(char: string): void;
16
17
  }
@@ -147,6 +147,13 @@ class CalculatorPrompt extends base_1.Prompt {
147
147
  }
148
148
  return width;
149
149
  }
150
+ cleanup() {
151
+ if (this.lastLinesUp > 0) {
152
+ this.print(`\x1b[${this.lastLinesUp}B`);
153
+ this.lastLinesUp = 0;
154
+ }
155
+ super.cleanup();
156
+ }
150
157
  handleInput(char) {
151
158
  // Enter
152
159
  if (char === '\r' || char === '\n') {
@@ -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);
@@ -31,6 +31,7 @@ export declare class CurlPrompt extends Prompt<CurlResult, CurlOptions> {
31
31
  private shellEscapeDoubleQuoted;
32
32
  private generateCommand;
33
33
  protected render(firstRender: boolean): void;
34
+ protected cleanup(): void;
34
35
  protected handleInput(char: string, _buffer: Buffer): void;
35
36
  private cycleSection;
36
37
  private editHeaders;
@@ -155,6 +155,13 @@ class CurlPrompt extends base_1.Prompt {
155
155
  this.print(ansi_1.ANSI.HIDE_CURSOR);
156
156
  }
157
157
  }
158
+ cleanup() {
159
+ if (this.lastLinesUp > 0) {
160
+ this.print(`\x1b[${this.lastLinesUp}B`);
161
+ this.lastLinesUp = 0;
162
+ }
163
+ super.cleanup();
164
+ }
158
165
  handleInput(char, _buffer) {
159
166
  // Navigation
160
167
  if (char === '\t') {
@@ -16,5 +16,6 @@ export declare class FilePrompt extends Prompt<string, FileOptions> {
16
16
  */
17
17
  private updateSuggestions;
18
18
  protected render(firstRender: boolean): void;
19
+ protected cleanup(): void;
19
20
  protected handleInput(char: string): void;
20
21
  }
@@ -147,6 +147,13 @@ class FilePrompt extends base_1.Prompt {
147
147
  this.print(`\x1b[${targetCol}C`);
148
148
  this.print(ansi_1.ANSI.SHOW_CURSOR);
149
149
  }
150
+ cleanup() {
151
+ if (this.lastLinesUp > 0) {
152
+ this.print(`\x1b[${this.lastLinesUp}B`);
153
+ this.lastLinesUp = 0;
154
+ }
155
+ super.cleanup();
156
+ }
150
157
  handleInput(char) {
151
158
  if (char === '\t') {
152
159
  if (this.suggestions.length > 0) {
@@ -166,11 +173,6 @@ class FilePrompt extends base_1.Prompt {
166
173
  return;
167
174
  }
168
175
  if (char === '\r' || char === '\n') {
169
- // Move cursor to the bottom to ensure clean exit UI
170
- if (this.lastLinesUp > 0) {
171
- this.print(`\x1b[${this.lastLinesUp}B`);
172
- this.lastLinesUp = 0;
173
- }
174
176
  this.submit(this.input);
175
177
  return;
176
178
  }
@@ -13,5 +13,6 @@ export declare class FormPrompt extends Prompt<Record<string, string>, FormOptio
13
13
  protected handleMouse(event: MouseEvent): void;
14
14
  private moveFocus;
15
15
  private validateCurrentField;
16
+ protected cleanup(): void;
16
17
  private submitForm;
17
18
  }
@@ -196,6 +196,13 @@ class FormPrompt extends base_1.Prompt {
196
196
  this.render(false);
197
197
  return true;
198
198
  }
199
+ cleanup() {
200
+ if (this.lastLinesUp > 0) {
201
+ this.print(`\x1b[${this.lastLinesUp}B`);
202
+ this.lastLinesUp = 0;
203
+ }
204
+ super.cleanup();
205
+ }
199
206
  async submitForm() {
200
207
  this.globalError = '';
201
208
  // Validate all fields
@@ -30,6 +30,7 @@ export declare class PhonePrompt extends Prompt<string, PhoneOptions> {
30
30
  */
31
31
  private renderFormattedNumber;
32
32
  protected render(firstRender: boolean): void;
33
+ protected cleanup(): void;
33
34
  protected handleInput(char: string): void;
34
35
  private filterCountries;
35
36
  private cycleCountry;
@@ -206,6 +206,13 @@ class PhonePrompt extends base_1.Prompt {
206
206
  }
207
207
  this.print(ansi_1.ANSI.SHOW_CURSOR);
208
208
  }
209
+ cleanup() {
210
+ if (this.lastLinesUp > 0) {
211
+ this.print(`\x1b[${this.lastLinesUp}B`);
212
+ this.lastLinesUp = 0;
213
+ }
214
+ super.cleanup();
215
+ }
209
216
  handleInput(char) {
210
217
  // Handle common keys
211
218
  if (char === '\r' || char === '\n') {
@@ -11,6 +11,7 @@ export declare class SnippetPrompt extends Prompt<string, SnippetOptions> {
11
11
  constructor(options: SnippetOptions);
12
12
  private parseTemplate;
13
13
  protected render(firstRender: boolean): void;
14
+ protected cleanup(): void;
14
15
  protected handleInput(char: string, _key: Buffer): void;
15
16
  protected handleMouse(event: MouseEvent): void;
16
17
  private moveFocus;
@@ -114,6 +114,13 @@ class SnippetPrompt extends base_1.Prompt {
114
114
  this.print(`\x1b[${cursorVisualIndex}C`);
115
115
  }
116
116
  }
117
+ cleanup() {
118
+ if (this.lastLinesUp > 0) {
119
+ this.print(`\x1b[${this.lastLinesUp}B`);
120
+ this.lastLinesUp = 0;
121
+ }
122
+ super.cleanup();
123
+ }
117
124
  handleInput(char, _key) {
118
125
  // Navigation: Tab / Shift+Tab
119
126
  if (char === '\u001b[Z') {
@@ -11,6 +11,7 @@ export declare class TextPrompt<O extends TextOptions = TextOptions> extends Pro
11
11
  private triggerSuggest;
12
12
  protected render(firstRender: boolean): void;
13
13
  private getSegmentWidth;
14
+ protected cleanup(): void;
14
15
  protected handleInput(char: string): void;
15
16
  private validateAndSubmit;
16
17
  }
@@ -179,6 +179,13 @@ class TextPrompt extends base_1.Prompt {
179
179
  getSegmentWidth(seg) {
180
180
  return (0, utils_1.stringWidth)(seg);
181
181
  }
182
+ cleanup() {
183
+ if (this.lastLinesUp > 0) {
184
+ this.print(`\x1b[${this.lastLinesUp}B`);
185
+ this.lastLinesUp = 0;
186
+ }
187
+ super.cleanup();
188
+ }
182
189
  handleInput(char) {
183
190
  // Tab (Accept Suggestion)
184
191
  if (char === '\t') {
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.0",
3
+ "version": "1.1.0",
4
4
  "description": "Zero-dependency, interactive CLI prompt",
5
5
  "repository": {
6
6
  "type": "git",