Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fd for openSUSE:Factory checked in at 2024-05-09 17:28:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fd (Old) and /work/SRC/openSUSE:Factory/.fd.new.1880 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fd" Thu May 9 17:28:50 2024 rev:23 rq:1172852 version:10.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/fd/fd.changes 2024-05-08 11:41:50.209365084 +0200 +++ /work/SRC/openSUSE:Factory/.fd.new.1880/fd.changes 2024-05-09 17:28:57.936062954 +0200 @@ -1,0 +2,13 @@ +Wed May 8 17:44:56 UTC 2024 - Michael Vetter <[email protected]> + +- Update to version 10.1.0: + * Add --format to help in README + * Prepare for 10.1.0 release + * Implement option for printing custom formats + * docs: Make auto option for --strip-cwd-prefix more clear + * feat: Add option to always include cwd prefix + * Add upcoming to changelog + * Attempt to add aarch64 osx build to CI + * Fix #1085 for real + +------------------------------------------------------------------- Old: ---- fd-10.0.0.tar.xz New: ---- fd-10.1.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fd.spec ++++++ --- /var/tmp/diff_new_pack.fFL0lc/_old 2024-05-09 17:28:59.488119618 +0200 +++ /var/tmp/diff_new_pack.fFL0lc/_new 2024-05-09 17:28:59.488119618 +0200 @@ -17,7 +17,7 @@ Name: fd -Version: 10.0.0 +Version: 10.1.0 Release: 0 Summary: An alternative to the "find" utility License: Apache-2.0 AND MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.fFL0lc/_old 2024-05-09 17:28:59.532121224 +0200 +++ /var/tmp/diff_new_pack.fFL0lc/_new 2024-05-09 17:28:59.536121370 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/sharkdp/fd.git</param> <param name="versionformat">@PARENT_TAG@</param> <param name="scm">git</param> - <param name="revision">v10.0.0</param> + <param name="revision">v10.1.0</param> <param name="match-tag">*</param> <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param> <param name="versionrewrite-replacement">\1</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.fFL0lc/_old 2024-05-09 17:28:59.564122392 +0200 +++ /var/tmp/diff_new_pack.fFL0lc/_new 2024-05-09 17:28:59.564122392 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/sharkdp/fd.git</param> - <param name="changesrevision">8acd7722f09ff45ef51335751160e0a8dcc096dc</param></service></servicedata> + <param name="changesrevision">289a68bac3938d56b176d5b15fab312fd538e949</param></service></servicedata> (No newline at EOF) ++++++ fd-10.0.0.tar.xz -> fd-10.1.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/.github/workflows/CICD.yml new/fd-10.1.0/.github/workflows/CICD.yml --- old/fd-10.0.0/.github/workflows/CICD.yml 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/.github/workflows/CICD.yml 2024-05-08 08:40:02.000000000 +0200 @@ -88,6 +88,7 @@ - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } - { target: x86_64-apple-darwin , os: macos-12 } + - { target: aarch64-apple-darwin , os: macos-14 } - { target: x86_64-pc-windows-gnu , os: windows-2019 } - { target: x86_64-pc-windows-msvc , os: windows-2019 } - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } @@ -134,11 +135,7 @@ - name: Build shell: bash - run: | - case ${{ matrix.job.target }} in - aarch64-*) export JEMALLOC_SYS_WITH_LG_PAGE=16 ;; - esac; - $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} + run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} - name: Set binary name & path id: bin diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/CHANGELOG.md new/fd-10.1.0/CHANGELOG.md --- old/fd-10.0.0/CHANGELOG.md 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/CHANGELOG.md 2024-05-08 08:40:02.000000000 +0200 @@ -1,3 +1,18 @@ +# 10.1.0 + +## Features + +- Allow passing an optional argument to `--strip-cwd-prefix` of "always", "never", or "auto". to force whether the cwd prefix is stripped or not. +- Add a `--format` option which allows using a format template for direct ouput similar to the template used for `--exec`. (#1043) + +## Bugfixes +- Fix aarch64 page size again. This time it should actually work. (#1085, #1549) (@tavianator) + + +## Other + +- aarch64-apple-darwin target added to builds on the release page. Note that this is a tier 2 rust target. + # v10.0.0 ## Features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/Cargo.lock new/fd-10.1.0/Cargo.lock --- old/fd-10.0.0/Cargo.lock 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/Cargo.lock 2024-05-08 08:40:02.000000000 +0200 @@ -308,7 +308,7 @@ [[package]] name = "fd-find" -version = "10.0.0" +version = "10.1.0" dependencies = [ "aho-corasick", "anyhow", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/Cargo.toml new/fd-10.1.0/Cargo.toml --- old/fd-10.0.0/Cargo.toml 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/Cargo.toml 2024-05-08 08:40:02.000000000 +0200 @@ -16,7 +16,7 @@ name = "fd-find" readme = "README.md" repository = "https://github.com/sharkdp/fd" -version = "10.0.0" +version = "10.1.0" edition= "2021" rust-version = "1.77.2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/Cross.toml new/fd-10.1.0/Cross.toml --- old/fd-10.0.0/Cross.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/fd-10.1.0/Cross.toml 2024-05-08 08:40:02.000000000 +0200 @@ -0,0 +1,6 @@ +# https://github.com/sharkdp/fd/issues/1085 +[target.aarch64-unknown-linux-gnu.env] +passthrough = ["JEMALLOC_SYS_WITH_LG_PAGE=16"] + +[target.aarch64-unknown-linux-musl.env] +passthrough = ["JEMALLOC_SYS_WITH_LG_PAGE=16"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/README.md new/fd-10.1.0/README.md --- old/fd-10.0.0/README.md 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/README.md 2024-05-08 08:40:02.000000000 +0200 @@ -340,6 +340,7 @@ --changed-within <date|dur> Filter by file modification time (newer than) --changed-before <date|dur> Filter by file modification time (older than) -o, --owner <user:group> Filter by owning user and/or group + --format <fmt> Print results according to template -x, --exec <cmd>... Execute a command for each search result -X, --exec-batch <cmd>... Execute a command with all search results at once -c, --color <when> When to use colors [default: auto] [possible values: auto, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/contrib/completion/_fd new/fd-10.1.0/contrib/completion/_fd --- old/fd-10.0.0/contrib/completion/_fd 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/contrib/completion/_fd 2024-05-08 08:40:02.000000000 +0200 @@ -162,7 +162,7 @@ $no'(*)*--search-path=[set search path (instead of positional <path> arguments)]:directory:_files -/' + strip-cwd-prefix - $no'(strip-cwd-prefix exec-cmds)--strip-cwd-prefix[Strip ./ prefix when output is redirected]' + $no'(strip-cwd-prefix exec-cmds)--strip-cwd-prefix=[When to strip ./]:when:(always never auto)' + and '--and=[additional required search path]:pattern' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/doc/fd.1 new/fd-10.1.0/doc/fd.1 --- old/fd-10.0.0/doc/fd.1 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/doc/fd.1 2024-05-08 08:40:02.000000000 +0200 @@ -156,9 +156,20 @@ Enable the display of filesystem errors for situations such as insufficient permissions or dead symlinks. .TP -.B \-\-strip-cwd-prefix -By default, relative paths are prefixed with './' when the output goes to a non interactive terminal -(TTY). Use this flag to disable this behaviour. +.B \-\-strip-cwd-prefix [when] +By default, relative paths are prefixed with './' when -x/--exec, +-X/--exec-batch, or -0/--print0 are given, to reduce the risk of a +path starting with '-' being treated as a command line option. Use +this flag to change this behavior. If this flag is used without a value, +it is equivalent to passing "always". Possible values are: +.RS +.IP never +Never strip the ./ at the beginning of paths +.IP always +Always strip the ./ at the beginning of paths +.IP auto +Only strip if used with --exec, --exec-batch, or --print0. That is, it resets to the default behavior. +.RE .TP .B \-\-one\-file\-system, \-\-mount, \-\-xdev By default, fd will traverse the file system tree as far as other options dictate. With this flag, fd ensures that it does not descend into a different file system than the one it started in. Comparable to the -mount or -xdev filters of find(1). @@ -364,6 +375,30 @@ Provide paths to search as an alternative to the positional \fIpath\fR argument. Changes the usage to \'fd [FLAGS/OPTIONS] \-\-search\-path PATH \-\-search\-path PATH2 [PATTERN]\' .TP +.BI "\-\-format " fmt +Specify a template string that is used for printing a line for each file found. + +The following placeholders are substituted into the string for each file before printing: +.RS +.IP {} +path (of the current search result) +.IP {/} +basename +.IP {//} +parent directory +.IP {.} +path without file extension +.IP {/.} +basename without file extension +.IP {{ +literal '{' (an escape sequence) +.IP }} +literal '}' (an escape sequence) +.P +Notice that you can use "{{" and "}}" to escape "{" and "}" respectively, which is especially +useful if you need to include the literal text of one of the above placeholders. +.RE +.TP .BI "\-x, \-\-exec " command .RS Execute @@ -384,29 +419,12 @@ --threads=1, the order is determined by the operating system and may not be what you expect. Thus, it is recommended that you don't rely on any ordering of the results. -The following placeholders are substituted before the command is executed: -.RS -.IP {} -path (of the current search result) -.IP {/} -basename -.IP {//} -parent directory -.IP {.} -path without file extension -.IP {/.} -basename without file extension -.IP {{ -literal '{' (an escape sequence) -.IP }} -literal '}' (an escape sequence) -.RE +Before executing the command, any placeholder patterns in the command are replaced with the +corresponding values for the current file. The same placeholders are used as in the "\-\-format" +option. If no placeholder is present, an implicit "{}" at the end is assumed. -Notice that you can use "{{" and "}}" to escape "{" and "}" respectively, which is especially -useful if you need to include the literal text of one of the above placeholders. - Examples: - find all *.zip files and unzip them: @@ -430,19 +448,9 @@ The order of the arguments is non-deterministic and should not be relied upon. -One of the following placeholders is substituted before the command is executed: -.RS -.IP {} -path (of all search results) -.IP {/} -basename -.IP {//} -parent directory -.IP {.} -path without file extension -.IP {/.} -basename without file extension -.RE +This uses the same placeholders as "\-\-format" and "\-\-exec", but instead of expanding +once per command invocation each argument containing a placeholder is expanding for every +file in a batch and passed as separate arguments. If no placeholder is present, an implicit "{}" at the end is assumed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/cli.rs new/fd-10.1.0/src/cli.rs --- old/fd-10.0.0/src/cli.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/cli.rs 2024-05-08 08:40:02.000000000 +0200 @@ -452,6 +452,20 @@ )] pub owner: Option<OwnerFilter>, + /// Instead of printing the file normally, print the format string with the following placeholders replaced: + /// '{}': path (of the current search result) + /// '{/}': basename + /// '{//}': parent directory + /// '{.}': path without file extension + /// '{/.}': basename without file extension + #[arg( + long, + value_name = "fmt", + help = "Print results according to template", + conflicts_with = "list_details" + )] + pub format: Option<String>, + #[command(flatten)] pub exec: Exec, @@ -617,9 +631,10 @@ /// By default, relative paths are prefixed with './' when -x/--exec, /// -X/--exec-batch, or -0/--print0 are given, to reduce the risk of a /// path starting with '-' being treated as a command line option. Use - /// this flag to disable this behaviour. - #[arg(long, conflicts_with_all(&["path", "search_path"]), hide_short_help = true, long_help)] - pub strip_cwd_prefix: bool, + /// this flag to change this behavior. If this flag is used without a value, + /// it is equivalent to passing "always". + #[arg(long, conflicts_with_all(&["path", "search_path"]), value_name = "when", hide_short_help = true, require_equals = true, long_help)] + strip_cwd_prefix: Option<Option<StripCwdWhen>>, /// By default, fd will traverse the file system tree as far as other options /// dictate. With this flag, fd ensures that it does not descend into a @@ -700,6 +715,16 @@ .or_else(|| self.max_one_result.then_some(1)) } + pub fn strip_cwd_prefix<P: FnOnce() -> bool>(&self, auto_pred: P) -> bool { + use self::StripCwdWhen::*; + self.no_search_paths() + && match self.strip_cwd_prefix.map_or(Auto, |o| o.unwrap_or(Always)) { + Auto => auto_pred(), + Always => true, + Never => false, + } + } + #[cfg(feature = "completions")] pub fn gen_completions(&self) -> anyhow::Result<Option<Shell>> { self.gen_completions @@ -760,6 +785,16 @@ Never, } +#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)] +pub enum StripCwdWhen { + /// Use the default behavior + Auto, + /// Always strip the ./ at the beginning of paths + Always, + /// Never strip the ./ + Never, +} + // there isn't a derive api for getting grouped values yet, // so we have to use hand-rolled parsing for exec and exec-batch pub struct Exec { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/config.rs new/fd-10.1.0/src/config.rs --- old/fd-10.0.0/src/config.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/config.rs 2024-05-08 08:40:02.000000000 +0200 @@ -8,6 +8,7 @@ #[cfg(unix)] use crate::filter::OwnerFilter; use crate::filter::{SizeFilter, TimeFilter}; +use crate::fmt::FormatTemplate; /// Configuration options for *fd*. pub struct Config { @@ -85,6 +86,9 @@ /// The value (if present) will be a lowercase string without leading dots. pub extensions: Option<RegexSet>, + /// A format string to use to format results, similarly to exec + pub format: Option<FormatTemplate>, + /// If a value is supplied, each item found will be used to generate and execute commands. pub command: Option<Arc<CommandSet>>, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/exec/input.rs new/fd-10.1.0/src/exec/input.rs --- old/fd-10.0.0/src/exec/input.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/exec/input.rs 1970-01-01 01:00:00.000000000 +0100 @@ -1,87 +0,0 @@ -use std::ffi::{OsStr, OsString}; -use std::path::{Path, PathBuf}; - -use crate::filesystem::strip_current_dir; - -/// Removes the parent component of the path -pub fn basename(path: &Path) -> &OsStr { - path.file_name().unwrap_or(path.as_os_str()) -} - -/// Removes the extension from the path -pub fn remove_extension(path: &Path) -> OsString { - let dirname = dirname(path); - let stem = path.file_stem().unwrap_or(path.as_os_str()); - - let path = PathBuf::from(dirname).join(stem); - - strip_current_dir(&path).to_owned().into_os_string() -} - -/// Removes the basename from the path. -pub fn dirname(path: &Path) -> OsString { - path.parent() - .map(|p| { - if p == OsStr::new("") { - OsString::from(".") - } else { - p.as_os_str().to_owned() - } - }) - .unwrap_or_else(|| path.as_os_str().to_owned()) -} - -#[cfg(test)] -mod path_tests { - use super::*; - use std::path::MAIN_SEPARATOR_STR; - - fn correct(input: &str) -> String { - input.replace('/', MAIN_SEPARATOR_STR) - } - - macro_rules! func_tests { - ($($name:ident: $func:ident for $input:expr => $output:expr)+) => { - $( - #[test] - fn $name() { - let input_path = PathBuf::from(&correct($input)); - let output_string = OsString::from(correct($output)); - assert_eq!($func(&input_path), output_string); - } - )+ - } - } - - func_tests! { - remove_ext_simple: remove_extension for "foo.txt" => "foo" - remove_ext_dir: remove_extension for "dir/foo.txt" => "dir/foo" - hidden: remove_extension for ".foo" => ".foo" - remove_ext_utf8: remove_extension for "ð.txt" => "ð" - remove_ext_empty: remove_extension for "" => "" - - basename_simple: basename for "foo.txt" => "foo.txt" - basename_dir: basename for "dir/foo.txt" => "foo.txt" - basename_empty: basename for "" => "" - basename_utf8_0: basename for "ð/foo.txt" => "foo.txt" - basename_utf8_1: basename for "dir/ð.txt" => "ð.txt" - - dirname_simple: dirname for "foo.txt" => "." - dirname_dir: dirname for "dir/foo.txt" => "dir" - dirname_utf8_0: dirname for "ð/foo.txt" => "ð" - dirname_utf8_1: dirname for "dir/ð.txt" => "dir" - } - - #[test] - #[cfg(windows)] - fn dirname_root() { - assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:")); - assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\")); - } - - #[test] - #[cfg(not(windows))] - fn dirname_root() { - assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/")); - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/exec/mod.rs new/fd-10.1.0/src/exec/mod.rs --- old/fd-10.0.0/src/exec/mod.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/exec/mod.rs 2024-05-08 08:40:02.000000000 +0200 @@ -1,13 +1,10 @@ mod command; -mod input; mod job; -mod token; -use std::borrow::Cow; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::io; use std::iter; -use std::path::{Component, Path, PathBuf, Prefix}; +use std::path::{Path, PathBuf}; use std::process::Stdio; use std::sync::Mutex; @@ -15,11 +12,10 @@ use argmax::Command; use crate::exit_codes::{merge_exitcodes, ExitCode}; +use crate::fmt::{FormatTemplate, Token}; use self::command::{execute_commands, handle_cmd_error}; -use self::input::{basename, dirname, remove_extension}; pub use self::job::{batch, job}; -use self::token::{tokenize, Token}; /// Execution mode of the command #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -131,7 +127,7 @@ #[derive(Debug)] struct CommandBuilder { pre_args: Vec<OsString>, - path_arg: ArgumentTemplate, + path_arg: FormatTemplate, post_args: Vec<OsString>, cmd: Command, count: usize, @@ -220,7 +216,7 @@ /// `generate_and_execute()` method will be used to generate a command and execute it. #[derive(Debug, Clone, PartialEq)] struct CommandTemplate { - args: Vec<ArgumentTemplate>, + args: Vec<FormatTemplate>, } impl CommandTemplate { @@ -235,7 +231,7 @@ for arg in input { let arg = arg.as_ref(); - let tmpl = tokenize(arg); + let tmpl = FormatTemplate::parse(arg); has_placeholder |= tmpl.has_tokens(); args.push(tmpl); } @@ -251,7 +247,7 @@ // If a placeholder token was not supplied, append one at the end of the command. if !has_placeholder { - args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder])); + args.push(FormatTemplate::Tokens(vec![Token::Placeholder])); } Ok(CommandTemplate { args }) @@ -274,111 +270,6 @@ } } -/// Represents a template for a single command argument. -/// -/// The argument is either a collection of `Token`s including at least one placeholder variant, or -/// a fixed text. -#[derive(Clone, Debug, PartialEq)] -enum ArgumentTemplate { - Tokens(Vec<Token>), - Text(String), -} - -impl ArgumentTemplate { - pub fn has_tokens(&self) -> bool { - matches!(self, ArgumentTemplate::Tokens(_)) - } - - /// Generate an argument from this template. If path_separator is Some, then it will replace - /// the path separator in all placeholder tokens. Text arguments and tokens are not affected by - /// path separator substitution. - pub fn generate(&self, path: impl AsRef<Path>, path_separator: Option<&str>) -> OsString { - use self::Token::*; - let path = path.as_ref(); - - match *self { - ArgumentTemplate::Tokens(ref tokens) => { - let mut s = OsString::new(); - for token in tokens { - match *token { - Basename => s.push(Self::replace_separator(basename(path), path_separator)), - BasenameNoExt => s.push(Self::replace_separator( - &remove_extension(basename(path).as_ref()), - path_separator, - )), - NoExt => s.push(Self::replace_separator( - &remove_extension(path), - path_separator, - )), - Parent => s.push(Self::replace_separator(&dirname(path), path_separator)), - Placeholder => { - s.push(Self::replace_separator(path.as_ref(), path_separator)) - } - Text(ref string) => s.push(string), - } - } - s - } - ArgumentTemplate::Text(ref text) => OsString::from(text), - } - } - - /// Replace the path separator in the input with the custom separator string. If path_separator - /// is None, simply return a borrowed Cow<OsStr> of the input. Otherwise, the input is - /// interpreted as a Path and its components are iterated through and re-joined into a new - /// OsString. - fn replace_separator<'a>(path: &'a OsStr, path_separator: Option<&str>) -> Cow<'a, OsStr> { - // fast-path - no replacement necessary - if path_separator.is_none() { - return Cow::Borrowed(path); - } - - let path_separator = path_separator.unwrap(); - let mut out = OsString::with_capacity(path.len()); - let mut components = Path::new(path).components().peekable(); - - while let Some(comp) = components.next() { - match comp { - // Absolute paths on Windows are tricky. A Prefix component is usually a drive - // letter or UNC path, and is usually followed by RootDir. There are also - // "verbatim" prefixes beginning with "\\?\" that skip normalization. We choose to - // ignore verbatim path prefixes here because they're very rare, might be - // impossible to reach here, and there's no good way to deal with them. If users - // are doing something advanced involving verbatim windows paths, they can do their - // own output filtering with a tool like sed. - Component::Prefix(prefix) => { - if let Prefix::UNC(server, share) = prefix.kind() { - // Prefix::UNC is a parsed version of '\\server\share' - out.push(path_separator); - out.push(path_separator); - out.push(server); - out.push(path_separator); - out.push(share); - } else { - // All other Windows prefix types are rendered as-is. This results in e.g. "C:" for - // drive letters. DeviceNS and Verbatim* prefixes won't have backslashes converted, - // but they're not returned by directories fd can search anyway so we don't worry - // about them. - out.push(comp.as_os_str()); - } - } - - // Root directory is always replaced with the custom separator. - Component::RootDir => out.push(path_separator), - - // Everything else is joined normally, with a trailing separator if we're not last - _ => { - out.push(comp.as_os_str()); - if components.peek().is_some() { - out.push(path_separator); - } - } - } - } - Cow::Owned(out) - } -} - #[cfg(test)] mod tests { use super::*; @@ -398,9 +289,9 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Text("${SHELL}:".into()), - ArgumentTemplate::Tokens(vec![Token::Placeholder]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Text("${SHELL}:".into()), + FormatTemplate::Tokens(vec![Token::Placeholder]), ] }], mode: ExecutionMode::OneByOne, @@ -415,8 +306,8 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Tokens(vec![Token::NoExt]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Tokens(vec![Token::NoExt]), ], }], mode: ExecutionMode::OneByOne, @@ -431,8 +322,8 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Tokens(vec![Token::Basename]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Tokens(vec![Token::Basename]), ], }], mode: ExecutionMode::OneByOne, @@ -447,8 +338,8 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Tokens(vec![Token::Parent]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Tokens(vec![Token::Parent]), ], }], mode: ExecutionMode::OneByOne, @@ -463,8 +354,8 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Tokens(vec![Token::BasenameNoExt]), ], }], mode: ExecutionMode::OneByOne, @@ -494,9 +385,9 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("cp".into()), - ArgumentTemplate::Tokens(vec![Token::Placeholder]), - ArgumentTemplate::Tokens(vec![ + FormatTemplate::Text("cp".into()), + FormatTemplate::Tokens(vec![Token::Placeholder]), + FormatTemplate::Tokens(vec![ Token::BasenameNoExt, Token::Text(".ext".into()) ]), @@ -514,8 +405,8 @@ CommandSet { commands: vec![CommandTemplate { args: vec![ - ArgumentTemplate::Text("echo".into()), - ArgumentTemplate::Tokens(vec![Token::NoExt]), + FormatTemplate::Text("echo".into()), + FormatTemplate::Tokens(vec![Token::NoExt]), ], }], mode: ExecutionMode::Batch, @@ -540,7 +431,7 @@ #[test] fn generate_custom_path_separator() { - let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]); + let arg = FormatTemplate::Tokens(vec![Token::Placeholder]); macro_rules! check { ($input:expr, $expected:expr) => { assert_eq!(arg.generate($input, Some("#")), OsString::from($expected)); @@ -555,7 +446,7 @@ #[cfg(windows)] #[test] fn generate_custom_path_separator_windows() { - let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]); + let arg = FormatTemplate::Tokens(vec![Token::Placeholder]); macro_rules! check { ($input:expr, $expected:expr) => { assert_eq!(arg.generate($input, Some("#")), OsString::from($expected)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/exec/token.rs new/fd-10.1.0/src/exec/token.rs --- old/fd-10.0.0/src/exec/token.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/exec/token.rs 1970-01-01 01:00:00.000000000 +0100 @@ -1,98 +0,0 @@ -use aho_corasick::AhoCorasick; -use std::fmt::{self, Display, Formatter}; -use std::sync::OnceLock; - -use super::ArgumentTemplate; - -/// Designates what should be written to a buffer -/// -/// Each `Token` contains either text, or a placeholder variant, which will be used to generate -/// commands after all tokens for a given command template have been collected. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Token { - Placeholder, - Basename, - Parent, - NoExt, - BasenameNoExt, - Text(String), -} - -impl Display for Token { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match *self { - Token::Placeholder => f.write_str("{}")?, - Token::Basename => f.write_str("{/}")?, - Token::Parent => f.write_str("{//}")?, - Token::NoExt => f.write_str("{.}")?, - Token::BasenameNoExt => f.write_str("{/.}")?, - Token::Text(ref string) => f.write_str(string)?, - } - Ok(()) - } -} - -static PLACEHOLDERS: OnceLock<AhoCorasick> = OnceLock::new(); - -pub(super) fn tokenize(input: &str) -> ArgumentTemplate { - // NOTE: we assume that { and } have the same length - const BRACE_LEN: usize = '{'.len_utf8(); - let mut tokens = Vec::new(); - let mut remaining = input; - let mut buf = String::new(); - let placeholders = PLACEHOLDERS.get_or_init(|| { - AhoCorasick::new(["{{", "}}", "{}", "{/}", "{//}", "{.}", "{/.}"]).unwrap() - }); - while let Some(m) = placeholders.find(remaining) { - match m.pattern().as_u32() { - 0 | 1 => { - // we found an escaped {{ or }}, so add - // everything up to the first char to the buffer - // then skip the second one. - buf += &remaining[..m.start() + BRACE_LEN]; - remaining = &remaining[m.end()..]; - } - id if !remaining[m.end()..].starts_with('}') => { - buf += &remaining[..m.start()]; - if !buf.is_empty() { - tokens.push(Token::Text(std::mem::take(&mut buf))); - } - tokens.push(token_from_pattern_id(id)); - remaining = &remaining[m.end()..]; - } - _ => { - // We got a normal pattern, but the final "}" - // is escaped, so add up to that to the buffer, then - // skip the final } - buf += &remaining[..m.end()]; - remaining = &remaining[m.end() + BRACE_LEN..]; - } - } - } - // Add the rest of the string to the buffer, and add the final buffer to the tokens - if !remaining.is_empty() { - buf += remaining; - } - if tokens.is_empty() { - // No placeholders were found, so just return the text - return ArgumentTemplate::Text(buf); - } - // Add final text segment - if !buf.is_empty() { - tokens.push(Token::Text(buf)); - } - debug_assert!(!tokens.is_empty()); - ArgumentTemplate::Tokens(tokens) -} - -fn token_from_pattern_id(id: u32) -> Token { - use Token::*; - match id { - 2 => Placeholder, - 3 => Basename, - 4 => Parent, - 5 => NoExt, - 6 => BasenameNoExt, - _ => unreachable!(), - } -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/fmt/input.rs new/fd-10.1.0/src/fmt/input.rs --- old/fd-10.0.0/src/fmt/input.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/fd-10.1.0/src/fmt/input.rs 2024-05-08 08:40:02.000000000 +0200 @@ -0,0 +1,87 @@ +use std::ffi::{OsStr, OsString}; +use std::path::{Path, PathBuf}; + +use crate::filesystem::strip_current_dir; + +/// Removes the parent component of the path +pub fn basename(path: &Path) -> &OsStr { + path.file_name().unwrap_or(path.as_os_str()) +} + +/// Removes the extension from the path +pub fn remove_extension(path: &Path) -> OsString { + let dirname = dirname(path); + let stem = path.file_stem().unwrap_or(path.as_os_str()); + + let path = PathBuf::from(dirname).join(stem); + + strip_current_dir(&path).to_owned().into_os_string() +} + +/// Removes the basename from the path. +pub fn dirname(path: &Path) -> OsString { + path.parent() + .map(|p| { + if p == OsStr::new("") { + OsString::from(".") + } else { + p.as_os_str().to_owned() + } + }) + .unwrap_or_else(|| path.as_os_str().to_owned()) +} + +#[cfg(test)] +mod path_tests { + use super::*; + use std::path::MAIN_SEPARATOR_STR; + + fn correct(input: &str) -> String { + input.replace('/', MAIN_SEPARATOR_STR) + } + + macro_rules! func_tests { + ($($name:ident: $func:ident for $input:expr => $output:expr)+) => { + $( + #[test] + fn $name() { + let input_path = PathBuf::from(&correct($input)); + let output_string = OsString::from(correct($output)); + assert_eq!($func(&input_path), output_string); + } + )+ + } + } + + func_tests! { + remove_ext_simple: remove_extension for "foo.txt" => "foo" + remove_ext_dir: remove_extension for "dir/foo.txt" => "dir/foo" + hidden: remove_extension for ".foo" => ".foo" + remove_ext_utf8: remove_extension for "ð.txt" => "ð" + remove_ext_empty: remove_extension for "" => "" + + basename_simple: basename for "foo.txt" => "foo.txt" + basename_dir: basename for "dir/foo.txt" => "foo.txt" + basename_empty: basename for "" => "" + basename_utf8_0: basename for "ð/foo.txt" => "foo.txt" + basename_utf8_1: basename for "dir/ð.txt" => "ð.txt" + + dirname_simple: dirname for "foo.txt" => "." + dirname_dir: dirname for "dir/foo.txt" => "dir" + dirname_utf8_0: dirname for "ð/foo.txt" => "ð" + dirname_utf8_1: dirname for "dir/ð.txt" => "dir" + } + + #[test] + #[cfg(windows)] + fn dirname_root() { + assert_eq!(dirname(&PathBuf::from("C:")), OsString::from("C:")); + assert_eq!(dirname(&PathBuf::from("\\")), OsString::from("\\")); + } + + #[test] + #[cfg(not(windows))] + fn dirname_root() { + assert_eq!(dirname(&PathBuf::from("/")), OsString::from("/")); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/fmt/mod.rs new/fd-10.1.0/src/fmt/mod.rs --- old/fd-10.0.0/src/fmt/mod.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/fd-10.1.0/src/fmt/mod.rs 2024-05-08 08:40:02.000000000 +0200 @@ -0,0 +1,281 @@ +mod input; + +use std::borrow::Cow; +use std::ffi::{OsStr, OsString}; +use std::fmt::{self, Display, Formatter}; +use std::path::{Component, Path, Prefix}; +use std::sync::OnceLock; + +use aho_corasick::AhoCorasick; + +use self::input::{basename, dirname, remove_extension}; + +/// Designates what should be written to a buffer +/// +/// Each `Token` contains either text, or a placeholder variant, which will be used to generate +/// commands after all tokens for a given command template have been collected. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Token { + Placeholder, + Basename, + Parent, + NoExt, + BasenameNoExt, + Text(String), +} + +impl Display for Token { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + Token::Placeholder => f.write_str("{}")?, + Token::Basename => f.write_str("{/}")?, + Token::Parent => f.write_str("{//}")?, + Token::NoExt => f.write_str("{.}")?, + Token::BasenameNoExt => f.write_str("{/.}")?, + Token::Text(ref string) => f.write_str(string)?, + } + Ok(()) + } +} + +/// A parsed format string +/// +/// This is either a collection of `Token`s including at least one placeholder variant, +/// or a fixed text. +#[derive(Clone, Debug, PartialEq)] +pub enum FormatTemplate { + Tokens(Vec<Token>), + Text(String), +} + +static PLACEHOLDERS: OnceLock<AhoCorasick> = OnceLock::new(); + +impl FormatTemplate { + pub fn has_tokens(&self) -> bool { + matches!(self, FormatTemplate::Tokens(_)) + } + + pub fn parse(fmt: &str) -> Self { + // NOTE: we assume that { and } have the same length + const BRACE_LEN: usize = '{'.len_utf8(); + let mut tokens = Vec::new(); + let mut remaining = fmt; + let mut buf = String::new(); + let placeholders = PLACEHOLDERS.get_or_init(|| { + AhoCorasick::new(["{{", "}}", "{}", "{/}", "{//}", "{.}", "{/.}"]).unwrap() + }); + while let Some(m) = placeholders.find(remaining) { + match m.pattern().as_u32() { + 0 | 1 => { + // we found an escaped {{ or }}, so add + // everything up to the first char to the buffer + // then skip the second one. + buf += &remaining[..m.start() + BRACE_LEN]; + remaining = &remaining[m.end()..]; + } + id if !remaining[m.end()..].starts_with('}') => { + buf += &remaining[..m.start()]; + if !buf.is_empty() { + tokens.push(Token::Text(std::mem::take(&mut buf))); + } + tokens.push(token_from_pattern_id(id)); + remaining = &remaining[m.end()..]; + } + _ => { + // We got a normal pattern, but the final "}" + // is escaped, so add up to that to the buffer, then + // skip the final } + buf += &remaining[..m.end()]; + remaining = &remaining[m.end() + BRACE_LEN..]; + } + } + } + // Add the rest of the string to the buffer, and add the final buffer to the tokens + if !remaining.is_empty() { + buf += remaining; + } + if tokens.is_empty() { + // No placeholders were found, so just return the text + return FormatTemplate::Text(buf); + } + // Add final text segment + if !buf.is_empty() { + tokens.push(Token::Text(buf)); + } + debug_assert!(!tokens.is_empty()); + FormatTemplate::Tokens(tokens) + } + + /// Generate a result string from this template. If path_separator is Some, then it will replace + /// the path separator in all placeholder tokens. Fixed text and tokens are not affected by + /// path separator substitution. + pub fn generate(&self, path: impl AsRef<Path>, path_separator: Option<&str>) -> OsString { + use Token::*; + let path = path.as_ref(); + + match *self { + Self::Tokens(ref tokens) => { + let mut s = OsString::new(); + for token in tokens { + match token { + Basename => s.push(Self::replace_separator(basename(path), path_separator)), + BasenameNoExt => s.push(Self::replace_separator( + &remove_extension(basename(path).as_ref()), + path_separator, + )), + NoExt => s.push(Self::replace_separator( + &remove_extension(path), + path_separator, + )), + Parent => s.push(Self::replace_separator(&dirname(path), path_separator)), + Placeholder => { + s.push(Self::replace_separator(path.as_ref(), path_separator)) + } + Text(ref string) => s.push(string), + } + } + s + } + Self::Text(ref text) => OsString::from(text), + } + } + + /// Replace the path separator in the input with the custom separator string. If path_separator + /// is None, simply return a borrowed Cow<OsStr> of the input. Otherwise, the input is + /// interpreted as a Path and its components are iterated through and re-joined into a new + /// OsString. + fn replace_separator<'a>(path: &'a OsStr, path_separator: Option<&str>) -> Cow<'a, OsStr> { + // fast-path - no replacement necessary + if path_separator.is_none() { + return Cow::Borrowed(path); + } + + let path_separator = path_separator.unwrap(); + let mut out = OsString::with_capacity(path.len()); + let mut components = Path::new(path).components().peekable(); + + while let Some(comp) = components.next() { + match comp { + // Absolute paths on Windows are tricky. A Prefix component is usually a drive + // letter or UNC path, and is usually followed by RootDir. There are also + // "verbatim" prefixes beginning with "\\?\" that skip normalization. We choose to + // ignore verbatim path prefixes here because they're very rare, might be + // impossible to reach here, and there's no good way to deal with them. If users + // are doing something advanced involving verbatim windows paths, they can do their + // own output filtering with a tool like sed. + Component::Prefix(prefix) => { + if let Prefix::UNC(server, share) = prefix.kind() { + // Prefix::UNC is a parsed version of '\\server\share' + out.push(path_separator); + out.push(path_separator); + out.push(server); + out.push(path_separator); + out.push(share); + } else { + // All other Windows prefix types are rendered as-is. This results in e.g. "C:" for + // drive letters. DeviceNS and Verbatim* prefixes won't have backslashes converted, + // but they're not returned by directories fd can search anyway so we don't worry + // about them. + out.push(comp.as_os_str()); + } + } + + // Root directory is always replaced with the custom separator. + Component::RootDir => out.push(path_separator), + + // Everything else is joined normally, with a trailing separator if we're not last + _ => { + out.push(comp.as_os_str()); + if components.peek().is_some() { + out.push(path_separator); + } + } + } + } + Cow::Owned(out) + } +} + +// Convert the id from an aho-corasick match to the +// appropriate token +fn token_from_pattern_id(id: u32) -> Token { + use Token::*; + match id { + 2 => Placeholder, + 3 => Basename, + 4 => Parent, + 5 => NoExt, + 6 => BasenameNoExt, + _ => unreachable!(), + } +} + +#[cfg(test)] +mod fmt_tests { + use super::*; + use std::path::PathBuf; + + #[test] + fn parse_no_placeholders() { + let templ = FormatTemplate::parse("This string has no placeholders"); + assert_eq!( + templ, + FormatTemplate::Text("This string has no placeholders".into()) + ); + } + + #[test] + fn parse_only_brace_escapes() { + let templ = FormatTemplate::parse("This string only has escapes like {{ and }}"); + assert_eq!( + templ, + FormatTemplate::Text("This string only has escapes like { and }".into()) + ); + } + + #[test] + fn all_placeholders() { + use Token::*; + + let templ = FormatTemplate::parse( + "{{path={} \ + basename={/} \ + parent={//} \ + noExt={.} \ + basenameNoExt={/.} \ + }}", + ); + assert_eq!( + templ, + FormatTemplate::Tokens(vec![ + Text("{path=".into()), + Placeholder, + Text(" basename=".into()), + Basename, + Text(" parent=".into()), + Parent, + Text(" noExt=".into()), + NoExt, + Text(" basenameNoExt=".into()), + BasenameNoExt, + Text(" }".into()), + ]) + ); + + let mut path = PathBuf::new(); + path.push("a"); + path.push("folder"); + path.push("file.txt"); + + let expanded = templ.generate(&path, Some("/")).into_string().unwrap(); + + assert_eq!( + expanded, + "{path=a/folder/file.txt \ + basename=file.txt \ + parent=a/folder \ + noExt=a/folder/file \ + basenameNoExt=file }" + ); + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/main.rs new/fd-10.1.0/src/main.rs --- old/fd-10.0.0/src/main.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/main.rs 2024-05-08 08:40:02.000000000 +0200 @@ -7,6 +7,7 @@ mod filesystem; mod filetypes; mod filter; +mod fmt; mod output; mod regex_helper; mod walk; @@ -299,6 +300,10 @@ .build() }) .transpose()?, + format: opts + .format + .as_deref() + .map(crate::fmt::FormatTemplate::parse), command: command.map(Arc::new), batch_size: opts.batch_size, exclude_patterns: opts.exclude.iter().map(|p| String::from("!") + p).collect(), @@ -311,8 +316,7 @@ path_separator, actual_path_separator, max_results: opts.max_results(), - strip_cwd_prefix: (opts.no_search_paths() - && (opts.strip_cwd_prefix || !(opts.null_separator || has_command))), + strip_cwd_prefix: opts.strip_cwd_prefix(|| !(opts.null_separator || has_command)), }) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/src/output.rs new/fd-10.1.0/src/output.rs --- old/fd-10.0.0/src/output.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/src/output.rs 2024-05-08 08:40:02.000000000 +0200 @@ -7,6 +7,7 @@ use crate::dir_entry::DirEntry; use crate::error::print_error; use crate::exit_codes::ExitCode; +use crate::fmt::FormatTemplate; fn replace_path_separator(path: &str, new_path_separator: &str) -> String { path.replace(std::path::MAIN_SEPARATOR, new_path_separator) @@ -14,7 +15,10 @@ // TODO: this function is performance critical and can probably be optimized pub fn print_entry<W: Write>(stdout: &mut W, entry: &DirEntry, config: &Config) { - let r = if let Some(ref ls_colors) = config.ls_colors { + // TODO: use format if supplied + let r = if let Some(ref format) = config.format { + print_entry_format(stdout, entry, config, format) + } else if let Some(ref ls_colors) = config.ls_colors { print_entry_colorized(stdout, entry, config, ls_colors) } else { print_entry_uncolorized(stdout, entry, config) @@ -55,6 +59,22 @@ } // TODO: this function is performance critical and can probably be optimized +fn print_entry_format<W: Write>( + stdout: &mut W, + entry: &DirEntry, + config: &Config, + format: &FormatTemplate, +) -> io::Result<()> { + let separator = if config.null_separator { "\0" } else { "\n" }; + let output = format.generate( + entry.stripped_path(config), + config.path_separator.as_deref(), + ); + // TODO: support writing raw bytes on unix? + write!(stdout, "{}{}", output.to_string_lossy(), separator) +} + +// TODO: this function is performance critical and can probably be optimized fn print_entry_colorized<W: Write>( stdout: &mut W, entry: &DirEntry, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fd-10.0.0/tests/tests.rs new/fd-10.1.0/tests/tests.rs --- old/fd-10.0.0/tests/tests.rs 2024-05-06 08:01:31.000000000 +0200 +++ new/fd-10.1.0/tests/tests.rs 2024-05-08 08:40:02.000000000 +0200 @@ -1624,6 +1624,66 @@ ); } +#[test] +fn format() { + let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); + + te.assert_output( + &["--format", "path={}", "--path-separator=/"], + "path=a.foo + path=e1 e2 + path=one + path=one/b.foo + path=one/two + path=one/two/C.Foo2 + path=one/two/c.foo + path=one/two/three + path=one/two/three/d.foo + path=one/two/three/directory_foo + path=symlink", + ); + + te.assert_output( + &["foo", "--format", "noExt={.}", "--path-separator=/"], + "noExt=a + noExt=one/b + noExt=one/two/C + noExt=one/two/c + noExt=one/two/three/d + noExt=one/two/three/directory_foo", + ); + + te.assert_output( + &["foo", "--format", "basename={/}", "--path-separator=/"], + "basename=a.foo + basename=b.foo + basename=C.Foo2 + basename=c.foo + basename=d.foo + basename=directory_foo", + ); + + te.assert_output( + &["foo", "--format", "name={/.}", "--path-separator=/"], + "name=a + name=b + name=C + name=c + name=d + name=directory_foo", + ); + + te.assert_output( + &["foo", "--format", "parent={//}", "--path-separator=/"], + "parent=. + parent=one + parent=one/two + parent=one/two + parent=one/two/three + parent=one/two/three", + ); +} + /// Shell script execution (--exec) #[test] fn test_exec() { ++++++ fd.obsinfo ++++++ --- /var/tmp/diff_new_pack.fFL0lc/_old 2024-05-09 17:28:59.680126628 +0200 +++ /var/tmp/diff_new_pack.fFL0lc/_new 2024-05-09 17:28:59.684126774 +0200 @@ -1,5 +1,5 @@ name: fd -version: 10.0.0 -mtime: 1714975291 -commit: 8acd7722f09ff45ef51335751160e0a8dcc096dc +version: 10.1.0 +mtime: 1715150402 +commit: 289a68bac3938d56b176d5b15fab312fd538e949 ++++++ rust.patch ++++++ --- /var/tmp/diff_new_pack.fFL0lc/_old 2024-05-09 17:28:59.700127358 +0200 +++ /var/tmp/diff_new_pack.fFL0lc/_new 2024-05-09 17:28:59.704127504 +0200 @@ -1,9 +1,8 @@ -diff -urN fd-10.0.0-A/Cargo.toml fd-10.0.0-B/Cargo.toml ---- fd-10.0.0-A/Cargo.toml 2024-05-08 07:34:07.191560537 +1000 -+++ fd-10.0.0-B/Cargo.toml 2024-05-06 16:01:31.000000000 +1000 +--- fd-10.1.0/Cargo.toml 2024-05-08 08:40:02.000000000 +0200 ++++ fd-10.1.0/Cargo.toml 2024-05-09 08:45:17.441968003 +0200 @@ -18,7 +18,7 @@ repository = "https://github.com/sharkdp/fd" - version = "10.0.0" + version = "10.1.0" edition= "2021" -rust-version = "1.77.2" +rust-version = "1.77.0" ++++++ vendor.tar.xz ++++++ /work/SRC/openSUSE:Factory/fd/vendor.tar.xz /work/SRC/openSUSE:Factory/.fd.new.1880/vendor.tar.xz differ: char 15, line 1
