safe-rm 1.0.8 → 3.0.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/.eslintignore +2 -0
- package/.eslintrc.js +9 -0
- package/.github/workflows/nodejs.yml +27 -0
- package/.safe-rm/config +35 -0
- package/README.md +119 -24
- package/bin/rm.sh +460 -126
- package/package.json +26 -3
- package/test/cases.js +275 -0
- package/test/helper.js +178 -0
- package/test/rm.test.js +8 -0
- package/test/safe-rm-as.test.js +11 -0
- package/test/safe-rm.test.js +7 -0
- package/test/argument.sh +0 -5
- package/test/fold.sh +0 -39
- package/test/for.sh +0 -22
- package/test/nested-switch.sh +0 -72
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safe-rm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A much safer replacement of bash rm with nearly full functionalities and options of the rm command!",
|
|
5
5
|
"bin": {
|
|
6
6
|
"safe-rm": "./bin/rm.sh"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "ava --timeout=10s --verbose",
|
|
10
|
+
"test:dev": "NODE_DEBUG=safe-rm npm run test",
|
|
11
|
+
"test:debug": "SAFE_RM_DEBUG=1 npm run test:dev",
|
|
12
|
+
"test:mock-linux": "SAFE_RM_DEBUG_LINUX=1 npm run test",
|
|
13
|
+
"test:mock-linux:debug": "SAFE_RM_DEBUG=1 SAFE_RM_DEBUG_LINUX=1 npm run test:dev",
|
|
14
|
+
"lint": "eslint .",
|
|
15
|
+
"fix": "eslint . --fix"
|
|
10
16
|
},
|
|
11
17
|
"repository": {
|
|
12
18
|
"type": "git",
|
|
@@ -14,15 +20,32 @@
|
|
|
14
20
|
},
|
|
15
21
|
"keywords": [
|
|
16
22
|
"safe-rm",
|
|
23
|
+
"cli",
|
|
17
24
|
"rm",
|
|
18
25
|
"linux",
|
|
19
26
|
"alternative",
|
|
20
27
|
"trash"
|
|
21
28
|
],
|
|
29
|
+
"ava": {
|
|
30
|
+
"files": [
|
|
31
|
+
"test/*.test.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
22
34
|
"author": "kael",
|
|
23
35
|
"license": "MIT",
|
|
24
36
|
"bugs": {
|
|
25
37
|
"url": "https://github.com/kaelzhang/shell-safe-rm/issues"
|
|
26
38
|
},
|
|
27
|
-
"homepage": "https://github.com/kaelzhang/shell-safe-rm#readme"
|
|
39
|
+
"homepage": "https://github.com/kaelzhang/shell-safe-rm#readme",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@ostai/eslint-config": "^4.0.0",
|
|
42
|
+
"ava": "^6.2.0",
|
|
43
|
+
"delay": "^5.0.0",
|
|
44
|
+
"eslint": "^8.57.0",
|
|
45
|
+
"eslint-plugin-import": "^2.31.0",
|
|
46
|
+
"fs-extra": "^11.2.0",
|
|
47
|
+
"home": "^2.0.0",
|
|
48
|
+
"tmp": "^0.2.3",
|
|
49
|
+
"uuid": "^11.0.3"
|
|
50
|
+
}
|
|
28
51
|
}
|
package/test/cases.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const {v4: uuid} = require('uuid')
|
|
3
|
+
const delay = require('delay')
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
generateContextMethods,
|
|
7
|
+
assertEmptySuccess,
|
|
8
|
+
IS_MACOS
|
|
9
|
+
} = require('./helper')
|
|
10
|
+
|
|
11
|
+
const SAFE_RM_PATH = path.join(__dirname, '..', 'bin', 'rm.sh')
|
|
12
|
+
|
|
13
|
+
const is_rm = type => type === 'rm'
|
|
14
|
+
// const is_safe_rm = type => type === 'safe-rm'
|
|
15
|
+
|
|
16
|
+
// Whether it is a test spec for AppleScript version of safe-rm
|
|
17
|
+
const is_as = type => type === 'safe-rm-as'
|
|
18
|
+
|
|
19
|
+
// We skip testing trash dir for vanilla rm
|
|
20
|
+
const should_skip_test_trash_dir = type => is_rm(type) || is_as(type)
|
|
21
|
+
|
|
22
|
+
module.exports = (
|
|
23
|
+
test,
|
|
24
|
+
{
|
|
25
|
+
type,
|
|
26
|
+
// test_trash_dir,
|
|
27
|
+
command = SAFE_RM_PATH,
|
|
28
|
+
env = {}
|
|
29
|
+
} = {}
|
|
30
|
+
) => {
|
|
31
|
+
// Setup before each test
|
|
32
|
+
test.beforeEach(generateContextMethods(command, env))
|
|
33
|
+
|
|
34
|
+
test(`removes a single file`, async t => {
|
|
35
|
+
const {
|
|
36
|
+
createFile,
|
|
37
|
+
runRm,
|
|
38
|
+
pathExists,
|
|
39
|
+
lsFileInTrash
|
|
40
|
+
} = t.context
|
|
41
|
+
|
|
42
|
+
const filepath = await createFile()
|
|
43
|
+
const result = await runRm([filepath])
|
|
44
|
+
|
|
45
|
+
t.is(result.code, 0, 'exit code should be 0')
|
|
46
|
+
t.false(await pathExists(filepath), 'file should be removed')
|
|
47
|
+
|
|
48
|
+
if (should_skip_test_trash_dir(type)) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const files = await lsFileInTrash(filepath)
|
|
53
|
+
|
|
54
|
+
t.is(files.length, 1, 'should be one in the trash')
|
|
55
|
+
t.is(
|
|
56
|
+
path.basename(files[0]),
|
|
57
|
+
path.basename(filepath),
|
|
58
|
+
'file name should match'
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const EXTs = [
|
|
63
|
+
'',
|
|
64
|
+
'.jpg'
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
EXTs.forEach(ext => {
|
|
68
|
+
const extra = ext
|
|
69
|
+
? ` with ext "${ext}"`
|
|
70
|
+
: ''
|
|
71
|
+
|
|
72
|
+
test(`removes multiple files of the same name${extra}`, async t => {
|
|
73
|
+
const {
|
|
74
|
+
createFile,
|
|
75
|
+
runRm,
|
|
76
|
+
pathExists,
|
|
77
|
+
lsFileInTrash
|
|
78
|
+
} = t.context
|
|
79
|
+
|
|
80
|
+
const filename = uuid()
|
|
81
|
+
const full_name = filename + ext
|
|
82
|
+
|
|
83
|
+
const now = Date.now()
|
|
84
|
+
const to_next_second = 1000 - now % 1000
|
|
85
|
+
await delay(to_next_second)
|
|
86
|
+
|
|
87
|
+
const filepath1 = await createFile({name: full_name, content: '1'})
|
|
88
|
+
const result1 = await runRm([filepath1])
|
|
89
|
+
|
|
90
|
+
const filepath2 = await createFile({name: full_name, content: '2'})
|
|
91
|
+
const result2 = await runRm([filepath2])
|
|
92
|
+
|
|
93
|
+
const filepath3 = await createFile({name: full_name, content: '3'})
|
|
94
|
+
const result3 = await runRm([filepath3])
|
|
95
|
+
|
|
96
|
+
assertEmptySuccess(t, result1)
|
|
97
|
+
t.false(await pathExists(filepath1), 'file 1 should be removed')
|
|
98
|
+
|
|
99
|
+
assertEmptySuccess(t, result2)
|
|
100
|
+
t.false(await pathExists(filepath2), 'file 2 should be removed')
|
|
101
|
+
|
|
102
|
+
assertEmptySuccess(t, result3)
|
|
103
|
+
t.false(await pathExists(filepath3), 'file 3 should be removed')
|
|
104
|
+
|
|
105
|
+
if (should_skip_test_trash_dir(type)) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const files = (await lsFileInTrash(full_name))
|
|
110
|
+
.sort((a, b) => a.length - b.length)
|
|
111
|
+
|
|
112
|
+
if (IS_MACOS) {
|
|
113
|
+
// /path/to/foo[.jpg]
|
|
114
|
+
// /path/to/foo 12.58.23[.jpg]
|
|
115
|
+
// /path/to/foo 12.58.23 12.58.23[.jpg]
|
|
116
|
+
const [f1, f2, f3] = files
|
|
117
|
+
|
|
118
|
+
const [fb1, fb2, fb3] = [f1, f2, f3].map(
|
|
119
|
+
f => {
|
|
120
|
+
const base = path.basename(f)
|
|
121
|
+
|
|
122
|
+
return base.slice(0, base.length - ext.length)
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const [fbs1, fbs2, fbs3] = [fb1, fb2, fb3].map(f => f.split(' '))
|
|
127
|
+
|
|
128
|
+
const time = fbs2[1]
|
|
129
|
+
|
|
130
|
+
t.true(files.every(f => f.endsWith(ext)), 'should have the same ext')
|
|
131
|
+
|
|
132
|
+
t.is(fb1, filename)
|
|
133
|
+
t.is(fbs1[0], filename)
|
|
134
|
+
t.is(fbs2[0], filename)
|
|
135
|
+
t.is(fbs3[0], filename)
|
|
136
|
+
|
|
137
|
+
t.is(fbs3[1], time)
|
|
138
|
+
t.is(fbs3[2], time)
|
|
139
|
+
} else {
|
|
140
|
+
// /path/to/foo[.jpg]
|
|
141
|
+
// /path/to/foo[.jpg].1
|
|
142
|
+
// /path/to/foo[.jpg].2
|
|
143
|
+
const nums = []
|
|
144
|
+
const bases = []
|
|
145
|
+
const re = /(\.(\d+))?$/
|
|
146
|
+
|
|
147
|
+
for (const file of files) {
|
|
148
|
+
const match = file.match(re)
|
|
149
|
+
const n = match[2]
|
|
150
|
+
? parseInt(match[2], 10)
|
|
151
|
+
: 0
|
|
152
|
+
|
|
153
|
+
const f = file.slice(0, match.index)
|
|
154
|
+
|
|
155
|
+
nums.push(n)
|
|
156
|
+
bases.push(f)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
t.true(bases.every(f => f.endsWith(ext)), 'should have the same ext')
|
|
160
|
+
t.is(nums[0], 0)
|
|
161
|
+
t.is(nums[1], 1)
|
|
162
|
+
t.is(nums[2], 2)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test(`removes a single file in trash permanently`, async t => {
|
|
168
|
+
const {
|
|
169
|
+
trash_path,
|
|
170
|
+
createFile,
|
|
171
|
+
runRm,
|
|
172
|
+
pathExists,
|
|
173
|
+
lsFileInTrash
|
|
174
|
+
} = t.context
|
|
175
|
+
|
|
176
|
+
const filepath = await createFile({name: path.join(trash_path, uuid())})
|
|
177
|
+
const result = await runRm([filepath], {
|
|
178
|
+
env: {
|
|
179
|
+
SAFE_RM_PERM_DEL_FILES_IN_TRASH: 'yes'
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
assertEmptySuccess(t, result)
|
|
184
|
+
t.false(await pathExists(filepath), 'file should be removed')
|
|
185
|
+
|
|
186
|
+
if (should_skip_test_trash_dir(type)) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const files = await lsFileInTrash(filepath)
|
|
191
|
+
|
|
192
|
+
t.is(files.length, 0, 'should be already removed')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test(`#22 exit code with -f option`, async t => {
|
|
196
|
+
const {
|
|
197
|
+
source_path,
|
|
198
|
+
runRm
|
|
199
|
+
} = t.context
|
|
200
|
+
|
|
201
|
+
const filepath = path.join(source_path, uuid())
|
|
202
|
+
const result = await runRm(['-f', filepath])
|
|
203
|
+
|
|
204
|
+
assertEmptySuccess(t, result, ', if rm -f a non-existing file')
|
|
205
|
+
|
|
206
|
+
const result_no_f = await runRm([filepath])
|
|
207
|
+
|
|
208
|
+
t.is(result_no_f.code, 1, 'exit code should be 1 without -f')
|
|
209
|
+
t.is(result_no_f.stdout, '', 'stdout should be empty')
|
|
210
|
+
|
|
211
|
+
t.true(
|
|
212
|
+
// The stderr of different rm distributions may vary:
|
|
213
|
+
// - Linux rm: "rm: cannot remove 'nonexistent.txt': No such file or directory"
|
|
214
|
+
// - Mac rm: "rm: nonexistent.txt: No such file or directory"
|
|
215
|
+
// So we just check if the stderr includes "No such file or directory"
|
|
216
|
+
result_no_f.stderr.includes('No such file or directory'), `stderr should include "No such file or directory": ${
|
|
217
|
+
result_no_f.stderr
|
|
218
|
+
}`)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test(`removes an empty directory: -d`, async t => {
|
|
222
|
+
const {
|
|
223
|
+
createDir,
|
|
224
|
+
runRm,
|
|
225
|
+
pathExists
|
|
226
|
+
} = t.context
|
|
227
|
+
|
|
228
|
+
const dirpath = await createDir()
|
|
229
|
+
const result1 = await runRm([dirpath])
|
|
230
|
+
|
|
231
|
+
t.is(result1.code, 1, 'exit code should be 1')
|
|
232
|
+
t.true(result1.stderr.includes('a directory'), 'stderr should include "a directory"')
|
|
233
|
+
|
|
234
|
+
const result2 = await runRm(['-d', dirpath])
|
|
235
|
+
|
|
236
|
+
assertEmptySuccess(t, result2)
|
|
237
|
+
t.false(await pathExists(dirpath), 'directory should be removed')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Only test for safe-rm
|
|
241
|
+
!is_rm(type) && test(`protected rules`, async t => {
|
|
242
|
+
const {
|
|
243
|
+
createDir,
|
|
244
|
+
createFile,
|
|
245
|
+
runRm,
|
|
246
|
+
pathExists
|
|
247
|
+
} = t.context
|
|
248
|
+
|
|
249
|
+
const config_root = await createDir({
|
|
250
|
+
name: '.safe-rm'
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const dir = await createDir()
|
|
254
|
+
const filepath = await createFile({
|
|
255
|
+
under: dir
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
await createFile({
|
|
259
|
+
name: '.gitignore',
|
|
260
|
+
under: config_root,
|
|
261
|
+
content: `${dir}
|
|
262
|
+
`
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
const result = await runRm([filepath], {
|
|
266
|
+
env: {
|
|
267
|
+
SAFE_RM_CONFIG_ROOT: config_root
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
t.is(result.code, 1, 'exit code should be 1')
|
|
272
|
+
t.true(result.stderr.includes('protected'), 'stderr should include "protected"')
|
|
273
|
+
t.true(await pathExists(filepath), 'file should not be removed')
|
|
274
|
+
})
|
|
275
|
+
}
|
package/test/helper.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs').promises
|
|
3
|
+
const {spawn} = require('child_process')
|
|
4
|
+
const tmp = require('tmp')
|
|
5
|
+
const fse = require('fs-extra')
|
|
6
|
+
const {v4: uuid} = require('uuid')
|
|
7
|
+
const log = require('util').debuglog('safe-rm')
|
|
8
|
+
|
|
9
|
+
const TEST_DIR = path.join(tmp.dirSync().name, 'safe-rm-tests')
|
|
10
|
+
|
|
11
|
+
const IS_MACOS = process.platform === 'darwin'
|
|
12
|
+
// For linux mock testing
|
|
13
|
+
&& !process.env.SAFE_RM_DEBUG_LINUX
|
|
14
|
+
|
|
15
|
+
const generateContextMethods = (
|
|
16
|
+
rm_command,
|
|
17
|
+
rm_command_env
|
|
18
|
+
) => async t => {
|
|
19
|
+
const root_path = path.join(TEST_DIR, uuid())
|
|
20
|
+
t.context.root = await fse.ensureDir(root_path)
|
|
21
|
+
|
|
22
|
+
const source_path = t.context.source_path = path.join(root_path, 'source')
|
|
23
|
+
const trash = rm_command_env.SAFE_RM_TRASH
|
|
24
|
+
? rm_command_env.SAFE_RM_TRASH
|
|
25
|
+
: path.join(root_path, 'trash')
|
|
26
|
+
|
|
27
|
+
t.context.trash_path = process.platform === 'darwin'
|
|
28
|
+
? trash
|
|
29
|
+
: path.join(trash, 'files')
|
|
30
|
+
|
|
31
|
+
await Promise.all([
|
|
32
|
+
fse.ensureDir(source_path),
|
|
33
|
+
fse.ensureDir(t.context.trash_path)
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
// Helper function to create a temporary directory
|
|
37
|
+
async function createDir ({
|
|
38
|
+
name = uuid(),
|
|
39
|
+
under
|
|
40
|
+
} = {}) {
|
|
41
|
+
const dirpath = path.resolve(under || t.context.source_path, name)
|
|
42
|
+
await fse.ensureDir(dirpath)
|
|
43
|
+
|
|
44
|
+
return dirpath
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper function to create a temporary file
|
|
48
|
+
async function createFile ({
|
|
49
|
+
name = uuid(),
|
|
50
|
+
content = 'test content',
|
|
51
|
+
under
|
|
52
|
+
} = {}) {
|
|
53
|
+
const filepath = path.resolve(under || t.context.source_path, name)
|
|
54
|
+
await fs.writeFile(filepath, content)
|
|
55
|
+
|
|
56
|
+
return filepath
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper function to run rm commands
|
|
60
|
+
function runRm (args, {
|
|
61
|
+
input = '',
|
|
62
|
+
command = rm_command,
|
|
63
|
+
env: arg_env = {}
|
|
64
|
+
} = {}) {
|
|
65
|
+
return new Promise(resolve => {
|
|
66
|
+
const env = {
|
|
67
|
+
// ...process.env,
|
|
68
|
+
...{
|
|
69
|
+
SAFE_RM_TRASH: t.context.trash_path
|
|
70
|
+
},
|
|
71
|
+
...rm_command_env,
|
|
72
|
+
...arg_env
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const child = spawn(command, args, {
|
|
76
|
+
env
|
|
77
|
+
})
|
|
78
|
+
let stdout = ''
|
|
79
|
+
let stderr = ''
|
|
80
|
+
|
|
81
|
+
child.stdout.on('data', data => {
|
|
82
|
+
stdout += data.toString()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
child.stderr.on('data', data => {
|
|
86
|
+
stderr += data.toString()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (input) {
|
|
90
|
+
if (!child.stdin) {
|
|
91
|
+
throw new Error('Child process does not support stdin')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
child.stdin.write(input)
|
|
95
|
+
child.stdin.end()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
child.on('close', code => {
|
|
99
|
+
const resolved = {
|
|
100
|
+
code,
|
|
101
|
+
stdout,
|
|
102
|
+
stderr
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
log(command, 'result:', resolved)
|
|
106
|
+
|
|
107
|
+
resolve(resolved)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Helper function to check if path exists
|
|
113
|
+
async function pathExists (filepath) {
|
|
114
|
+
const realpath = path.resolve(t.context.source_path, filepath)
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await fs.access(realpath)
|
|
118
|
+
return true
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function lsFileInMacTrash (filepath) {
|
|
125
|
+
const {trash_path} = t.context
|
|
126
|
+
|
|
127
|
+
const files = await fs.readdir(trash_path)
|
|
128
|
+
|
|
129
|
+
const _filename = path.basename(filepath)
|
|
130
|
+
const ext = path.extname(_filename)
|
|
131
|
+
const filename = path.basename(_filename, ext)
|
|
132
|
+
|
|
133
|
+
const filtered = files.filter(
|
|
134
|
+
f => f.endsWith(ext) && f.startsWith(filename)
|
|
135
|
+
).map(f => path.join(trash_path, f))
|
|
136
|
+
|
|
137
|
+
return filtered
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function lsFileInLinuxTrash (filepath) {
|
|
141
|
+
const {trash_path: _trash_path} = t.context
|
|
142
|
+
const trash_path = path.join(_trash_path, 'files')
|
|
143
|
+
|
|
144
|
+
const filename = path.basename(filepath)
|
|
145
|
+
|
|
146
|
+
const files = await fs.readdir(trash_path)
|
|
147
|
+
|
|
148
|
+
return files
|
|
149
|
+
.filter(f => f.startsWith(filename))
|
|
150
|
+
.map(f => path.join(trash_path, f))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function lsFileInTrash (filepath) {
|
|
154
|
+
return IS_MACOS
|
|
155
|
+
? lsFileInMacTrash(filepath)
|
|
156
|
+
: lsFileInLinuxTrash(filepath)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Object.assign(t.context, {
|
|
160
|
+
createDir,
|
|
161
|
+
createFile,
|
|
162
|
+
runRm,
|
|
163
|
+
pathExists,
|
|
164
|
+
lsFileInTrash
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const assertEmptySuccess = (t, result, a = '', b = '', c = '') => {
|
|
169
|
+
t.is(result.code, 0, `exit code should be 0${a}`)
|
|
170
|
+
t.is(result.stdout, '', `stdout should be empty${b}`)
|
|
171
|
+
t.is(result.stderr, '', `stderr should be empty${c}`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = {
|
|
175
|
+
generateContextMethods,
|
|
176
|
+
assertEmptySuccess,
|
|
177
|
+
IS_MACOS
|
|
178
|
+
}
|
package/test/rm.test.js
ADDED
package/test/fold.sh
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
n=-abcde
|
|
4
|
-
|
|
5
|
-
split_echo(){
|
|
6
|
-
split=`echo $1 | fold -w1`
|
|
7
|
-
|
|
8
|
-
local s
|
|
9
|
-
|
|
10
|
-
for s in ${split[@]}
|
|
11
|
-
do
|
|
12
|
-
echo $s
|
|
13
|
-
done
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
split_echo $n
|
|
17
|
-
|
|
18
|
-
echo $s
|
|
19
|
-
|
|
20
|
-
cut_echo(){
|
|
21
|
-
echo "remove leading char ${1:1}"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
cut_echo abcdefg
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
match(){
|
|
28
|
-
if [[ ${1:0:1} =~ [aA] ]]; then
|
|
29
|
-
echo start with a
|
|
30
|
-
else
|
|
31
|
-
echo no
|
|
32
|
-
fi
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
match abc
|
|
36
|
-
|
|
37
|
-
match Acbdfd
|
|
38
|
-
match ddd
|
|
39
|
-
|
package/test/for.sh
DELETED
package/test/nested-switch.sh
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
print(){
|
|
5
|
-
case $1 in
|
|
6
|
-
1[0-9][0-9]*)
|
|
7
|
-
echo 1
|
|
8
|
-
;;
|
|
9
|
-
|
|
10
|
-
1[0-9]*)
|
|
11
|
-
echo 3
|
|
12
|
-
;;
|
|
13
|
-
|
|
14
|
-
1)
|
|
15
|
-
echo 4
|
|
16
|
-
;;
|
|
17
|
-
|
|
18
|
-
*)
|
|
19
|
-
echo 5
|
|
20
|
-
;;
|
|
21
|
-
esac
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
print_2(){
|
|
25
|
-
case $1 in
|
|
26
|
-
1[0-9]+)
|
|
27
|
-
echo 1
|
|
28
|
-
;;
|
|
29
|
-
|
|
30
|
-
1[0-9]*)
|
|
31
|
-
echo 2
|
|
32
|
-
;;
|
|
33
|
-
*)
|
|
34
|
-
echo 3
|
|
35
|
-
;;
|
|
36
|
-
esac
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
print_3(){
|
|
40
|
-
case $1 in
|
|
41
|
-
.|..)
|
|
42
|
-
echo 1
|
|
43
|
-
;;
|
|
44
|
-
|
|
45
|
-
a)
|
|
46
|
-
echo 2
|
|
47
|
-
;;
|
|
48
|
-
|
|
49
|
-
aa)
|
|
50
|
-
echo 3
|
|
51
|
-
;;
|
|
52
|
-
esac
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
print 1234
|
|
56
|
-
print 123
|
|
57
|
-
print 12
|
|
58
|
-
print 1
|
|
59
|
-
|
|
60
|
-
echo ---------
|
|
61
|
-
|
|
62
|
-
print_2 1234
|
|
63
|
-
print_2 123
|
|
64
|
-
print_2 12
|
|
65
|
-
print_2 1
|
|
66
|
-
|
|
67
|
-
echo ---------
|
|
68
|
-
|
|
69
|
-
print_3 a
|
|
70
|
-
print_3 aa
|
|
71
|
-
print_3 ..
|
|
72
|
-
print_3 .
|