Skip to content

Commit

Permalink
[stdlib-candidate] Clean up file bulk-rename a little (nushell#798)
Browse files Browse the repository at this point in the history
[Generated diff](https://www.diffnow.com/report/xmq4f) for command
because move broke it.

- No `file` prefix similar to other [filesystem
commands](https://www.nushell.sh/commands/categories/filesystem.html)
- Input paths instead of directory param for filters or globbing
- Record closure param with original path (`$in` is still stem)
- `--verbose` table output
- `--no-exectute` for dry run with `--verbose`
- Shorthand flags
- Parallel renaming for large directories
- More tests

@amtoine Request feedback 🙏🏼 Happy to revise ot revert anything!

```console
❯ bulk-rename -h
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
   }

Usage:
  > main {flags} <update_stem>

Flags:
  -v, --verbose - Show which files were renamed, if any
  -n, --no-execute - Do not make any changes; add --verbose to see what would be made
whitespace bug: nushell/nushell#12264
  -h, --help - Display the help message for this command

Parameters:
  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

Input/output types:
  ╭───┬───────────┬─────────────────────────────────╮
  │ # │   input   │             output              │
  ├───┼───────────┼─────────────────────────────────┤
  │ 0 │ list<any> │ nothing                         │
  │ 1 │ list<any> │ table<old: string, new: string> │
  ╰───┴───────────┴─────────────────────────────────╯
```
  • Loading branch information
texastoland committed Mar 30, 2024
1 parent f399769 commit 268201e
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 87 deletions.
49 changes: 49 additions & 0 deletions stdlib-candidate/std-rfc/bulk-rename.nu
Original file line number Diff line number Diff line change
@@ -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<any> -> nothing, list<any> -> table<old: path new: path>] {
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
}
}
35 changes: 0 additions & 35 deletions stdlib-candidate/std-rfc/fs.nu

This file was deleted.

2 changes: 1 addition & 1 deletion stdlib-candidate/std-rfc/mod.nu
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
export module record/
export module str/
# commands
export use fs.nu *
export use bulk-rename.nu *
export use set-env.nu *
95 changes: 95 additions & 0 deletions stdlib-candidate/tests/bulk-rename.nu
Original file line number Diff line number Diff line change
@@ -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<string> 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
}
50 changes: 0 additions & 50 deletions stdlib-candidate/tests/fs.nu

This file was deleted.

2 changes: 1 addition & 1 deletion stdlib-candidate/tests/mod.nu
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export module fs.nu
export module bulk-rename.nu
export module record.nu
export module str_xpend.nu

0 comments on commit 268201e

Please sign in to comment.