-
Notifications
You must be signed in to change notification settings - Fork 60
Implement rename
#632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mmclinton
wants to merge
2
commits into
uutils:main
Choose a base branch
from
mmclinton:rename-implement
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Implement rename
#632
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [package] | ||
| name = "uu_rename" | ||
| version = "0.0.1" | ||
| edition = "2021" | ||
| description = "rename ~ (uutils) Rename files by substring replacement" | ||
|
|
||
| [lib] | ||
| path = "src/rename.rs" | ||
|
|
||
| [[bin]] | ||
| name = "rename" | ||
| path = "src/main.rs" | ||
|
|
||
| [dependencies] | ||
| clap = { workspace = true } | ||
| uucore = { workspace = true } | ||
|
|
||
| [target.'cfg(unix)'.dependencies] | ||
| libc = { workspace = true } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # rename | ||
|
|
||
| ``` | ||
| rename [options] <substring> <replacement> <file>... | ||
| ``` | ||
|
|
||
| Rename files. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // This file is part of the uutils util-linux package. | ||
| // | ||
| // For the full copyright and license information, please view the LICENSE | ||
| // file that was distributed with this source code. | ||
|
|
||
| //! Where getopt would have stopped reading options. | ||
| //! | ||
| //! rename(1) permutes options with operands, so a flag is recognized anywhere | ||
| //! before a `--` - after all three operands, or between the substring and the | ||
| //! replacement. `POSIXLY_CORRECT` turns that off, and its mere presence does | ||
| //! it whatever the value: scanning stops at the first argument that is not an | ||
| //! option, and everything from there on is a filename however much it looks | ||
| //! like a flag. | ||
| //! | ||
| //! clap has no such concept, so rather than teach it one the argv is | ||
| //! terminated where getopt would have stopped and clap is handed the result. | ||
|
|
||
| use std::env; | ||
| use std::ffi::{OsStr, OsString}; | ||
|
|
||
| const TERMINATOR: &str = "--"; | ||
|
|
||
| pub(crate) fn collect_getopt_argv(args: impl uucore::Args) -> Vec<OsString> { | ||
| terminate_at_first_operand(args.collect(), env::var_os("POSIXLY_CORRECT").is_some()) | ||
| } | ||
|
|
||
| /// A bare `-` is an operand and not an option, so it stops the scan like any | ||
| /// other name. `--` is longer than one unit and so answers true here, which is | ||
| /// why the caller tests for it first. | ||
| fn is_option(arg: &OsStr) -> bool { | ||
| // ASCII survives this on every platform: the encoded form keeps ASCII bytes | ||
| // as themselves, which is all a leading `-` needs. | ||
| matches!(arg.as_encoded_bytes(), [b'-', _, ..]) | ||
| } | ||
|
|
||
| fn terminate_at_first_operand(mut argv: Vec<OsString>, posixly_correct: bool) -> Vec<OsString> { | ||
| if !posixly_correct { | ||
| return argv; | ||
| } | ||
|
|
||
| let mut operand_at = None; | ||
| // argv[0] is the utility's own name and is never scanned. | ||
| for (index, arg) in argv.iter().enumerate().skip(1) { | ||
| if arg == TERMINATOR { | ||
| // getopt consumes this one itself, so there is nothing to insert | ||
| // and nothing after it to protect. | ||
| return argv; | ||
| } | ||
| if !is_option(arg) { | ||
| operand_at = Some(index); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if let Some(index) = operand_at { | ||
| argv.insert(index, OsString::from(TERMINATOR)); | ||
| } | ||
|
|
||
| argv | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // This file is part of the uutils util-linux package. | ||
| // | ||
| // For the full copyright and license information, please view the LICENSE | ||
| // file that was distributed with this source code. | ||
|
|
||
| //! The bridge between OsStr and the code units the engine works on. | ||
| //! | ||
| //! Both directions are lossless: bytes on unix, UTF-16 units on Windows. The | ||
| //! path separator is 0x2F in either encoding, so the scope rule needs no | ||
| //! special casing. `\` is deliberately not a separator here: rename(1) | ||
| //! documents `/`, and Rust accepts `/` on Windows too. | ||
| //! | ||
| //! Only unix and windows are covered; a third target does not build. | ||
| //! | ||
| //! Writing a name is done here rather than through `uucore::display::OsWrite` | ||
| //! because that trait refuses a Windows name that is not valid Unicode, with | ||
| //! `io::ErrorKind::InvalidData`, where a report has to print whatever the name | ||
| //! actually is - and because it has no impl for stderr. | ||
|
|
||
| #[cfg(unix)] | ||
| mod imp { | ||
| use std::ffi::{OsStr, OsString}; | ||
| use std::io::{self, Write}; | ||
| use std::os::unix::ffi::{OsStrExt, OsStringExt}; | ||
| use std::path::Path; | ||
|
|
||
| pub(crate) type Unit = u8; | ||
| pub(crate) const SEP: Unit = b'/'; | ||
|
|
||
| pub(crate) fn units(s: &OsStr) -> Vec<Unit> { | ||
| s.as_bytes().to_vec() | ||
| } | ||
|
|
||
| pub(crate) fn os_string(units: Vec<Unit>) -> OsString { | ||
| OsString::from_vec(units) | ||
| } | ||
|
|
||
| /// Filenames are written raw. A name holding an invalid byte is emitted as | ||
| /// that byte, which is what C util-linux does and what makes its -v output | ||
| /// round-trip through a shell. | ||
| pub(crate) fn write_os(w: &mut impl Write, s: &OsStr) -> io::Result<()> { | ||
| w.write_all(s.as_bytes()) | ||
| } | ||
|
|
||
| pub(crate) fn symlink(target: &OsStr, link: &Path) -> io::Result<()> { | ||
| std::os::unix::fs::symlink(target, link) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(windows)] | ||
| mod imp { | ||
| use std::ffi::{OsStr, OsString}; | ||
| use std::io::{self, Write}; | ||
| use std::os::windows::ffi::{OsStrExt, OsStringExt}; | ||
| use std::path::Path; | ||
|
|
||
| pub(crate) type Unit = u16; | ||
| pub(crate) const SEP: Unit = b'/' as u16; | ||
|
|
||
| pub(crate) fn units(s: &OsStr) -> Vec<Unit> { | ||
| s.encode_wide().collect() | ||
| } | ||
|
|
||
| pub(crate) fn os_string(units: Vec<Unit>) -> OsString { | ||
| OsString::from_wide(&units) | ||
| } | ||
|
|
||
| /// There is no raw byte form of a Windows filename, so a lone surrogate is | ||
| /// replaced here. Unlike the unix arm this is lossy, and only for output. | ||
| pub(crate) fn write_os(w: &mut impl Write, s: &OsStr) -> io::Result<()> { | ||
| w.write_all(s.to_string_lossy().as_bytes()) | ||
| } | ||
|
|
||
| /// Windows splits the call in two and needs the privilege or Developer | ||
| /// Mode to make either. Which one is right depends on what the target | ||
| /// names, and a relative target is stored relative to the LINK, so it is | ||
| /// resolved from the link's own directory - not from the process working | ||
| /// directory, which would answer about a different object and leave a link | ||
| /// Windows reports as broken. The -o guard resolves relative to the | ||
| /// process instead, because that side has C util-linux to conform to and | ||
| /// this side has none. | ||
| /// | ||
| /// A target that does not resolve is treated as a file, and whatever the | ||
| /// OS then says is passed back to the caller rather than pre-empted here. | ||
| pub(crate) fn symlink(target: &OsStr, link: &Path) -> io::Result<()> { | ||
| let base = link.parent().unwrap_or_else(|| Path::new("")); | ||
| if base.join(target).is_dir() { | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } else { | ||
| std::os::windows::fs::symlink_file(target, link) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub(crate) use imp::{os_string, symlink, units, write_os, Unit, SEP}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // This file is part of the uutils util-linux package. | ||
| // | ||
| // For the full copyright and license information, please view the LICENSE | ||
| // file that was distributed with this source code. | ||
|
|
||
| //! Per-operand diagnostics. | ||
| //! | ||
| //! Each variant keeps the path it complains about as an `OsString` and writes | ||
| //! it out as the bytes it is made of. A name that is not valid UTF-8 is exactly | ||
| //! the kind of name people reach for rename to fix, and C util-linux prints it | ||
| //! raw on stderr just as it does on stdout; rendering it through a `String` | ||
| //! would replace it with U+FFFD. | ||
| //! | ||
| //! That is also why these do not implement `Display`. `Formatter` writes | ||
| //! `&str`, so the diagnostic and a lossless name are mutually exclusive. | ||
| //! | ||
| //! The trailing " (os error NN)" that Rust appends to an io::Error is stripped | ||
| //! back off: C util-linux prints the bare strerror text and nothing else. | ||
|
|
||
| use std::ffi::OsString; | ||
| use std::io::{self, Write}; | ||
|
|
||
| use uucore::error::strip_errno; | ||
|
|
||
| use crate::output::Output; | ||
|
|
||
| #[derive(Debug)] | ||
| pub(crate) enum RenameError { | ||
| NotAccessible { | ||
| path: OsString, | ||
| source: io::Error, | ||
| }, | ||
|
|
||
| NotASymlink { | ||
| path: OsString, | ||
| }, | ||
|
|
||
| RenameFailed { | ||
| old: OsString, | ||
| new: OsString, | ||
| source: io::Error, | ||
| }, | ||
|
|
||
| /// The two halves of a symlink rewrite report separately, as they do for | ||
| /// C util-linux: a link in a directory that denies writes fails at the | ||
| /// unlink, and says so. | ||
| UnlinkFailed { | ||
| path: OsString, | ||
| source: io::Error, | ||
| }, | ||
|
|
||
| /// This one names the target it tried to create, so an empty target prints | ||
| /// two spaces between "to" and "failed". | ||
| SymlinkFailed { | ||
| path: OsString, | ||
| new: OsString, | ||
| source: io::Error, | ||
| }, | ||
|
|
||
| WriteFailed { | ||
| source: io::Error, | ||
| }, | ||
| } | ||
|
|
||
| impl RenameError { | ||
| /// The `rename: ` prefix is written here rather than by `show_error!`, | ||
| /// which renders through `format!` and cannot carry a name losslessly. | ||
| pub(crate) fn report<W: Write>(&self, out: &mut Output<W>) { | ||
| out.write_bytes(uucore::util_name().as_bytes()); | ||
| out.write_bytes(b": "); | ||
|
|
||
| match self { | ||
| Self::NotAccessible { path, source } => { | ||
| out.write_os(path); | ||
| out.write_bytes(b": not accessible: "); | ||
| out.write_bytes(strip_errno(source).as_bytes()); | ||
| } | ||
| Self::NotASymlink { path } => { | ||
| out.write_os(path); | ||
| out.write_bytes(b": not a symbolic link"); | ||
| } | ||
| Self::RenameFailed { old, new, source } => { | ||
| out.write_os(old); | ||
| out.write_bytes(b": rename to "); | ||
| out.write_os(new); | ||
| out.write_bytes(b" failed: "); | ||
| out.write_bytes(strip_errno(source).as_bytes()); | ||
| } | ||
| Self::UnlinkFailed { path, source } => { | ||
| out.write_os(path); | ||
| out.write_bytes(b": unlink failed: "); | ||
| out.write_bytes(strip_errno(source).as_bytes()); | ||
| } | ||
| Self::SymlinkFailed { path, new, source } => { | ||
| out.write_os(path); | ||
| out.write_bytes(b": symlinking to "); | ||
| out.write_os(new); | ||
| out.write_bytes(b" failed: "); | ||
| out.write_bytes(strip_errno(source).as_bytes()); | ||
| } | ||
| Self::WriteFailed { source } => { | ||
| out.write_bytes(b"write error: "); | ||
| out.write_bytes(strip_errno(source).as_bytes()); | ||
| } | ||
| } | ||
|
|
||
| out.write_bytes(b"\n"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| uucore::bin!(uu_rename); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you said dropping it costs nothing, so let's drop it :)
no other utility rewrites argv, i'd rather keep clap's behavior everywhere