Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 70 additions & 5 deletions crates/bashkit/src/builtins/rg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! Usage:
//! rg PATTERN [PATH...]
//! rg -i PATTERN file # case insensitive
//! rg -n PATTERN file # show line numbers (default)
//! rg -n PATTERN file # show line numbers (off by default in non-tty)
//! rg -c PATTERN file # count matches
//! rg -l PATTERN file # files with matches
//! rg -v PATTERN file # invert match
Expand Down Expand Up @@ -46,7 +46,7 @@ impl RgOptions {
pattern: String::new(),
paths: Vec::new(),
ignore_case: false,
line_numbers: true, // rg shows line numbers by default
line_numbers: false, // non-tty: suppress line numbers (real rg behavior)
count_only: false,
files_with_matches: false,
invert_match: false,
Expand All @@ -68,6 +68,7 @@ impl RgOptions {
match chars[j] {
'i' => opts.ignore_case = true,
'n' => opts.line_numbers = true,
'N' => opts.line_numbers = false,
'c' => opts.count_only = true,
'l' => opts.files_with_matches = true,
'v' => opts.invert_match = true,
Expand All @@ -89,6 +90,8 @@ impl RgOptions {
// no-op
} else if opt == "no-line-number" {
opts.line_numbers = false;
} else if opt == "line-number" {
opts.line_numbers = true;
}
// ignore other long options
} else {
Expand Down Expand Up @@ -472,15 +475,77 @@ mod tests {
}

#[tokio::test]
async fn test_rg_line_numbers_default() {
async fn test_rg_no_line_numbers_default() {
// Non-tty: line numbers suppressed by default (like real rg)
let result = run_rg(
&["world", "/test.txt"],
None,
&[("/test.txt", b"hello\nworld\n")],
)
.await;
assert_eq!(result.exit_code, 0);
// Line numbers on by default, "world" is on line 2
assert!(result.stdout.contains("2:"));
assert_eq!(result.stdout.trim(), "world");
assert!(!result.stdout.contains("2:"));
}

#[tokio::test]
async fn test_rg_line_numbers_explicit() {
// -n flag enables line numbers
let result = run_rg(
&["-n", "world", "/test.txt"],
None,
&[("/test.txt", b"hello\nworld\n")],
)
.await;
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("2:world"));
}

#[tokio::test]
async fn test_rg_no_line_number_flag_short() {
// -N flag explicitly disables line numbers
let result = run_rg(
&["-N", "world", "/test.txt"],
None,
&[("/test.txt", b"hello\nworld\n")],
)
.await;
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "world");
}

#[tokio::test]
async fn test_rg_no_line_number_flag_long() {
// --no-line-number flag explicitly disables line numbers
let result = run_rg(
&["--no-line-number", "world", "/test.txt"],
None,
&[("/test.txt", b"hello\nworld\n")],
)
.await;
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "world");
}

#[tokio::test]
async fn test_rg_line_number_long_flag() {
// --line-number flag enables line numbers
let result = run_rg(
&["--line-number", "world", "/test.txt"],
None,
&[("/test.txt", b"hello\nworld\n")],
)
.await;
assert_eq!(result.exit_code, 0);
assert!(result.stdout.contains("2:world"));
}

#[tokio::test]
async fn test_rg_stdin_no_line_numbers() {
// Stdin piped: no line numbers by default
let result = run_rg(&["hello"], Some("hello world\n"), &[]).await;
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.trim(), "hello world");
assert!(!result.stdout.contains("1:"));
}
}
94 changes: 94 additions & 0 deletions crates/bashkit/tests/spec_cases/grep/rg.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
### rg_basic_match
# Basic rg pattern match
printf 'hello world\ngoodbye\nhello again\n' | rg hello
### expect
hello world
hello again
### end

### rg_no_line_numbers_default
# rg suppresses line numbers when piped (non-tty)
printf 'foo\nbar\nbaz\n' | rg bar
### expect
bar
### end

### rg_line_numbers_with_n
# -n flag enables line numbers
printf 'foo\nbar\nbaz\n' | rg -n bar
### expect
2:bar
### end

### rg_no_line_number_N_flag
# -N flag explicitly suppresses line numbers
printf 'foo\nbar\n' | rg -N bar
### expect
bar
### end

### rg_no_line_number_long
# --no-line-number long flag
printf 'foo\nbar\n' | rg --no-line-number bar
### expect
bar
### end

### rg_line_number_long
# --line-number long flag enables line numbers
printf 'foo\nbar\nbaz\n' | rg --line-number bar
### expect
2:bar
### end

### rg_no_match
# No match returns exit code 1
printf 'foo\nbar\n' | rg xyz
### exit_code: 1
### expect
### end

### rg_case_insensitive
# Case insensitive search
printf 'Hello\nWORLD\nhello\n' | rg -i hello
### expect
Hello
hello
### end

### rg_count
# Count matches
printf 'foo\nbar\nfoo again\n' | rg -c foo
### expect
2
### end

### rg_invert_match
# Invert match
printf 'foo\nbar\nbaz\n' | rg -v foo
### expect
bar
baz
### end

### rg_fixed_strings
# Fixed string (no regex)
printf 'a.b\naxb\n' | rg -F 'a.b'
### expect
a.b
### end

### rg_word_boundary
# Word boundary match
printf 'cat\ncatch\nmy cat\n' | rg -w cat
### expect
cat
my cat
### end

### rg_max_count
# Stop after N matches
printf 'foo\nfoo\nfoo\n' | rg -m 1 foo
### expect
foo
### end
Loading