safe-rm 3.1.4 → 3.1.5
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/bin/rm.sh +88 -20
- package/package.json +1 -1
- package/test/cases.js +289 -24
- package/test/helper.js +7 -2
package/bin/rm.sh
CHANGED
|
@@ -330,7 +330,7 @@ if [[ ! -e $SAFE_RM_TRASH ]]; then
|
|
|
330
330
|
echo -n "(yes/no): "
|
|
331
331
|
|
|
332
332
|
read answer
|
|
333
|
-
if [[ $answer == "yes" || ! -n $
|
|
333
|
+
if [[ $answer == "yes" || ! -n $answer ]]; then
|
|
334
334
|
mkdir -p "$SAFE_RM_TRASH"
|
|
335
335
|
else
|
|
336
336
|
echo "Canceled!"
|
|
@@ -455,7 +455,7 @@ trash(){
|
|
|
455
455
|
local target=$1
|
|
456
456
|
|
|
457
457
|
if [[ -n $SAFE_RM_PERM_DEL_FILES_IN_TRASH ]]; then
|
|
458
|
-
if
|
|
458
|
+
if is_in_trash "$target"; then
|
|
459
459
|
# If the target is already in the trash, delete it permanently
|
|
460
460
|
/bin/rm -rf "$target"
|
|
461
461
|
else
|
|
@@ -498,6 +498,43 @@ get_absolute_path(){
|
|
|
498
498
|
}
|
|
499
499
|
|
|
500
500
|
|
|
501
|
+
encode_trashinfo_path(){
|
|
502
|
+
local path=$1
|
|
503
|
+
local out=
|
|
504
|
+
local i
|
|
505
|
+
local char
|
|
506
|
+
local hex
|
|
507
|
+
local LC_ALL=C
|
|
508
|
+
|
|
509
|
+
for ((i = 0; i < ${#path}; i += 1)); do
|
|
510
|
+
char=${path:i:1}
|
|
511
|
+
|
|
512
|
+
case "$char" in
|
|
513
|
+
[a-zA-Z0-9._~/-])
|
|
514
|
+
out="$out$char"
|
|
515
|
+
;;
|
|
516
|
+
*)
|
|
517
|
+
hex=$(printf '%s' "$char" | od -An -tx1 | tr -d ' \n' | tr '[:lower:]' '[:upper:]')
|
|
518
|
+
out="$out%$hex"
|
|
519
|
+
;;
|
|
520
|
+
esac
|
|
521
|
+
done
|
|
522
|
+
|
|
523
|
+
printf '%s\n' "$out"
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
is_in_trash(){
|
|
528
|
+
local target_abs
|
|
529
|
+
local trash_abs
|
|
530
|
+
|
|
531
|
+
target_abs=$(get_absolute_path "$1") || return 1
|
|
532
|
+
trash_abs=$(cd "$SAFE_RM_TRASH" && pwd) || return 1
|
|
533
|
+
|
|
534
|
+
[[ "$target_abs" == "$trash_abs" || "$target_abs" == "$trash_abs"/* ]]
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
|
|
501
538
|
# Returns
|
|
502
539
|
# - 0: the target is protected
|
|
503
540
|
# - 1: the target is not protected
|
|
@@ -559,16 +596,22 @@ check_mac_trash_path(){
|
|
|
559
596
|
local ext=$2
|
|
560
597
|
local full_path="$path$ext"
|
|
561
598
|
|
|
562
|
-
|
|
563
|
-
if [[ -e "$full_path" ]]; then
|
|
564
|
-
debug "$LINENO: $full_path already exists"
|
|
565
|
-
|
|
566
|
-
# renew $_short_time_ret
|
|
567
|
-
short_time
|
|
568
|
-
check_mac_trash_path "$path $_short_time_ret" "$ext"
|
|
569
|
-
else
|
|
599
|
+
if [[ ! -e "$full_path" ]]; then
|
|
570
600
|
_mac_trash_path_ret=$full_path
|
|
601
|
+
return
|
|
571
602
|
fi
|
|
603
|
+
|
|
604
|
+
debug "$LINENO: $full_path already exists"
|
|
605
|
+
|
|
606
|
+
short_time
|
|
607
|
+
full_path="$path $_short_time_ret$ext"
|
|
608
|
+
|
|
609
|
+
while [[ -e "$full_path" ]]; do
|
|
610
|
+
debug "$LINENO: $full_path already exists"
|
|
611
|
+
full_path="${full_path}X"
|
|
612
|
+
done
|
|
613
|
+
|
|
614
|
+
_mac_trash_path_ret=$full_path
|
|
572
615
|
}
|
|
573
616
|
|
|
574
617
|
|
|
@@ -651,16 +694,35 @@ check_linux_trash_base(){
|
|
|
651
694
|
|
|
652
695
|
local max_n=0
|
|
653
696
|
local num=
|
|
697
|
+
local restore_nullglob=$(shopt -p nullglob)
|
|
698
|
+
local restore_dotglob=$(shopt -p dotglob)
|
|
699
|
+
shopt -s nullglob dotglob
|
|
700
|
+
local list=("$trash"/*)
|
|
701
|
+
eval "$restore_nullglob"
|
|
702
|
+
eval "$restore_dotglob"
|
|
703
|
+
local file
|
|
704
|
+
local name
|
|
705
|
+
local suffix
|
|
654
706
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
max_n=$num
|
|
661
|
-
fi
|
|
707
|
+
for file in "${list[@]}"; do
|
|
708
|
+
name=$(basename -- "$file")
|
|
709
|
+
|
|
710
|
+
if [[ "$name" != "$base".* ]]; then
|
|
711
|
+
continue
|
|
662
712
|
fi
|
|
663
|
-
|
|
713
|
+
|
|
714
|
+
suffix=${name#"$base".}
|
|
715
|
+
|
|
716
|
+
if [[ ! "$suffix" =~ ^[0-9]+$ ]]; then
|
|
717
|
+
continue
|
|
718
|
+
fi
|
|
719
|
+
|
|
720
|
+
# Remove leading zeros and make sure the number is in base 10
|
|
721
|
+
num=$((10#$suffix))
|
|
722
|
+
if ((num > max_n)); then
|
|
723
|
+
max_n=$num
|
|
724
|
+
fi
|
|
725
|
+
done
|
|
664
726
|
|
|
665
727
|
(( max_n += 1 ))
|
|
666
728
|
|
|
@@ -674,6 +736,12 @@ check_linux_trash_base(){
|
|
|
674
736
|
# trash a file or dir directly for linux
|
|
675
737
|
# - move the target into
|
|
676
738
|
linux_trash(){
|
|
739
|
+
local original_path
|
|
740
|
+
local trashinfo_path_value
|
|
741
|
+
|
|
742
|
+
original_path=$(get_absolute_path "$1") || return 1
|
|
743
|
+
trashinfo_path_value=$(encode_trashinfo_path "$original_path") || return 1
|
|
744
|
+
|
|
677
745
|
check_target_to_move "$1"
|
|
678
746
|
local move=$_to_move
|
|
679
747
|
local base=$(basename -- "$move")
|
|
@@ -700,7 +768,7 @@ linux_trash(){
|
|
|
700
768
|
local trash_time=$(date +%Y-%m-%dT%H:%M:%S)
|
|
701
769
|
cat > "$info_path" <<EOF
|
|
702
770
|
[Trash Info]
|
|
703
|
-
Path=$
|
|
771
|
+
Path=$trashinfo_path_value
|
|
704
772
|
DeletionDate=$trash_time
|
|
705
773
|
EOF
|
|
706
774
|
local info_status=$?
|
|
@@ -739,7 +807,7 @@ list_files(){
|
|
|
739
807
|
debug "$LINENO: ${#FILE_NAME[@]} files or directory to process: ${FILE_NAME[@]}"
|
|
740
808
|
|
|
741
809
|
# test remove interactive_once: ask for 3 or more files or with recursive option
|
|
742
|
-
if [[ (${#FILE_NAME[@]} >
|
|
810
|
+
if [[ (${#FILE_NAME[@]} > 3 || $OPT_RECURSIVE == 1) && $OPT_INTERACTIVE_ONCE == 1 ]]; then
|
|
743
811
|
echo -n "$COMMAND: remove all arguments? "
|
|
744
812
|
read answer
|
|
745
813
|
|
package/package.json
CHANGED
package/test/cases.js
CHANGED
|
@@ -20,6 +20,9 @@ const is_as = type => type === 'safe-rm-as'
|
|
|
20
20
|
// We skip testing trash dir for vanilla rm
|
|
21
21
|
const should_skip_test_trash_dir = type => is_rm(type) || is_as(type)
|
|
22
22
|
|
|
23
|
+
const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
24
|
+
const encodeTrashInfoPath = value => value.split('/').map(encodeURIComponent).join('/')
|
|
25
|
+
|
|
23
26
|
module.exports = (
|
|
24
27
|
test,
|
|
25
28
|
{
|
|
@@ -111,32 +114,24 @@ module.exports = (
|
|
|
111
114
|
.sort((a, b) => a.length - b.length)
|
|
112
115
|
|
|
113
116
|
if (IS_MACOS) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
f => {
|
|
121
|
-
const base = path.basename(f)
|
|
122
|
-
|
|
123
|
-
return base.slice(0, base.length - ext.length)
|
|
124
|
-
}
|
|
117
|
+
const baseNames = files.map(file => path.basename(file))
|
|
118
|
+
const timestamped = baseNames.filter(base => base !== full_name)
|
|
119
|
+
const timestampedRe = new RegExp(
|
|
120
|
+
ext
|
|
121
|
+
? `^${escapeRegExp(filename)} \\d{2}\\.\\d{2}\\.\\d{2}${escapeRegExp(ext)}X*$`
|
|
122
|
+
: `^${escapeRegExp(filename)} \\d{2}\\.\\d{2}\\.\\d{2}X*$`
|
|
125
123
|
)
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
t.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
t.is(fbs3[1], time)
|
|
139
|
-
t.is(fbs3[2], time)
|
|
125
|
+
t.is(baseNames.filter(base => base === full_name).length, 1)
|
|
126
|
+
t.is(timestamped.length, 2)
|
|
127
|
+
t.true(
|
|
128
|
+
timestamped.every(base => timestampedRe.test(base)),
|
|
129
|
+
'timestamped duplicates should follow Finder naming'
|
|
130
|
+
)
|
|
131
|
+
t.true(
|
|
132
|
+
timestamped.some(base => ext ? base.endsWith(ext) : !base.endsWith('X')),
|
|
133
|
+
'should include a plain timestamped duplicate'
|
|
134
|
+
)
|
|
140
135
|
} else {
|
|
141
136
|
// /path/to/foo[.jpg]
|
|
142
137
|
// /path/to/foo[.jpg].1
|
|
@@ -193,6 +188,276 @@ module.exports = (
|
|
|
193
188
|
t.is(files.length, 0, 'should be already removed')
|
|
194
189
|
})
|
|
195
190
|
|
|
191
|
+
!is_rm(type) && !is_as(type) && test(`permanent delete only applies to real trash descendants`, async t => {
|
|
192
|
+
const {
|
|
193
|
+
createDir,
|
|
194
|
+
createFile,
|
|
195
|
+
runRm,
|
|
196
|
+
pathExists,
|
|
197
|
+
lsFileInTrash
|
|
198
|
+
} = t.context
|
|
199
|
+
|
|
200
|
+
const sibling = await createDir({
|
|
201
|
+
name: `${path.basename(t.context.trash_path)}-other`,
|
|
202
|
+
under: path.dirname(t.context.trash_path)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const filepath = await createFile({
|
|
206
|
+
name: uuid(),
|
|
207
|
+
under: sibling
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const result = await runRm([filepath], {
|
|
211
|
+
env: {
|
|
212
|
+
SAFE_RM_PERM_DEL_FILES_IN_TRASH: 'yes'
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
assertEmptySuccess(t, result)
|
|
217
|
+
t.false(await pathExists(filepath), 'file should be removed from the sibling dir')
|
|
218
|
+
|
|
219
|
+
const files = await lsFileInTrash(filepath)
|
|
220
|
+
|
|
221
|
+
t.is(files.length, 1, 'file should be moved into trash instead of being hard-deleted')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
!is_rm(type) && !is_as(type) && !IS_MACOS && test(`linux duplicate naming treats regex chars literally`, async t => {
|
|
225
|
+
const {
|
|
226
|
+
trash_path,
|
|
227
|
+
createDir,
|
|
228
|
+
createFile,
|
|
229
|
+
runRm,
|
|
230
|
+
pathExists,
|
|
231
|
+
lsFileInTrash
|
|
232
|
+
} = t.context
|
|
233
|
+
|
|
234
|
+
const filename = 'a+b'
|
|
235
|
+
const trashFilesDir = await createDir({
|
|
236
|
+
name: 'files',
|
|
237
|
+
under: trash_path
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const existing0 = await createFile({
|
|
241
|
+
name: filename,
|
|
242
|
+
under: trashFilesDir,
|
|
243
|
+
content: 'existing-0'
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const existing1 = await createFile({
|
|
247
|
+
name: `${filename}.1`,
|
|
248
|
+
under: trashFilesDir,
|
|
249
|
+
content: 'existing-1'
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const filepath = await createFile({
|
|
253
|
+
name: filename,
|
|
254
|
+
content: 'incoming'
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const result = await runRm([filepath])
|
|
258
|
+
|
|
259
|
+
assertEmptySuccess(t, result)
|
|
260
|
+
t.false(await pathExists(filepath), 'source file should be removed')
|
|
261
|
+
|
|
262
|
+
const files = (await lsFileInTrash(filename))
|
|
263
|
+
.map(file => path.basename(file))
|
|
264
|
+
.sort()
|
|
265
|
+
|
|
266
|
+
t.deepEqual(files, [filename, `${filename}.1`, `${filename}.2`])
|
|
267
|
+
t.is(await fs.readFile(existing0, 'utf8'), 'existing-0')
|
|
268
|
+
t.is(await fs.readFile(existing1, 'utf8'), 'existing-1')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
!is_rm(type) && !is_as(type) && !IS_MACOS && test(`linux .trashinfo stores an encoded absolute original path`, async t => {
|
|
272
|
+
const {
|
|
273
|
+
source_path,
|
|
274
|
+
trash_path,
|
|
275
|
+
createFile,
|
|
276
|
+
runRm,
|
|
277
|
+
pathExists
|
|
278
|
+
} = t.context
|
|
279
|
+
|
|
280
|
+
const filename = 'space name'
|
|
281
|
+
const filepath = await createFile({
|
|
282
|
+
name: filename
|
|
283
|
+
})
|
|
284
|
+
const canonicalFilepath = await fs.realpath(filepath)
|
|
285
|
+
|
|
286
|
+
const result = await runRm([`./${filename}`], {
|
|
287
|
+
cwd: source_path
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
assertEmptySuccess(t, result)
|
|
291
|
+
t.false(await pathExists(filepath), 'source file should be removed')
|
|
292
|
+
|
|
293
|
+
const infoPath = path.join(trash_path, 'info', `${filename}.trashinfo`)
|
|
294
|
+
const info = await fs.readFile(infoPath, 'utf8')
|
|
295
|
+
const expectedPath = encodeTrashInfoPath(canonicalFilepath)
|
|
296
|
+
|
|
297
|
+
t.true(info.includes(`[Trash Info]`))
|
|
298
|
+
t.true(info.includes(`Path=${expectedPath}`))
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
!is_rm(type) && !is_as(type) && IS_MACOS && test(`mac fallback duplicate naming matches Finder without extension`, async t => {
|
|
302
|
+
const {
|
|
303
|
+
root,
|
|
304
|
+
createDir,
|
|
305
|
+
createFile,
|
|
306
|
+
runRm,
|
|
307
|
+
lsFileInTrash
|
|
308
|
+
} = t.context
|
|
309
|
+
|
|
310
|
+
const mockBin = await createDir({
|
|
311
|
+
name: 'mock-bin-noext',
|
|
312
|
+
under: root
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
const mockDate = await createFile({
|
|
316
|
+
name: 'date',
|
|
317
|
+
under: mockBin,
|
|
318
|
+
content: '#!/usr/bin/env bash\necho 12.34.56\n'
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
await fs.chmod(mockDate, 0o755)
|
|
322
|
+
|
|
323
|
+
const filename = `finder-noext-${uuid()}`
|
|
324
|
+
|
|
325
|
+
for (const content of ['1', '2', '3', '4']) {
|
|
326
|
+
const filepath = await createFile({
|
|
327
|
+
name: filename,
|
|
328
|
+
content
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
const result = await runRm([filepath], {
|
|
332
|
+
env: {
|
|
333
|
+
PATH: `${mockBin}:${process.env.PATH}`
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
assertEmptySuccess(t, result)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const files = (await lsFileInTrash(filename))
|
|
341
|
+
.map(file => path.basename(file))
|
|
342
|
+
.sort((a, b) => a.length - b.length)
|
|
343
|
+
|
|
344
|
+
t.deepEqual(files, [
|
|
345
|
+
filename,
|
|
346
|
+
`${filename} 12.34.56`,
|
|
347
|
+
`${filename} 12.34.56X`,
|
|
348
|
+
`${filename} 12.34.56XX`
|
|
349
|
+
])
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
!is_rm(type) && !is_as(type) && IS_MACOS && test(`mac fallback duplicate naming matches Finder with extensions`, async t => {
|
|
353
|
+
const {
|
|
354
|
+
root,
|
|
355
|
+
createDir,
|
|
356
|
+
createFile,
|
|
357
|
+
runRm,
|
|
358
|
+
lsFileInTrash
|
|
359
|
+
} = t.context
|
|
360
|
+
|
|
361
|
+
const mockBin = await createDir({
|
|
362
|
+
name: 'mock-bin-ext',
|
|
363
|
+
under: root
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const mockDate = await createFile({
|
|
367
|
+
name: 'date',
|
|
368
|
+
under: mockBin,
|
|
369
|
+
content: '#!/usr/bin/env bash\necho 12.34.56\n'
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
await fs.chmod(mockDate, 0o755)
|
|
373
|
+
|
|
374
|
+
const filename = `finder-ext-${uuid()}.jpg`
|
|
375
|
+
|
|
376
|
+
for (const content of ['1', '2', '3', '4']) {
|
|
377
|
+
const filepath = await createFile({
|
|
378
|
+
name: filename,
|
|
379
|
+
content
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
const result = await runRm([filepath], {
|
|
383
|
+
env: {
|
|
384
|
+
PATH: `${mockBin}:${process.env.PATH}`
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
assertEmptySuccess(t, result)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const files = (await lsFileInTrash(filename))
|
|
392
|
+
.map(file => path.basename(file))
|
|
393
|
+
.sort((a, b) => a.length - b.length)
|
|
394
|
+
|
|
395
|
+
const baseName = path.basename(filename, '.jpg')
|
|
396
|
+
|
|
397
|
+
t.deepEqual(files, [
|
|
398
|
+
filename,
|
|
399
|
+
`${baseName} 12.34.56.jpg`,
|
|
400
|
+
`${baseName} 12.34.56.jpgX`,
|
|
401
|
+
`${baseName} 12.34.56.jpgXX`
|
|
402
|
+
])
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
!is_rm(type) && !is_as(type) && test(`-I only prompts for more than three files`, async t => {
|
|
406
|
+
const {
|
|
407
|
+
createFile,
|
|
408
|
+
runRm,
|
|
409
|
+
pathExists
|
|
410
|
+
} = t.context
|
|
411
|
+
|
|
412
|
+
const files3 = await Promise.all(['a', 'b', 'c'].map(name => createFile({name})))
|
|
413
|
+
const result3 = await runRm(['-I', ...files3])
|
|
414
|
+
|
|
415
|
+
assertEmptySuccess(t, result3)
|
|
416
|
+
t.true(
|
|
417
|
+
(await Promise.all(files3.map(file => pathExists(file)))).every(exists => !exists),
|
|
418
|
+
'three files should be removed without a once-interactive prompt'
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
const files4 = await Promise.all(['d', 'e', 'f', 'g'].map(name => createFile({name})))
|
|
422
|
+
const result4 = await runRm(['-I', ...files4], {
|
|
423
|
+
input: ['n']
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
t.is(result4.code, 0, 'exit code should be 0 when declining the once-interactive prompt')
|
|
427
|
+
t.true(result4.stdout.includes('remove all arguments?'), 'stdout should contain the once-interactive prompt')
|
|
428
|
+
t.true(
|
|
429
|
+
(await Promise.all(files4.map(file => pathExists(file)))).every(Boolean),
|
|
430
|
+
'files should remain after declining the once-interactive prompt'
|
|
431
|
+
)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
!is_rm(type) && !is_as(type) && IS_MACOS && test(`declining trash directory creation aborts removal`, async t => {
|
|
435
|
+
const {
|
|
436
|
+
root,
|
|
437
|
+
createFile,
|
|
438
|
+
runRm,
|
|
439
|
+
pathExists
|
|
440
|
+
} = t.context
|
|
441
|
+
|
|
442
|
+
const filepath = await createFile({
|
|
443
|
+
name: 'needs-trash'
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
const trashPath = path.join(root, 'missing-trash')
|
|
447
|
+
const result = await runRm([filepath], {
|
|
448
|
+
input: ['no'],
|
|
449
|
+
env: {
|
|
450
|
+
SAFE_RM_TRASH: trashPath
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
t.is(result.code, 1, 'exit code should be 1 when declining trash creation')
|
|
455
|
+
t.true(result.stdout.includes(`Directory "${trashPath}" does not exist`))
|
|
456
|
+
t.true(result.stdout.includes('Canceled!'))
|
|
457
|
+
t.true(await pathExists(filepath), 'file should remain in place')
|
|
458
|
+
t.false(await pathExists(trashPath), 'trash directory should not be created')
|
|
459
|
+
})
|
|
460
|
+
|
|
196
461
|
test(`#22 exit code with -f option`, async t => {
|
|
197
462
|
const {
|
|
198
463
|
source_path,
|
package/test/helper.js
CHANGED
|
@@ -12,6 +12,8 @@ const IS_MACOS = process.platform === 'darwin'
|
|
|
12
12
|
// For linux mock testing
|
|
13
13
|
&& !process.env.SAFE_RM_DEBUG_LINUX
|
|
14
14
|
|
|
15
|
+
const escapeRegExp = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
16
|
+
|
|
15
17
|
const generateContextMethods = (
|
|
16
18
|
rm_command,
|
|
17
19
|
rm_command_env
|
|
@@ -83,7 +85,7 @@ const generateContextMethods = (
|
|
|
83
85
|
child.stdout.on('data', data => {
|
|
84
86
|
stdout += data.toString()
|
|
85
87
|
|
|
86
|
-
if (
|
|
88
|
+
if (/[?:]\s*$/.test(stdout)) {
|
|
87
89
|
if (!child.stdin) {
|
|
88
90
|
reject(new Error('Child process does not support stdin'))
|
|
89
91
|
return
|
|
@@ -144,9 +146,12 @@ const generateContextMethods = (
|
|
|
144
146
|
const _filename = path.basename(filepath)
|
|
145
147
|
const ext = path.extname(_filename)
|
|
146
148
|
const filename = path.basename(_filename, ext)
|
|
149
|
+
const pattern = ext
|
|
150
|
+
? new RegExp(`^${escapeRegExp(filename)}(?: \\d{2}\\.\\d{2}\\.\\d{2})?${escapeRegExp(ext)}X*$`)
|
|
151
|
+
: new RegExp(`^${escapeRegExp(filename)}(?: \\d{2}\\.\\d{2}\\.\\d{2}X*)?$`)
|
|
147
152
|
|
|
148
153
|
const filtered = files.filter(
|
|
149
|
-
f =>
|
|
154
|
+
f => pattern.test(f)
|
|
150
155
|
).map(f => path.join(trash_path, f))
|
|
151
156
|
|
|
152
157
|
return filtered
|