-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommand-join.ts
113 lines (106 loc) · 2.93 KB
/
command-join.ts
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
const NEEDS_QUOTE = /[\s\\*\?\[\]`$()#<>|&;]/
export function joinNix(arr: readonly string[]) {
const out: string[] = []
for (const command of arr) {
// if it is an empty string then append an empty string indicator
if (command.length === 0) {
out.push("''")
continue
}
// whether we need a quote for the current block
let needsQuote = false
// collection of quoted strings and escaped single quotes
const blocks: string[] = []
// string collector
let currentBlock: string[] = []
const flushCurrentBlock = () => {
// skip if we don't have anything collected as the current block
if (!currentBlock.length) { return }
if (needsQuote) {
currentBlock.unshift("'")
currentBlock.push("'")
}
blocks.push(currentBlock.join(''))
currentBlock = []
}
for (const char of command) {
if (char === "'") { // if single quote
// flush the current block
flushCurrentBlock()
// escape a single quote
blocks.push("\\'")
continue
}
if (NEEDS_QUOTE.test(char)) {
needsQuote = true
}
currentBlock.push(char)
}
// flush last block
flushCurrentBlock()
const escapedCommand = blocks.join('')
out.push(escapedCommand)
}
return out.join(' ')
}
export function joinWin(arr: readonly string[]) {
const out: string[] = []
for (const command of arr) {
// if it is an empty string then append an empty string indicator
if (command.length === 0) {
out.push('""')
continue
}
if (!/[\s\\"<>|&]/.test(command)) {
out.push(command)
continue
}
let backslashes = 0
// start escape quote
const outString: string[] = ["\""]
const flushBackslashes = (n: number) => {
outString.push("\\".repeat(n * backslashes))
backslashes = 0
}
for (let char of command) {
// if char is a backslash
if (char === "\\") {
// enqueue backslash
backslashes++
}
// if char is a double quote
else if (char === "\"") {
// doubly end backslash sequence if any
flushBackslashes(2)
// push string \" to escape quote
outString.push("\\\"")
}
else {
// singly end backslash sequence if any
flushBackslashes(1)
outString.push(char)
}
}
// flush any remaining backslashes
flushBackslashes(2)
// end escape quote
outString.push("\"")
let escapedCommand = outString.join('')
// escape some special characters
escapedCommand = escapedCommand.replace(/[&|<>;%^]/g, match => `^${match}`)
out.push(escapedCommand)
}
return out.join(' ')
}
export function commandJoin(arg: string | readonly string[]): string {
if (typeof arg === 'string') {
arg = [arg] as readonly string[]
}
arg = Array.from(arg)
if (process.platform === 'win32') {
return joinWin(arg)
}
else {
return joinNix(arg)
}
}