Skip to content

Commit

Permalink
Add docs and tests for --format
Browse files Browse the repository at this point in the history
Also fix bug where we didn't strip leading cwd.
  • Loading branch information
tmccombs committed Jan 29, 2024
1 parent 985f2b1 commit b3a82e6
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 34 deletions.
63 changes: 30 additions & 33 deletions doc/fd.1
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,30 @@ Set the path separator to use when printing file paths. The default is the OS-sp
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
Expand All @@ -383,29 +407,12 @@ If parallelism is enabled, the order commands will be executed in is non-determi
--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:
Expand All @@ -429,19 +436,9 @@ once, with all search results as arguments.

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.

Expand Down
57 changes: 57 additions & 0 deletions src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,60 @@ fn token_from_pattern_id(id: u32) -> Token {
_ => 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 }");
}
}
2 changes: 1 addition & 1 deletion src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fn print_entry_format<W: Write>(
format: &FormatTemplate,
) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" };
let output = format.generate(entry.path(), config.path_separator.as_deref());
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)
}
Expand Down
60 changes: 60 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,66 @@ fn test_excludes() {
);
}

#[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() {
Expand Down

0 comments on commit b3a82e6

Please sign in to comment.