From 96d419f4f16744935d9e1967c08efef9c04828f3 Mon Sep 17 00:00:00 2001 From: Texas Toland Date: Wed, 20 Mar 2024 14:32:02 -0500 Subject: [PATCH] Clean up `file bulk-rename` a little - No `file` prefix similar to other filesystem commands - Optional input especially for globbing - Record closure param with full path for filtering `--directory` (see examples) - `--verbose` table output - `--no-exectute` for dry-run with `--verbose` - Shorthand flags - Parallel renaming for large directories - More tests --- stdlib-candidate/std-rfc/bulk-rename.nu | 49 +++++++++++++ stdlib-candidate/std-rfc/fs.nu | 35 --------- stdlib-candidate/std-rfc/mod.nu | 2 +- stdlib-candidate/tests/bulk-rename.nu | 95 +++++++++++++++++++++++++ stdlib-candidate/tests/fs.nu | 50 ------------- stdlib-candidate/tests/mod.nu | 2 +- 6 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 stdlib-candidate/std-rfc/bulk-rename.nu delete mode 100644 stdlib-candidate/std-rfc/fs.nu create mode 100644 stdlib-candidate/tests/bulk-rename.nu delete mode 100644 stdlib-candidate/tests/fs.nu diff --git a/stdlib-candidate/std-rfc/bulk-rename.nu b/stdlib-candidate/std-rfc/bulk-rename.nu new file mode 100644 index 000000000..f17fdeacc --- /dev/null +++ b/stdlib-candidate/std-rfc/bulk-rename.nu @@ -0,0 +1,49 @@ +# Rename bulk input files in parallel using a closure. +# +# The reason behind this command is quite simple: +# - Sometimes one receives a bunch of files with integer ids: 1, 2, 3, ... +# - These ids come rarely with padding... i.e. 1 instead of 001 when there are 3-digit ids +# - This means that file with id 9 will be sorted way after file with id 1000 +# +# This command allows to do such a task! +# +# Examples: +# Rename `.mise.toml` files to `.mise.local.toml` recursively +# > glob **/.mise.toml | bulk-rename { str append .local } +# +# Rename files in `/foo` with a name that has an id to have 3 digits with 0-padding +# > ls /foo | bulk-rename { |path| +# if $path.input.type == file { +# $path.stem | parse "some_format_{id}" +# | get 0 +# | update id { fill --alignment r --character 0 --width 3 } +# | $"some_format_($in.id)" +# } +# # else skip dirs +# } +export def main [ + update_stem: closure, # The code to rename the file stem: receives the old stem as input and a record param with both `stem` and `input` keys + --verbose (-v), # Show which files were renamed, if any + --no-execute (-n) # Do not make any changes; add --verbose to see what would be made +]: [list -> nothing, list -> table] { + let renamed = par-each --keep-order { |input| + let update_or_keep_stem = { |parts| + do $update_stem { stem: $in input: $input } | default $parts.stem + } + let old = if ($input | describe) == string { + $input + } else { + $input.name # convenience for ls + } + let new = $old | path parse | update stem $update_or_keep_stem | path join + if $new != $old { + if not $no_execute { + mv --force --verbose=$verbose $old $new + } + { old: $old new: $new } + } + } + if $verbose { + $renamed + } +} diff --git a/stdlib-candidate/std-rfc/fs.nu b/stdlib-candidate/std-rfc/fs.nu deleted file mode 100644 index e2fce5210..000000000 --- a/stdlib-candidate/std-rfc/fs.nu +++ /dev/null @@ -1,35 +0,0 @@ -# rename a bulk of files in a directory using a closure -# -# the reason behind this command is quite simple -# - sometimes one receives a bunch of files with integer ids: 1, 2, 3, ... -# - these ids come rarely with padding... i.e. 1 instead of 001 when there are 3-digit ids -# - this means that file with id 9 will be sorted way after file with id 1000 -# -# this command allows to do such a task! -# -# # Examples -# rename files in `/foo` with a name that has an id to have 3 digits with 0-padding -# > file bulk-rename /foo { -# parse "some_format_{id}" -# | get 0 -# | update id { fill --alignment r --character 0 --width 3 } -# | $"some_format_($in.id)" -# } -export def "file bulk-rename" [ - directory: path, # the path where files need to be renamed in bulk - stem_update: closure, # the code to run on the stem of the files: should start with parsing the format and end with reconstructing the same format - --verbose, # be verbose when moving the files around -]: nothing -> nothing { - ls --full-paths $directory | insert new {|row| - $row.name | path parse | update stem $stem_update | path join - } - | each { - if $verbose { - mv --force --verbose $in.name $in.new - } else { - mv --force $in.name $in.new - } - } - - null -} diff --git a/stdlib-candidate/std-rfc/mod.nu b/stdlib-candidate/std-rfc/mod.nu index e4e473b67..b41d733a0 100644 --- a/stdlib-candidate/std-rfc/mod.nu +++ b/stdlib-candidate/std-rfc/mod.nu @@ -2,5 +2,5 @@ export module record/ export module str.nu # commands -export use fs.nu * +export use bulk-rename.nu * export use set-env.nu * diff --git a/stdlib-candidate/tests/bulk-rename.nu b/stdlib-candidate/tests/bulk-rename.nu new file mode 100644 index 000000000..7e624041b --- /dev/null +++ b/stdlib-candidate/tests/bulk-rename.nu @@ -0,0 +1,95 @@ +use std assert +use ../std-rfc 'bulk-rename' + +const fixture = [ + .gitignore + Cargo.toml + LICENSE + README.md + src + test.nu +] + +export def 'test ls' [] { + let expects = [ + .gitignore # hidden by ls + _Cargo.toml + _LICENSE + _README.md + _src + _test.nu + ] + test $expects { + ls $in | bulk-rename { '_' + $in } + } +} + +export def 'test --no-execute' [] { + test $fixture { + ls $in | bulk-rename --no-execute { '_' + $in } + } +} + +export def 'test --verbose' [] { + let expects = [ + # .gitignore unchanged + _Cargo.toml + _LICENSE + _README.md + _src + _test.nu + ] + let renamed = test $fixture { + ls $in | bulk-rename --verbose --no-execute { '_' + $in } + } + assert equal ($renamed.new | each { path basename }) $expects +} + +export def 'test skip-extensions' [] { + let expects = [ + .gitignore + Cargo.toml + LICENSE.txt # changed + README.md + src.txt # changed + test.nu + ] + test $expects { + ls $in | bulk-rename { |path| + if $path.input.name ends-with $path.stem { + $path.stem + .txt + } + } + } +} + +export def 'test glob' [] { + let expects = [ + LICENSE # skipped + _.gitignore + _Cargo.toml + _README.md + _test.nu + src # skipped + ] + test $expects { + glob ($in | path join *.*) | bulk-rename { '_' + $in } + } +} + +def test [expects: list command: closure] { + let test_dir = $nu.temp-path | path join (random uuid) + def actual-files [] { + ls --all --short-names $test_dir | get name | sort + } + # before + mkdir $test_dir + $fixture | each { |name| touch ($test_dir | path join $name) } + assert equal (actual-files) $fixture + # test + let renamed = $test_dir | do $command + assert equal (actual-files) $expects + # after + rm --recursive --force $test_dir + $renamed +} diff --git a/stdlib-candidate/tests/fs.nu b/stdlib-candidate/tests/fs.nu deleted file mode 100644 index 9af5659f9..000000000 --- a/stdlib-candidate/tests/fs.nu +++ /dev/null @@ -1,50 +0,0 @@ -use std assert -use ../std-rfc "file bulk-rename" - -alias rename = file bulk-rename - -export def "test file bulk-rename" [] { - let test_dir = $nu.temp-path | path join (random uuid) - - mkdir $test_dir - seq 1 10 | each {|i| touch ($test_dir | path join $"some_($i)_format.txt") } - - let expected = [ - "some_10_format.txt", - "some_1_format.txt", - "some_2_format.txt", - "some_3_format.txt", - "some_4_format.txt", - "some_5_format.txt", - "some_6_format.txt", - "some_7_format.txt", - "some_8_format.txt", - "some_9_format.txt", - ] - let actual = glob $"($test_dir)/*" | str replace $test_dir "" | str trim --left --char "/" - assert equal ($actual | sort) $expected - - rename $test_dir { - parse "some_{i}_format" - | get 0 - | update i { fill --alignment r --character 0 --width 3 } - | $"some_($in.i)_format" - } - - let expected = [ - "some_001_format.txt", - "some_002_format.txt", - "some_003_format.txt", - "some_004_format.txt", - "some_005_format.txt", - "some_006_format.txt", - "some_007_format.txt", - "some_008_format.txt", - "some_009_format.txt", - "some_010_format.txt", - ] - let actual = glob $"($test_dir)/*" | str replace $test_dir "" | str trim --left --char "/" - assert equal ($actual | sort) $expected - - rm -rf $test_dir -} diff --git a/stdlib-candidate/tests/mod.nu b/stdlib-candidate/tests/mod.nu index 8f03760ec..8818b84f7 100644 --- a/stdlib-candidate/tests/mod.nu +++ b/stdlib-candidate/tests/mod.nu @@ -1,3 +1,3 @@ -export module fs.nu +export module bulk-rename.nu export module record.nu export module str.nu