Repository navigation
Improve GNU sed testsuite compatibility #345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,9 @@ use std::rc::Rc; | |
| use terminal_size::{Width, terminal_size}; | ||
| use uucore::error::UResult; | ||
|
|
||
| const DEFAULT_OUTPUT_WIDTH: usize = 60; | ||
| /// Default line-wrap width for the `l` command when no terminal, COLS, or | ||
| /// `-l` flag is available. Matches GNU sed's compiled-in default of 70. | ||
| const DEFAULT_OUTPUT_WIDTH: usize = 70; | ||
|
|
||
| const ERR_ADDRESS_0_USAGE: &str = | ||
| "address 0 can only be used with ~step, a second regular expression, or a read command"; | ||
|
|
@@ -266,8 +268,10 @@ fn compile_sequence( | |
| // Special first-line comment disabling default output | ||
| // According to POSIX: "If the first two characters in the script are | ||
| // "#n", the default output shall be suppressed". | ||
| // This only applies to the very first line of the first script source. | ||
| if !line.eol() | ||
| && line.current() == '#' | ||
| && lines.get_source_index() == 0 | ||
| && lines.get_line_number() == 1 | ||
| && line.get_pos() == 0 | ||
| { | ||
|
|
@@ -539,29 +543,33 @@ fn parse_number( | |
| } | ||
|
|
||
| /// Parse the end of a command, failing with an error on extra characters. | ||
| /// Valid command terminators are: EOL, ';', '}', and '#' (start of comment). | ||
| fn parse_command_ending( | ||
| lines: &ScriptLineProvider, | ||
| line: &mut ScriptCharProvider, | ||
| cmd: &mut Command, | ||
| ) -> UResult<()> { | ||
| if !line.eol() && line.current() == ';' { | ||
| line.advance(); | ||
| return Ok(()); | ||
| } | ||
| line.eat_spaces(); | ||
|
|
||
| if !line.eol() && line.current() == '}' { | ||
| if line.eol() { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| if !line.eol() { | ||
| return compilation_error( | ||
| match line.current() { | ||
| ';' => { | ||
| line.advance(); | ||
| Ok(()) | ||
| } | ||
| '}' | '#' => { | ||
| // Don't consume — the caller (compile_sequence) handles these | ||
| Ok(()) | ||
| } | ||
| _ => compilation_error( | ||
| lines, | ||
| line, | ||
| format!("extra characters at the end of the {} command", cmd.code), | ||
| ); | ||
| ), | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Convert a primitive BRE pattern to a safe ERE-compatible pattern. | ||
|
|
@@ -786,7 +794,7 @@ pub fn compile_replacement( | |
| if let Some(decoded) = parse_char_escape(line) { | ||
| push_script_char(&mut literal, decoded, character_mode); | ||
| } else { | ||
| literal.push(b'\\'); | ||
| // Unknown escape: drop the backslash (GNU behavior) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a test for this in tests/by-util/test_sed.rs |
||
| literal.push(line.current_byte()); | ||
| line.advance(); | ||
|
Comment on lines
+797
to
799
|
||
| } | ||
|
|
@@ -1064,6 +1072,8 @@ pub fn compile_subst_flags( | |
|
|
||
| ';' | '\n' => break, | ||
|
|
||
| '}' | '#' => break, // closing brace and comment start are valid terminators | ||
|
|
||
| other => { | ||
| return compilation_error( | ||
| lines, | ||
|
|
@@ -1228,7 +1238,16 @@ fn compile_label_command( | |
| } | ||
|
|
||
| /// Return the width of the command's terminal or a default. | ||
| /// GNU sed checks COLS env var first, then terminal width, then defaults to 70. | ||
| /// COLS values <= 0 or non-numeric are ignored. | ||
| fn output_width() -> usize { | ||
| if let Ok(cols) = std::env::var("COLS") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where does this COLS handling come from? i can't find it in the GNU manual. |
||
| && let Ok(n) = cols.parse::<usize>() | ||
| && n > 0 | ||
| { | ||
| // Subtract 1 to avoid line wraps on terminals | ||
| return n - 1; | ||
|
Comment on lines
+1248
to
+1249
|
||
| } | ||
| if let Some((Width(w), _)) = terminal_size() { | ||
| w as usize | ||
| } else { | ||
|
|
@@ -1242,21 +1261,31 @@ fn compile_number_command( | |
| lines: &mut ScriptLineProvider, | ||
| line: &mut ScriptCharProvider, | ||
| cmd: &mut Command, | ||
| _context: &mut ProcessingContext, | ||
| context: &mut ProcessingContext, | ||
| ) -> UResult<CommandHandling> { | ||
| line.advance(); // Skip the command character | ||
| line.eat_spaces(); // Skip any leading whitespace | ||
|
|
||
| match parse_number(lines, line, false)? { | ||
| Some(n) => { | ||
| // 'l<N>' is a GNU extension, reject in POSIX mode | ||
| if cmd.code == 'l' && context.posix { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a test for |
||
| return compilation_error(lines, line, "extra characters after command"); | ||
| } | ||
| cmd.data = CommandData::Number(n); | ||
| } | ||
| None => match cmd.code { | ||
| 'q' | 'Q' => { | ||
| cmd.data = CommandData::Number(0); | ||
| } | ||
| 'l' => { | ||
| cmd.data = CommandData::Number(output_width()); | ||
| // Use -l flag value if set (non-zero), else auto-detect | ||
| let width = if context.length > 0 { | ||
| context.length | ||
| } else { | ||
| output_width() | ||
| }; | ||
| cmd.data = CommandData::Number(width); | ||
| } | ||
| _ => panic!("invalid number-expecting command"), | ||
| }, | ||
|
|
@@ -1823,7 +1852,7 @@ mod tests { | |
| let err = result.unwrap_err(); | ||
| let msg = err.to_string(); | ||
|
|
||
| assert!(msg.contains("test.sed:42:5: error: unexpected token")); | ||
| assert!(msg.contains("test.sed:42: unexpected token")); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1840,7 +1869,7 @@ mod tests { | |
| let err = result.unwrap_err(); | ||
| let msg = err.to_string(); | ||
|
|
||
| assert_eq!(msg, "input.txt:3:1: error: invalid command 'x'"); | ||
| assert_eq!(msg, "input.txt:3: invalid command 'x'"); | ||
| } | ||
|
|
||
| // get_verified_cmd_spec | ||
|
|
@@ -1852,7 +1881,7 @@ mod tests { | |
|
|
||
| assert!(result.is_err()); | ||
| let msg = result.unwrap_err().to_string(); | ||
| assert!(msg.contains("test.sed:1:1: error: command expected")); | ||
| assert!(msg.contains("test.sed:1: command expected")); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1863,7 +1892,7 @@ mod tests { | |
|
|
||
| assert!(result.is_err()); | ||
| let msg = result.unwrap_err().to_string(); | ||
| assert!(msg.contains("script.sed:2:1: error: invalid command code `@'")); | ||
| assert!(msg.contains("script.sed:2: invalid command code `@'")); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1874,9 +1903,7 @@ mod tests { | |
|
|
||
| assert!(result.is_err()); | ||
| let msg = result.unwrap_err().to_string(); | ||
| assert!( | ||
| msg.contains("input.sed:3:1: error: command q expects up to 1 address(es), found 2") | ||
| ); | ||
| assert!(msg.contains("input.sed:3: command q expects up to 1 address(es), found 2")); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1896,9 +1923,7 @@ mod tests { | |
| let result = get_verified_cmd_spec(&lines, &line, 2, true); | ||
| assert!(result.is_err()); | ||
| let msg = result.unwrap_err().to_string(); | ||
| assert!( | ||
| msg.contains("input.sed:1:1: error: command i expects up to 1 address(es), found 2") | ||
| ); | ||
| assert!(msg.contains("input.sed:1: command i expects up to 1 address(es), found 2")); | ||
| } | ||
|
|
||
| // parse_number | ||
|
|
@@ -2386,7 +2411,7 @@ mod tests { | |
|
|
||
| assert_eq!(cmd.location.line_number, 1); | ||
| assert_eq!(cmd.location.column_number, 1); | ||
| assert_eq!(cmd.location.input_name.as_ref(), "<script argument 1>"); | ||
| assert_eq!(cmd.location.input_name.as_ref(), "-e expression #1"); | ||
|
|
||
| assert!(cmd.next.is_none()); | ||
| } | ||
|
|
@@ -2403,14 +2428,14 @@ mod tests { | |
| assert_eq!(cmd.code, 'l'); | ||
| assert_eq!(cmd.location.line_number, 1); | ||
| assert_eq!(cmd.location.column_number, 1); | ||
| assert_eq!(cmd.location.input_name.as_ref(), "<script argument 1>"); | ||
| assert_eq!(cmd.location.input_name.as_ref(), "-e expression #1"); | ||
|
|
||
| let binding2 = cmd.next.clone().unwrap(); | ||
| let cmd2 = binding2.borrow(); | ||
| assert_eq!(cmd2.code, 'q'); | ||
| assert_eq!(cmd2.location.line_number, 1); | ||
| assert_eq!(cmd2.location.column_number, 3); | ||
| assert_eq!(cmd2.location.input_name.as_ref(), "<script argument 1>"); | ||
| assert_eq!(cmd2.location.input_name.as_ref(), "-e expression #1"); | ||
|
|
||
| assert!(cmd2.next.is_none()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -364,7 +364,9 @@ fn build_context(matches: &ArgMatches) -> UResult<ProcessingContext> { | |
| in_place_suffix: matches | ||
| .get_one::<String>("in-place") | ||
| .and_then(|s| if s.is_empty() { None } else { Some(s.clone()) }), | ||
| length: matches.get_one::<u32>("length").map_or(70, |v| *v as usize), | ||
| // 0 means "not explicitly set via -l"; the l command then | ||
| // falls back to COLS, terminal width, or DEFAULT_OUTPUT_WIDTH. | ||
| length: matches.get_one::<u32>("length").map_or(0, |v| *v as usize), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with this, |
||
| quiet: matches.get_flag("quiet"), | ||
| posix: matches.get_flag("posix"), | ||
| separate: matches.get_flag("separate") || matches.contains_id("in-place"), | ||
|
|
@@ -513,7 +515,7 @@ mod tests { | |
| assert!(!ctx.follow_symlinks); | ||
| assert!(!ctx.in_place); | ||
| assert_eq!(ctx.in_place_suffix, None); | ||
| assert_eq!(ctx.length, 70); | ||
| assert_eq!(ctx.length, 0); | ||
| assert!(!ctx.quiet); | ||
| assert!(!ctx.posix); | ||
| assert!(!ctx.separate); | ||
|
|
@@ -648,7 +650,7 @@ mod tests { | |
| let ctx_default = build_context(&matches_default).unwrap(); | ||
| let ctx_custom = build_context(&matches_custom).unwrap(); | ||
|
|
||
| assert_eq!(ctx_default.length, 70); | ||
| assert_eq!(ctx_default.length, 0); | ||
| assert_eq!(ctx_custom.length, 120); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add tests for
}/#right after a command, spaces before;, and#nin a second -e