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

Reply via email to