Skip to content

Commit 578e199

Browse files
Anthony HuangBurntSushi
authored andcommitted
cli: add --field-{context,match}-separator flags
These flags permit configuring the bytes used to delimit fields in match or context lines, where "fields" are things like the file path, line number, column number and the match/context itself. Fixes BurntSushi#1842, Closes BurntSushi#1871
1 parent 46d0130 commit 578e199

5 files changed

Lines changed: 151 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Feature enhancements:
4343
Cargo will now produce static executables on Windows when using MSVC.
4444
* [FEATURE #1680](https://github.com/BurntSushi/ripgrep/pull/1680):
4545
Add `-.` as a short flag alias for `--hidden`.
46+
* [FEATURE #1842](https://github.com/BurntSushi/ripgrep/issues/1842):
47+
Add `--field-{context,match}-separator` for customizing field delimiters.
4648
* [FEATURE #1856](https://github.com/BurntSushi/ripgrep/pull/1856):
4749
The README now links to a
4850
[Spanish translation](https://github.com/UltiRequiem/traducciones/tree/master/ripgrep).

‎complete/_rg‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ _rg() {
303303
'--context-separator=[specify string used to separate non-continuous context lines in output]:separator'
304304
$no"--no-context-separator[don't print context separators]"
305305
'--debug[show debug messages]'
306+
'--field-context-separator[set string to delimit fields in context lines]'
307+
'--field-match-separator[set string to delimit fields in matching lines]'
306308
'--trace[show more verbose debug messages]'
307309
'--dfa-size-limit=[specify upper size limit of generated DFA]:DFA size (bytes)'
308310
"(1 stats)--files[show each file that would be searched (but don't search)]"

‎crates/core/app.rs‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ pub fn all_args_and_flags() -> Vec<RGArg> {
568568
flag_dfa_size_limit(&mut args);
569569
flag_encoding(&mut args);
570570
flag_engine(&mut args);
571+
flag_field_context_separator(&mut args);
572+
flag_field_match_separator(&mut args);
571573
flag_file(&mut args);
572574
flag_files(&mut args);
573575
flag_files_with_matches(&mut args);
@@ -1231,6 +1233,38 @@ This overrides previous uses of --pcre2 and --auto-hybrid-regex flags.
12311233
args.push(arg);
12321234
}
12331235

1236+
fn flag_field_context_separator(args: &mut Vec<RGArg>) {
1237+
const SHORT: &str = "Set the field context separator.";
1238+
const LONG: &str = long!(
1239+
"\
1240+
Set the field context separator, which is used to delimit file paths, line
1241+
numbers, columns and the context itself, when printing contextual lines. The
1242+
separator may be any number of bytes, including zero. Escape sequences like
1243+
\\x7F or \\t may be used. The default value is -.
1244+
"
1245+
);
1246+
let arg = RGArg::flag("field-context-separator", "SEPARATOR")
1247+
.help(SHORT)
1248+
.long_help(LONG);
1249+
args.push(arg);
1250+
}
1251+
1252+
fn flag_field_match_separator(args: &mut Vec<RGArg>) {
1253+
const SHORT: &str = "Set the match separator.";
1254+
const LONG: &str = long!(
1255+
"\
1256+
Set the field match separator, which is used to delimit file paths, line
1257+
numbers, columns and the match itself. The separator may be any number of
1258+
bytes, including zero. Escape sequences like \\x7F or \\t may be used. The
1259+
default value is -.
1260+
"
1261+
);
1262+
let arg = RGArg::flag("field-match-separator", "SEPARATOR")
1263+
.help(SHORT)
1264+
.long_help(LONG);
1265+
args.push(arg);
1266+
}
1267+
12341268
fn flag_file(args: &mut Vec<RGArg>) {
12351269
const SHORT: &str = "Search for patterns from the given file.";
12361270
const LONG: &str = long!(

‎crates/core/args.rs‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,8 +786,8 @@ impl ArgMatches {
786786
.trim_ascii(self.is_present("trim"))
787787
.separator_search(None)
788788
.separator_context(self.context_separator())
789-
.separator_field_match(b":".to_vec())
790-
.separator_field_context(b"-".to_vec())
789+
.separator_field_match(self.field_match_separator())
790+
.separator_field_context(self.field_context_separator())
791791
.separator_path(self.path_separator()?)
792792
.path_terminator(self.path_terminator());
793793
if separator_search {
@@ -1377,6 +1377,24 @@ impl ArgMatches {
13771377
}
13781378
}
13791379

1380+
/// Returns the unescaped field context separator. If one wasn't specified,
1381+
/// then '-' is used as the default.
1382+
fn field_context_separator(&self) -> Vec<u8> {
1383+
match self.value_of_os("field-context-separator") {
1384+
None => b"-".to_vec(),
1385+
Some(sep) => cli::unescape_os(&sep),
1386+
}
1387+
}
1388+
1389+
/// Returns the unescaped field match separator. If one wasn't specified,
1390+
/// then ':' is used as the default.
1391+
fn field_match_separator(&self) -> Vec<u8> {
1392+
match self.value_of_os("field-match-separator") {
1393+
None => b":".to_vec(),
1394+
Some(sep) => cli::unescape_os(&sep),
1395+
}
1396+
}
1397+
13801398
/// Get a sequence of all available patterns from the command line.
13811399
/// This includes reading the -e/--regexp and -f/--file flags.
13821400
///

‎tests/feature.rs‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,99 @@ rgtest!(f1404_nothing_searched_ignored, |dir: Dir, mut cmd: TestCommand| {
828828
eqnice!(expected, stderr);
829829
});
830830

831+
// See: https://github.com/BurntSushi/ripgrep/issues/1842
832+
rgtest!(f1842_field_context_separator, |dir: Dir, _: TestCommand| {
833+
dir.create("sherlock", SHERLOCK);
834+
835+
// Test the default.
836+
let base = &["-n", "-A1", "Doctor Watsons", "sherlock"];
837+
let expected = "\
838+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
839+
2-Holmeses, success in the province of detective work must always
840+
";
841+
eqnice!(expected, dir.command().args(base).stdout());
842+
843+
// Test that it can be overridden.
844+
let mut args = vec!["--field-context-separator", "!"];
845+
args.extend(base);
846+
let expected = "\
847+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
848+
2!Holmeses, success in the province of detective work must always
849+
";
850+
eqnice!(expected, dir.command().args(&args).stdout());
851+
852+
// Test that it can use multiple bytes.
853+
let mut args = vec!["--field-context-separator", "!!"];
854+
args.extend(base);
855+
let expected = "\
856+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
857+
2!!Holmeses, success in the province of detective work must always
858+
";
859+
eqnice!(expected, dir.command().args(&args).stdout());
860+
861+
// Test that unescaping works.
862+
let mut args = vec!["--field-context-separator", r"\x7F"];
863+
args.extend(base);
864+
let expected = "\
865+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
866+
2\x7FHolmeses, success in the province of detective work must always
867+
";
868+
eqnice!(expected, dir.command().args(&args).stdout());
869+
870+
// Test that an empty separator is OK.
871+
let mut args = vec!["--field-context-separator", r""];
872+
args.extend(base);
873+
let expected = "\
874+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
875+
2Holmeses, success in the province of detective work must always
876+
";
877+
eqnice!(expected, dir.command().args(&args).stdout());
878+
});
879+
880+
// See: https://github.com/BurntSushi/ripgrep/issues/1842
881+
rgtest!(f1842_field_match_separator, |dir: Dir, _: TestCommand| {
882+
dir.create("sherlock", SHERLOCK);
883+
884+
// Test the default.
885+
let base = &["-n", "Doctor Watsons", "sherlock"];
886+
let expected = "\
887+
1:For the Doctor Watsons of this world, as opposed to the Sherlock
888+
";
889+
eqnice!(expected, dir.command().args(base).stdout());
890+
891+
// Test that it can be overridden.
892+
let mut args = vec!["--field-match-separator", "!"];
893+
args.extend(base);
894+
let expected = "\
895+
1!For the Doctor Watsons of this world, as opposed to the Sherlock
896+
";
897+
eqnice!(expected, dir.command().args(&args).stdout());
898+
899+
// Test that it can use multiple bytes.
900+
let mut args = vec!["--field-match-separator", "!!"];
901+
args.extend(base);
902+
let expected = "\
903+
1!!For the Doctor Watsons of this world, as opposed to the Sherlock
904+
";
905+
eqnice!(expected, dir.command().args(&args).stdout());
906+
907+
// Test that unescaping works.
908+
let mut args = vec!["--field-match-separator", r"\x7F"];
909+
args.extend(base);
910+
let expected = "\
911+
1\x7FFor the Doctor Watsons of this world, as opposed to the Sherlock
912+
";
913+
eqnice!(expected, dir.command().args(&args).stdout());
914+
915+
// Test that an empty separator is OK.
916+
let mut args = vec!["--field-match-separator", r""];
917+
args.extend(base);
918+
let expected = "\
919+
1For the Doctor Watsons of this world, as opposed to the Sherlock
920+
";
921+
eqnice!(expected, dir.command().args(&args).stdout());
922+
});
923+
831924
rgtest!(no_context_sep, |dir: Dir, mut cmd: TestCommand| {
832925
dir.create("test", "foo\nctx\nbar\nctx\nfoo\nctx");
833926
cmd.args(&["-A1", "--no-context-separator", "foo", "test"]);

0 commit comments

Comments
 (0)