sharkdp/fd
Format templates
Active contributors: tmccombs, sharkdp
Purpose
A single, small template engine that handles both --exec/--exec-batch placeholders and the --format output template. It parses a template string into a sequence of tokens (literal text and placeholder kinds), then expands each token against a path, optionally rewriting path separators along the way.
Directory layout
src/
└── fmt/
├── mod.rs # Token, FormatTemplate, parser, generator, replace_separator
└── input.rs # basename, dirname, remove_extension helpers used by generatorKey abstractions
| Type | File | Purpose |
|---|---|---|
Token |
src/fmt/mod.rs |
Placeholder, Basename, Parent, NoExt, BasenameNoExt, or Text(String). |
FormatTemplate |
src/fmt/mod.rs |
Either Tokens(Vec<Token>) (template with at least one placeholder) or Text(String) (literal). |
replace_separator |
src/fmt/mod.rs |
Replaces MAIN_SEPARATOR with the user-chosen --path-separator, with special handling for Windows UNC and drive prefixes. |
basename, dirname, remove_extension |
src/fmt/input.rs |
Path helpers that match the documented placeholder semantics. |
Placeholders
| Token | Literal | Meaning | Example for documents/images/party.jpg |
|---|---|---|---|
Placeholder |
{} |
Full path | documents/images/party.jpg |
Basename |
{/} |
File name | party.jpg |
Parent |
{//} |
Parent directory | documents/images |
NoExt |
{.} |
Path without final extension | documents/images/party |
BasenameNoExt |
{/.} |
Basename without final extension | party |
Literal { |
{{ |
An actual { character |
— |
Literal } |
}} |
An actual } character |
— |
Parsing
FormatTemplate::parse uses an aho_corasick::AhoCorasick matcher over the seven literal patterns ["{{", "}}", "{}", "{/}", "{//}", "{.}", "{/.}"]. The match handler:
- On
{{or}}, copy the text up to the first brace into the buffer, then skip the second brace. This implements the escape rules. - On a placeholder pattern, push any pending text as
Token::Text, then push the correspondingTokenvariant. - On a placeholder pattern immediately followed by
}(e.g.{}}), treat the trailing}as escaped and add it to the buffer. This is what allows the test case"{{{},end}"→"{foo,end}".
If no placeholder pattern matched, the template collapses to FormatTemplate::Text(buf). Otherwise, every leftover buffer chunk becomes a final Token::Text.
The fixed AhoCorasick is built once and stored in a static OnceLock so subsequent parses are allocation-light.
Generation
FormatTemplate::generate(path, path_separator) walks the token list and pushes the right thing into a fresh OsString:
Placeholder→ the full path, possibly with separators rewritten.Basename→basename(path)(which ispath.file_name()falling back to the path itself if there is no name component).Parent→dirname(path)(path.parent()with empty parent normalised to".", falling back to the path'sOsStrif the parent isNone).NoExt→remove_extension(path)(dirnamejoined withpath.file_stem(), thenstrip_current_dir).BasenameNoExt→remove_extension(basename(path)).Text(s)→ just appended verbatim. Path separator substitution does not apply to literal text in the template.
FormatTemplate::Text(text) is generated with no per-path computation.
Path separator substitution
replace_separator is the trickiest part. It is invoked on placeholder substitutions only, never on literal text. The implementation iterates over Path::components() and:
- For Windows
Component::Prefix(UNC(server, share)), emits{sep}{sep}server{sep}share. - For other Windows prefixes (drive letters, etc.), emits the prefix as-is — drive letters like
C:are preserved. - For
Component::RootDir, emits the custom separator. - For all other components, emits the component bytes followed by the separator if there is another component coming.
Forward slashes on Windows are normalised by Path::components() itself, so "C:/foo/bar" becomes the same components as "C:\\foo\\bar". Verbatim prefixes (\\?\…) are intentionally left as-is on the basis that anyone using them is already past the point of caring about pretty separators.
Where it is used
--format.Config::formatis set when--formatis on.output::print_entry_formatcallsformat.generate(stripped_path, path_separator)and writes the resulting string.FormatTemplate::Textshort-circuits to a no-opOsStringcontaining just the literal template.--exec/--exec-batch. Each argv element is aFormatTemplate.CommandTemplate::generate(insrc/exec/mod.rs) walks them and feeds each intoargmax::Command::try_arg.CommandTemplate::newautomatically appends a finalToken::Placeholderif the user template contains no placeholder, which is what makesfd -e zip -x unzipwork without writingunzip {}explicitly.
Helpers in src/fmt/input.rs
pub fn basename(path: &Path) -> &OsStr {
path.file_name().unwrap_or(path.as_os_str())
}
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())
}
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()
}strip_current_dir (src/filesystem.rs) trims a leading ./, so remove_extension(Path::new("foo.txt")) returns "foo", not "./foo". The unit-test table in src/fmt/input.rs covers UTF-8 filenames ("💖.txt" → "💖"), nested dirs, hidden files (.foo keeps its dot because it has no extension to strip), and the empty path.
Entry points for modification
- Add a new placeholder. Add a
Tokenvariant, extend the aho-corasick pattern list, updateDisplay, and add a match arm ingenerate. Add a unit test insrc/fmt/mod.rs. - Change separator handling for a new prefix kind. Edit
replace_separator'sComponent::Prefixarm. - Change basename/dirname semantics. Edit
src/fmt/input.rsand update the unit-test table.
Related pages
- Command execution — the larger consumer of this engine.
- Output — the
--formatoutput path.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.