-
Notifications
You must be signed in to change notification settings - Fork 526
Expand file tree
/
Copy pathshlex.ts
More file actions
123 lines (112 loc) · 4.03 KB
/
shlex.ts
File metadata and controls
123 lines (112 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
export interface ShlexOptions {
mode: 'windows' | 'posix';
}
/**
* Splits a string into an iterable of tokens, similar to how a shell would parse arguments.
* Handles quoting and escaping according to the specified mode ('windows' or 'posix').
*
* In POSIX mode, backslash escapes the following character outside of quotes (the backslash
* is consumed). Inside double quotes, only $, `, ", \, and newline can be escaped.
* Inside single quotes, backslash has no special meaning.
*
* @param str The string to split into tokens.
* @param opt Optional options for splitting. If not provided, defaults to the platform-specific mode ('windows' or 'posix').
* @returns An iterable of tokens.
*/
export function* split(str: string, opt?: ShlexOptions): Iterable<string> {
opt = opt || {
mode: process.platform === 'win32' ? 'windows' : 'posix'
};
const quoteChars = opt.mode === 'posix' ? '\'"' : '"';
const escapeChars = '\\';
let escapeChar: string | undefined;
let token: string[] = [];
let quoteChar: string | undefined; // Track which quote character we're inside (for POSIX)
for (let i = 0; i < str.length; ++i) {
const char = str.charAt(i);
if (escapeChar) {
if (char === '\n') {
// Line continuation: consume both backslash and newline
} else if (opt.mode === 'posix') {
// POSIX escape handling
if (quoteChar === "'") {
// Inside single quotes: backslash has no special meaning
token.push(escapeChar, char);
} else if (quoteChar === '"') {
// Inside double quotes: only certain chars can be escaped
// $, `, ", \, and newline
if (char === '$' || char === '`' || char === '"' || char === '\\') {
token.push(char);
} else {
token.push(escapeChar, char);
}
} else {
// Outside quotes: backslash escapes any character
token.push(char);
}
} else {
// Windows mode: only backslash can be escaped
if (escapeChars.includes(char)) {
token.push(char);
} else {
token.push(escapeChar, char);
}
}
escapeChar = undefined;
continue;
}
if (escapeChars.includes(char)) {
// Start escape sequence
escapeChar = char;
continue;
}
if (quoteChar) {
if (char === quoteChar) {
// End of quoted section
quoteChar = undefined;
token.push(char);
continue;
}
token.push(char);
continue;
}
if (quoteChars.includes(char)) {
// Beginning of a quoted section
quoteChar = char;
token.push(char);
continue;
}
if (/[\t \n\r\f]/.test(char)) {
if (token.length > 0) {
yield token.join('');
}
token = [];
continue;
}
token.push(char);
}
if (token.length > 0) {
yield token.join('');
}
}
/**
* Quotes a string for safe use in a shell command.
* If the string contains special characters, it will be wrapped in double quotes and any existing double quotes will be escaped.
* @param str The string to quote.
* @param opt Optional options for quoting. If not provided, defaults to the platform-specific mode ('windows' or 'posix').
* @returns The quoted string.
*/
export function quote(str: string, opt?: ShlexOptions): string {
opt = opt || {
mode: process.platform === 'win32' ? 'windows' : 'posix'
};
if (str === '') {
return '""';
}
if (/[^\w@%\-+=:,./|><]/.test(str)) {
str = str.replace(/"/g, '\\"');
return `"${str}"`;
} else {
return str;
}
}