2 Commits
Author SHA1 Message Date
Diomidis Spinellis 1b9978f911 Polish the handling of 0r
- Use common constant for error message.
- Simplify command check at compile and runtime.
- Allow for future R command.
- Separate runtime handling into a function.
2026-06-30 16:13:06 +03:00
PranavRJoshi a708e09a72 feat: support zero-address file insertion
Signed-off-by: PranavRJoshi <pranavrjoshi1@gmail.com>
2026-06-30 15:17:19 +03:00
5 changed files with 143 additions and 8 deletions

No files matched your search

+56 -6
View File
@@ -29,6 +29,9 @@ use uucore::error::{UResult, USimpleError};
const DEFAULT_OUTPUT_WIDTH: usize = 60;
const ERR_ADDRESS_0_USAGE: &str =
"address 0 can only be used with ~step, a second regular expression, or a read command";
// Handling required after processing a command
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CommandHandling {
@@ -380,11 +383,7 @@ fn compile_address_range(
};
if is_line0 && !matches!(addr2, Address::Re(_)) && !is_step_match {
return compilation_error(
lines,
line,
"address 0 can only be used with a regular expression or ~step",
);
return compilation_error(lines, line, ERR_ADDRESS_0_USAGE);
}
// If needed, transform Address::Line into Address::Step*.
@@ -399,8 +398,14 @@ fn compile_address_range(
}
}
// Zero-address read command check
if is_line0 && n_addr == 1 {
return compilation_error(lines, line, "address 0 requires a second address");
// After retrieval of first address, subsequent spaces
// are consumed unconditionally. By now, the position
// must be in non-whitespace character or EOL.
if line.eol() || !matches!(line.current(), 'r' | 'R') {
return compilation_error(lines, line, ERR_ADDRESS_0_USAGE);
}
}
Ok(n_addr)
@@ -1849,6 +1854,51 @@ mod tests {
ScriptCharProvider::new("")
}
#[test]
fn test_zero_addr_r_accepted() {
for input in ["0r", "0 r"] {
let (lines, mut chars) = make_providers(input);
let mut cmd = Rc::new(RefCell::new(Command::default()));
let n_addr = compile_address_range(&lines, &mut chars, &mut cmd, &ctx()).unwrap();
assert_eq!(n_addr, 1);
assert!(matches!(cmd.borrow().addr1, Some(Address::Line(0))));
assert_eq!(chars.current(), 'r');
}
}
// Zero-address with no commands
#[test]
fn test_zero_addr_no_commands() {
let (lines, mut chars) = make_providers("0");
let mut cmd = Rc::new(RefCell::new(Command::default()));
let result = compile_address_range(&lines, &mut chars, &mut cmd, &ctx());
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains(ERR_ADDRESS_0_USAGE)
);
}
// Zero-address with a command other than 'r' must still be rejected.
#[test]
fn test_zero_addr_non_r_rejected() {
let (lines, mut chars) = make_providers("0p");
let mut cmd = Rc::new(RefCell::new(Command::default()));
let result = compile_address_range(&lines, &mut chars, &mut cmd, &ctx());
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains(ERR_ADDRESS_0_USAGE)
);
}
#[test]
fn test_compile_sequence_empty_input() {
let mut provider = make_line_provider(&[]);
+31
View File
@@ -415,6 +415,35 @@ fn list(output: &mut OutputBuffer, line: &IOChunk, max_width: usize) -> UResult<
Ok(())
}
/// Handle address 0 read at the beginning of each file.
fn address_0_process(
commands: Option<Rc<RefCell<Command>>>,
output: &mut OutputBuffer,
) -> UResult<()> {
// Prescan for zero-address which must produce output
// before any input line is read.
{
let mut current = commands;
while let Some(cmd_rc) = current {
let next = {
let cmd = cmd_rc.borrow();
if cmd.code == 'r'
&& matches!(cmd.addr1, Some(Address::Line(0)))
&& cmd.addr2.is_none()
{
let path = extract_variant!(cmd, Path);
output.copy_file(path)?;
}
cmd.next.clone()
};
current = next;
}
}
Ok(())
}
#[allow(clippy::cognitive_complexity)]
/// Process a single input file
fn process_file(
@@ -423,6 +452,8 @@ fn process_file(
output: &mut OutputBuffer,
context: &mut ProcessingContext,
) -> UResult<()> {
address_0_process(commands.clone(), output)?;
// Loop over the input lines as pattern space.
'lines: while let Some(mut pattern) = reader.get_line()? {
context.line_number += 1;
+10 -2
View File
@@ -973,6 +973,14 @@ check_output!(
check_output!(read_ok, [format!("4r {LINES2}"), LINES1.to_string()]);
check_output!(read_missing, ["5r /xyzzyxyzy42", LINES1]);
check_output!(read_empty, ["6r input/empty", LINES1]);
check_output!(
cmd_read_zero_addr,
[format!("0r {LINES2}"), LINES1.to_string()]
);
check_output!(
cmd_read_one_addr,
[format!("1r {LINES2}"), LINES1.to_string()]
);
#[test]
fn write_single_file() -> std::io::Result<()> {
@@ -1339,7 +1347,7 @@ fn test_addr0_second_required() {
.args(&["0p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:2: error: address 0 requires a second address\n");
.stderr_is("sed: <script argument 1>:1:2: error: address 0 can only be used with ~step, a second regular expression, or a read command\n");
}
#[test]
@@ -1348,7 +1356,7 @@ fn test_addr0_second_re_only() {
.args(&["0,4p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:4: error: address 0 can only be used with a regular expression or ~step\n");
.stderr_is("sed: <script argument 1>:1:4: error: address 0 can only be used with ~step, a second regular expression, or a read command\n");
}
#[test]
+23
View File
@@ -0,0 +1,23 @@
l1_1
l2_1
l2_2
l2_3
l2_4
l2_5
l2_6
l2_7
l2_8
l2_9
l1_2
l1_3
l1_4
l1_5
l1_6
l1_7
l1_8
l1_9
l1_10
l1_11
l1_12
l1_13
l1_14
+23
View File
@@ -0,0 +1,23 @@
l2_1
l2_2
l2_3
l2_4
l2_5
l2_6
l2_7
l2_8
l2_9
l1_1
l1_2
l1_3
l1_4
l1_5
l1_6
l1_7
l1_8
l1_9
l1_10
l1_11
l1_12
l1_13
l1_14