Skip to content

Commit 6ae16c6

Browse files
committed
fix: expand format placeholder followed by a literal '}'
A placeholder immediately followed by a literal '}' (e.g. '{}}' or '{.}}') was treated as fixed text instead of being expanded. The parser detected the placeholder pattern but, when the next character was '}', fell through to a branch that copied the whole sequence verbatim and dropped the trailing '}'. Remove that branch so the placeholder is always emitted and the following '}' is handled as ordinary literal text. This affects both --format and --exec, which share the template parser.
1 parent 5a5852e commit 6ae16c6

4 files changed

Lines changed: 42 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Fire the "search pattern contains a path separator" diagnostic for any pattern containing `/`, not just patterns that happen to name an existing directory. Preserves the legacy Windows behaviour that also flags native `\` separators when the pattern resolves to a real directory. See #1873.
1414
- Also fire the "search pattern contains a path separator" diagnostic for `--and` patterns, not only the primary positional pattern. `--and` patterns are matched against the file name just like the primary pattern, so a path separator in them silently returned zero results. See #1873.
1515
- Fix bug where passing "-" as a directory argument didn't actually search that directory, see #849 (@Sean-Kenneth-Doherty).
16+
- Fix `--format`/`--exec` placeholders that are immediately followed by a literal `}` (e.g. `{}}` or `{.}}`). The placeholder is now expanded with the `}` kept as trailing literal text, instead of the whole sequence being treated as fixed text.
1617

1718
# 10.4.2
1819

src/exec/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,9 @@ mod tests {
369369
fn tokens_with_literal_braces() {
370370
let template =
371371
CommandTemplate::new(vec!["{{}}", "{{", "{.}}"], ExecutionMode::OneByOne).unwrap();
372-
assert_eq!(
373-
generate_str(&template, "foo"),
374-
vec!["{}", "{", "{.}", "foo"]
375-
);
372+
// `{{}}` and `{{` are literal-brace escapes, while `{.}}` expands the
373+
// `{.}` placeholder and keeps the trailing `}` as a literal.
374+
assert_eq!(generate_str(&template, "foo"), vec!["{}", "{", "foo}"]);
376375
}
377376

378377
#[test]

src/fmt/mod.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,19 @@ impl FormatTemplate {
7373
buf += &remaining[..m.start() + BRACE_LEN];
7474
remaining = &remaining[m.end()..];
7575
}
76-
id if !remaining[m.end()..].starts_with('}') => {
76+
id => {
77+
// We found a placeholder. Add any preceding text to the
78+
// buffer, flush it, then push the placeholder token. A
79+
// literal '}' following the placeholder (as in `{}}`) is
80+
// left in `remaining` and handled as ordinary text on the
81+
// next iteration.
7782
buf += &remaining[..m.start()];
7883
if !buf.is_empty() {
7984
tokens.push(Token::Text(std::mem::take(&mut buf)));
8085
}
8186
tokens.push(token_from_pattern_id(id));
8287
remaining = &remaining[m.end()..];
8388
}
84-
_ => {
85-
// We got a normal pattern, but the final "}"
86-
// is escaped, so add up to that to the buffer, then
87-
// skip the final }
88-
buf += &remaining[..m.end()];
89-
remaining = &remaining[m.end() + BRACE_LEN..];
90-
}
9189
}
9290
}
9391
// Add the rest of the string to the buffer, and add the final buffer to the tokens
@@ -233,6 +231,31 @@ mod fmt_tests {
233231
);
234232
}
235233

234+
#[test]
235+
fn parse_placeholder_followed_by_literal_brace() {
236+
use Token::*;
237+
238+
// A placeholder immediately followed by a literal '}' should still be
239+
// expanded, with the '}' kept as trailing literal text.
240+
assert_eq!(
241+
FormatTemplate::parse("{}}"),
242+
FormatTemplate::Tokens(vec![Placeholder, Text("}".into())])
243+
);
244+
assert_eq!(
245+
FormatTemplate::parse("{/}}"),
246+
FormatTemplate::Tokens(vec![Basename, Text("}".into())])
247+
);
248+
249+
let mut path = PathBuf::new();
250+
path.push("a");
251+
path.push("file.txt");
252+
let expanded = FormatTemplate::parse("{}}")
253+
.generate(&path, Some("/"))
254+
.into_string()
255+
.unwrap();
256+
assert_eq!(expanded, "a/file.txt}");
257+
}
258+
236259
#[test]
237260
fn all_placeholders() {
238261
use Token::*;

tests/tests.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,6 +1755,13 @@ fn format() {
17551755
parent=one/two/three
17561756
parent=one/two/three",
17571757
);
1758+
1759+
// A placeholder immediately followed by a literal '}' is still expanded,
1760+
// with the '}' kept as trailing literal text (see issue with `{}}`).
1761+
te.assert_output(
1762+
&["^a\\.foo$", "--format", "{}}", "--path-separator=/"],
1763+
"a.foo}",
1764+
);
17581765
}
17591766

17601767
/// Shell script execution (--exec)

0 commit comments

Comments
 (0)