Compare commits
1
Commits
main
..
modern-diag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c099152810 |
No files matched your search
@@ -147,7 +147,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Retrieve reference artifacts
|
||||
uses: dawidd6/action-download-artifact@v27
|
||||
uses: dawidd6/action-download-artifact@v25
|
||||
continue-on-error: true
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
|
||||
@@ -97,12 +97,12 @@ jobs:
|
||||
RUSTC_WRAPPER: ""
|
||||
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTDOCFLAGS: "-Cpanic=abort"
|
||||
LLVM_PROFILE_FILE: "${{ github.workspace }}/sed-%p-%m.profraw"
|
||||
LLVM_PROFILE_FILE: "sed-%p-%m.profraw"
|
||||
- name: "`grcov` ~ install"
|
||||
id: build_grcov
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --depth=1 https://github.com/mozilla/grcov.git ~/grcov/
|
||||
git clone https://github.com/mozilla/grcov.git ~/grcov/
|
||||
cd ~/grcov
|
||||
cargo install --path .
|
||||
cd -
|
||||
|
||||
Generated
+325
-399
File diff suppressed because it is too large.
Load diff
+5
-1
@@ -1,7 +1,7 @@
|
||||
# sed (uutils)
|
||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||
|
||||
# spell-checker:ignore (libs) mangen sed binstall codspeed libc memchr memmap rlimit rustix textwrap uucore uutests xattr
|
||||
# spell-checker:ignore (libs) mangen sed binstall chrono codspeed libc memchr memmap rlimit rustix textwrap uucore uutests xattr
|
||||
# spell-checker:ignore (misc) clippy codegen msvc startswith uninlined uutils
|
||||
|
||||
[package]
|
||||
@@ -42,6 +42,9 @@ sed = []
|
||||
|
||||
[workspace.dependencies]
|
||||
assert_fs = "1.1.3"
|
||||
chrono = { version = "0.4.37", default-features = false, features = [
|
||||
"clock",
|
||||
] }
|
||||
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.5"
|
||||
clap_mangen = "0.3"
|
||||
@@ -85,6 +88,7 @@ rustix = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_fs = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
ctor = "1.0.0"
|
||||
divan = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
@@ -35,8 +35,6 @@ Below is a list of these extensions and incompatibilities.
|
||||
## Supported BSD and GNU extensions
|
||||
* The second address in a range can be specified as a relative address with +N.
|
||||
* In-place editing of file with the `-i` flag.
|
||||
As in GNU _sed_, a backup suffix must be attached (`-i.bak`,
|
||||
`--in-place=.bak`); BSD's `-i .bak` and `-i ''` are not supported.
|
||||
|
||||
## New extensions
|
||||
* Unicode characters can be specified in regular expression pattern, replacement
|
||||
|
||||
Generated
+390
-375
File diff suppressed because it is too large.
Load diff
+4
-17
@@ -54,8 +54,6 @@ pub struct ProcessingContext {
|
||||
pub last_file: bool,
|
||||
/// Stop processing further input.
|
||||
pub stop_processing: bool,
|
||||
/// Stop without writing a missing final newline (`Q`).
|
||||
pub quit_silently: bool,
|
||||
/// Whether sed operates on bytes or UTF-8 characters
|
||||
pub character_mode: CharacterMode,
|
||||
/// Previously compiled RE, saved for reuse when specifying an empty RE
|
||||
@@ -66,8 +64,8 @@ pub struct ProcessingContext {
|
||||
pub input_action: Option<InputAction>,
|
||||
/// Hold space
|
||||
pub hold: ByteSpace,
|
||||
/// Location of each `{` still open at compile time, innermost last
|
||||
pub open_blocks: Vec<ScriptLocation>,
|
||||
/// Nesting of { } at compile time
|
||||
pub parsed_block_nesting: usize,
|
||||
/// Command associated with each label
|
||||
pub label_to_command_map: HashMap<String, Rc<RefCell<Command>>>,
|
||||
/// Shared `R` readers keyed by file path, so that all `R` commands naming
|
||||
@@ -336,7 +334,6 @@ pub struct Command {
|
||||
pub addr2: Option<Address>, // End address
|
||||
pub non_select: bool, // True if '!'
|
||||
pub start_line: Option<usize>, // Start line number (or None if unlatched)
|
||||
pub range_started: bool, // True once the range has been entered
|
||||
pub data: CommandData, // Command-specific data
|
||||
pub next: Option<Rc<RefCell<Command>>>, // Pointer to next command
|
||||
pub location: ScriptLocation, // Command's definition location
|
||||
@@ -350,7 +347,6 @@ impl Default for Command {
|
||||
addr2: None,
|
||||
non_select: false,
|
||||
start_line: None,
|
||||
range_started: false,
|
||||
data: CommandData::None,
|
||||
next: None,
|
||||
location: ScriptLocation::default(),
|
||||
@@ -396,17 +392,8 @@ pub enum SpaceFlag {
|
||||
pub struct InputAction {
|
||||
/// Next command to execute (rather than commands from start)
|
||||
pub next_command: Option<Rc<RefCell<Command>>>,
|
||||
/// How the read line becomes the pattern space
|
||||
pub next_line: NextLine,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// How a line read by `n` or `N` becomes the pattern space
|
||||
pub enum NextLine {
|
||||
/// Replace the pattern space with the line (`n`)
|
||||
Replace,
|
||||
/// Append the line, after a newline, to these contents (`N`)
|
||||
Append(Vec<u8>),
|
||||
/// Data to prepend to the read contents
|
||||
pub prepend: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+7
-16
@@ -29,7 +29,7 @@ use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use terminal_size::{Width, terminal_size};
|
||||
use uucore::error::UResult;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
const DEFAULT_OUTPUT_WIDTH: usize = 60;
|
||||
|
||||
@@ -77,11 +77,6 @@ pub fn compile(
|
||||
#[cfg(any())]
|
||||
dbg!(&result);
|
||||
|
||||
// Like GNU sed, point at the innermost `{` left open.
|
||||
if let Some(location) = context.open_blocks.last() {
|
||||
return semantic_error(location, "unmatched `{'");
|
||||
}
|
||||
|
||||
// Link branch commands to the target label commands.
|
||||
populate_label_map(result.clone(), context)?;
|
||||
populate_range_commands(result.clone(), context);
|
||||
@@ -90,6 +85,9 @@ pub fn compile(
|
||||
// Link the ends of command blocks to their following commands.
|
||||
// This converts the tree into a graph, so it must be the last
|
||||
// conversion that traverses the structure as a tree.
|
||||
if context.parsed_block_nesting > 0 {
|
||||
return Err(USimpleError::new(1, "unmatched `{'"));
|
||||
}
|
||||
patch_block_endings(result.clone());
|
||||
|
||||
Ok(result)
|
||||
@@ -843,10 +841,6 @@ fn compile_subst_command(
|
||||
) -> UResult<CommandHandling> {
|
||||
line.advance(); // move past 's'
|
||||
|
||||
if line.eol() {
|
||||
return compilation_error(lines, line, "unterminated `s' command");
|
||||
}
|
||||
|
||||
let delimiter = line.current();
|
||||
if delimiter == '\0' || delimiter == '\\' {
|
||||
return compilation_error(
|
||||
@@ -913,10 +907,6 @@ fn compile_trans_command(
|
||||
) -> UResult<CommandHandling> {
|
||||
line.advance(); // move past 'y'
|
||||
|
||||
if line.eol() {
|
||||
return compilation_error(lines, line, "unterminated `y' command");
|
||||
}
|
||||
|
||||
let delimiter = line.current();
|
||||
if delimiter == '\0' || delimiter == '\\' {
|
||||
return compilation_error(
|
||||
@@ -1084,9 +1074,10 @@ fn compile_end_group_command(
|
||||
cmd: &mut Command,
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<CommandHandling> {
|
||||
if context.open_blocks.pop().is_none() {
|
||||
if context.parsed_block_nesting == 0 {
|
||||
return compilation_error(lines, line, "unexpected `}'");
|
||||
}
|
||||
context.parsed_block_nesting -= 1;
|
||||
line.advance();
|
||||
line.eat_spaces();
|
||||
parse_command_ending(lines, line, cmd)?;
|
||||
@@ -1185,7 +1176,7 @@ fn compile_block_command(
|
||||
context: &mut ProcessingContext,
|
||||
) -> UResult<CommandHandling> {
|
||||
line.advance(); // move past '{'
|
||||
context.open_blocks.push(cmd.location.clone());
|
||||
context.parsed_block_nesting += 1;
|
||||
let block_body = compile_sequence(lines, line, context)?;
|
||||
cmd.data = CommandData::BranchTarget(block_body);
|
||||
Ok(CommandHandling::Continue)
|
||||
|
||||
+6
-13
@@ -678,20 +678,13 @@ impl OutputBuffer {
|
||||
)))
|
||||
}
|
||||
|
||||
/// Schedule the specified text for output unchanged, after any
|
||||
/// deferred newline.
|
||||
pub fn write_raw(&mut self, bytes: &[u8]) -> io::Result<()> {
|
||||
self.flush_pending_newline()?;
|
||||
#[cfg(unix)]
|
||||
self.flush_mmap(WriteRange::Complete)?;
|
||||
self.out.write_all(bytes)
|
||||
}
|
||||
|
||||
/// Copy the specified file to the output, after any deferred newline.
|
||||
/// Copy the specified file to the output.
|
||||
pub fn copy_file(&mut self, path: &PathBuf) -> io::Result<()> {
|
||||
// As in GNU sed, write the deferred newline even when the file is
|
||||
// empty or cannot be read.
|
||||
self.write_raw(b"")?;
|
||||
// Flush mmap writes, if any.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.flush_mmap(WriteRange::Complete)?;
|
||||
}
|
||||
|
||||
let Ok(file) = File::open(path) else {
|
||||
// Per POSIX, if the file can't be read treat it as empty.
|
||||
|
||||
+7
-193
@@ -28,7 +28,6 @@ use crate::sed::script_line_provider::ScriptValue;
|
||||
use clap::{Arg, ArgMatches, Command, arg};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
@@ -39,7 +38,7 @@ const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (uutils)");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = parse_args(args)?;
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
// Don't use arg_required_else_help when declaring command
|
||||
// as it exits with code 2 and we use it to check
|
||||
@@ -57,120 +56,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_args(args: impl IntoIterator<Item = OsString>) -> clap::error::Result<ArgMatches> {
|
||||
let mut cmd = uu_app();
|
||||
cmd.build();
|
||||
let args = gnu_in_place_args(&cmd, args);
|
||||
cmd.try_get_matches_from(args)
|
||||
}
|
||||
|
||||
/// Rewrite GNU's `-iSUFFIX`, which clap cannot parse, as `--in-place=SUFFIX`,
|
||||
/// splitting it from flags clustered before it: `-ni.bak` becomes
|
||||
/// `-n --in-place=.bak`. The values of other options are left as they are.
|
||||
fn gnu_in_place_args(cmd: &Command, args: impl IntoIterator<Item = OsString>) -> Vec<OsString> {
|
||||
let mut args = args.into_iter();
|
||||
// The program name.
|
||||
let mut out: Vec<OsString> = args.next().into_iter().collect();
|
||||
while let Some(arg) = args.next() {
|
||||
// Pass non-UTF-8 arguments to clap unchanged.
|
||||
let arg = match arg.into_string() {
|
||||
Ok(arg) => arg,
|
||||
Err(arg) => {
|
||||
out.push(arg);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if arg == "--" {
|
||||
out.push(arg.into());
|
||||
break;
|
||||
}
|
||||
match option_kind(cmd, &arg) {
|
||||
OptionKind::InPlace(pos) => {
|
||||
if pos > 1 {
|
||||
out.push(arg[..pos].into());
|
||||
}
|
||||
out.push(format!("--in-place={}", &arg[pos + 1..]).into());
|
||||
}
|
||||
OptionKind::ValueFollows => {
|
||||
out.push(arg.into());
|
||||
// Keep the value as is, even if it looks like `-iSUFFIX`.
|
||||
out.extend(args.next());
|
||||
}
|
||||
OptionKind::Other => out.push(arg.into()),
|
||||
}
|
||||
}
|
||||
out.extend(args);
|
||||
out
|
||||
}
|
||||
|
||||
enum OptionKind {
|
||||
/// A cluster like `-ni.bak`, with the position of its `i`.
|
||||
InPlace(usize),
|
||||
/// An option whose value is the next argument, like `-f FILE`.
|
||||
ValueFollows,
|
||||
Other,
|
||||
}
|
||||
|
||||
fn option_kind(cmd: &Command, arg: &str) -> OptionKind {
|
||||
if let Some(name) = arg.strip_prefix("--") {
|
||||
return match long_option(cmd, name) {
|
||||
Some(opt) if opt.get_action().takes_values() && !opt.is_require_equals_set() => {
|
||||
OptionKind::ValueFollows
|
||||
}
|
||||
_ => OptionKind::Other,
|
||||
};
|
||||
}
|
||||
let Some(cluster) = arg.strip_prefix('-') else {
|
||||
return OptionKind::Other;
|
||||
};
|
||||
for (pos, c) in cluster.char_indices() {
|
||||
// Leave unknown options for clap to report.
|
||||
let Some(opt) = cmd.get_arguments().find(|a| {
|
||||
a.get_short() == Some(c) || a.get_all_short_aliases().is_some_and(|s| s.contains(&c))
|
||||
}) else {
|
||||
return OptionKind::Other;
|
||||
};
|
||||
let attached = pos + c.len_utf8() < cluster.len();
|
||||
if opt.get_id() == "in-place" {
|
||||
return if attached {
|
||||
OptionKind::InPlace(pos + 1)
|
||||
} else {
|
||||
OptionKind::Other
|
||||
};
|
||||
}
|
||||
if opt.get_action().takes_values() {
|
||||
// The rest of the cluster, or else the next argument, is the value.
|
||||
return if attached {
|
||||
OptionKind::Other
|
||||
} else {
|
||||
OptionKind::ValueFollows
|
||||
};
|
||||
}
|
||||
}
|
||||
OptionKind::Other
|
||||
}
|
||||
|
||||
/// Find a long option by its name or, as `infer_long_args` allows, a unique
|
||||
/// prefix of it. `None` if `name` includes a value.
|
||||
fn long_option<'a>(cmd: &'a Command, name: &str) -> Option<&'a Arg> {
|
||||
if name.contains('=') {
|
||||
return None;
|
||||
}
|
||||
let names = |a: &'a Arg| {
|
||||
a.get_long()
|
||||
.into_iter()
|
||||
.chain(a.get_all_aliases().into_iter().flatten())
|
||||
};
|
||||
if let Some(opt) = cmd.get_arguments().find(|a| names(a).any(|n| n == name)) {
|
||||
return Some(opt);
|
||||
}
|
||||
let mut found = cmd
|
||||
.get_arguments()
|
||||
.filter(|a| names(a).any(|n| n.starts_with(name)));
|
||||
let opt = found.next()?;
|
||||
found.next().is_none().then_some(opt)
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uu_app() -> Command {
|
||||
#[cfg(windows)]
|
||||
@@ -202,9 +87,7 @@ pub fn uu_app() -> Command {
|
||||
.short_alias('r')
|
||||
.help("Use extended regular expressions.")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
// As in GNU sed, a value may begin with `-`.
|
||||
arg!(-e --expression <SCRIPT> "Add script to executed commands.")
|
||||
.allow_hyphen_values(true)
|
||||
.action(clap::ArgAction::Append),
|
||||
// Access with .get_many::<PathBuf>("file")
|
||||
Arg::new("script-file")
|
||||
@@ -212,25 +95,20 @@ pub fn uu_app() -> Command {
|
||||
.long("script-file")
|
||||
.help("Specify script file.")
|
||||
.value_parser(clap::value_parser!(PathBuf))
|
||||
.allow_hyphen_values(true)
|
||||
.action(clap::ArgAction::Append),
|
||||
Arg::new("follow-symlinks")
|
||||
.long("follow-symlinks")
|
||||
.help("Follow symlinks when processing in place.")
|
||||
.action(clap::ArgAction::SetTrue),
|
||||
// Access with .get_one::<String>("in-place")
|
||||
// The SUFFIX must be attached, as in GNU sed.
|
||||
Arg::new("in-place")
|
||||
.short('i')
|
||||
.long("in-place")
|
||||
.help("Edit files in place, making a backup if SUFFIX is supplied.")
|
||||
.value_name("SUFFIX")
|
||||
.num_args(0..=1)
|
||||
.require_equals(true)
|
||||
.default_missing_value(""),
|
||||
// Access with .get_one::<u32>("line-length")
|
||||
arg!(-l --length <NUM> "Specify the 'l' command line-wrap length.")
|
||||
.allow_hyphen_values(true)
|
||||
.value_parser(clap::value_parser!(u32)),
|
||||
arg!(-n --quiet "Suppress automatic printing of pattern space.").aliases(["silent"]),
|
||||
arg!(--posix "Disable non-POSIX extensions."),
|
||||
@@ -383,14 +261,13 @@ fn build_context(matches: &ArgMatches) -> UResult<ProcessingContext> {
|
||||
last_line: false,
|
||||
last_file: false,
|
||||
stop_processing: false,
|
||||
quit_silently: false,
|
||||
saved_regex: None,
|
||||
input_action: None,
|
||||
hold: ByteSpace {
|
||||
content: Vec::new(),
|
||||
has_newline: true,
|
||||
},
|
||||
open_blocks: Vec::new(),
|
||||
parsed_block_nesting: 0,
|
||||
label_to_command_map: HashMap::new(),
|
||||
named_readers: HashMap::new(),
|
||||
range_commands: Vec::new(),
|
||||
@@ -568,76 +445,13 @@ mod tests {
|
||||
assert!(ctx.regex_extended);
|
||||
}
|
||||
|
||||
// In-place argument rewriting
|
||||
fn in_place_args(args: &[&str]) -> Vec<String> {
|
||||
let mut cmd = uu_app();
|
||||
cmd.build();
|
||||
gnu_in_place_args(&cmd, ["sed"].iter().chain(args).map(OsString::from))
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.map(|arg| arg.into_string().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_in_place_args_rewrite_attached_suffix() {
|
||||
let cases: &[(&[&str], &[&str])] = &[
|
||||
(&["-i.bak"], &["--in-place=.bak"]),
|
||||
// Everything after the `i` is the suffix, as in GNU sed.
|
||||
(&["-iE"], &["--in-place=E"]),
|
||||
(&["-i=.bak"], &["--in-place==.bak"]),
|
||||
// Flags clustered before `-i` are kept.
|
||||
(&["-ni.bak"], &["-n", "--in-place=.bak"]),
|
||||
(&["-Esi.bak"], &["-Es", "--in-place=.bak"]),
|
||||
(&["-ri.bak"], &["-r", "--in-place=.bak"]),
|
||||
(
|
||||
&["-i.bak", "--", "-i.keep"],
|
||||
&["--in-place=.bak", "--", "-i.keep"],
|
||||
),
|
||||
// Only the next argument is another option's value.
|
||||
(&["-e", "p", "-i.bak"], &["-e", "p", "--in-place=.bak"]),
|
||||
(
|
||||
&["--expression=p", "-i.bak"],
|
||||
&["--expression=p", "--in-place=.bak"],
|
||||
),
|
||||
(&["--quiet", "-i.bak"], &["--quiet", "--in-place=.bak"]),
|
||||
(
|
||||
&["--in-place", "-i.bak"],
|
||||
&["--in-place", "--in-place=.bak"],
|
||||
),
|
||||
// A `--` that is an option's value does not end the options.
|
||||
(&["-e", "--", "-i.bak"], &["-e", "--", "--in-place=.bak"]),
|
||||
];
|
||||
for (args, expected) in cases {
|
||||
assert_eq!(in_place_args(args), *expected, "args: {args:?}");
|
||||
}
|
||||
}
|
||||
fn test_in_place_with_suffix() {
|
||||
let matches = test_matches(&["-i", ".bak"]);
|
||||
let ctx = build_context(&matches).unwrap();
|
||||
|
||||
#[test]
|
||||
fn test_in_place_args_leave_other_arguments_alone() {
|
||||
let cases: &[&[&str]] = &[
|
||||
&["-i", "s/a/b/", "file"],
|
||||
&["-Ei", "s/a/b/", "file"],
|
||||
&["-En", "s/a/b/", "file"],
|
||||
&["--in-place=.bak", "s/a/b/", "file"],
|
||||
&["--in=.bak", "s/a/b/", "file"],
|
||||
// Values attached to other options.
|
||||
&["-fi.sed", "file"],
|
||||
&["-nfi.sed", "file"],
|
||||
&["-ei", "file"],
|
||||
// Values that follow other options.
|
||||
&["-f", "-ifoo.sed", "file"],
|
||||
&["-nf", "-ifoo.sed", "file"],
|
||||
&["-l", "-i5"],
|
||||
&["--expression", "-i.bak"],
|
||||
&["--expr", "-i.bak"],
|
||||
// Unknown options are left for clap to report.
|
||||
&["-xi.bak"],
|
||||
&["--", "-i.bak"],
|
||||
];
|
||||
for args in cases {
|
||||
assert_eq!(in_place_args(args), *args, "args: {args:?}");
|
||||
}
|
||||
assert!(ctx.in_place);
|
||||
assert_eq!(ctx.in_place_suffix, Some(".bak".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+27
-46
@@ -9,8 +9,8 @@
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::sed::command::{
|
||||
Address, AppendElement, CharacterMode, Command, CommandData, InputAction, NextLine,
|
||||
ProcessingContext, Transliteration,
|
||||
Address, AppendElement, CharacterMode, Command, CommandData, InputAction, ProcessingContext,
|
||||
Transliteration,
|
||||
};
|
||||
use crate::sed::delimited_parser::os_string_from_bytes;
|
||||
use crate::sed::error_handling::{ScriptLocation, input_runtime_error};
|
||||
@@ -124,15 +124,7 @@ fn applies(
|
||||
}
|
||||
} else if let Some(addr1) = &command.addr1 {
|
||||
// See if latch must start.
|
||||
let starts = match addr1 {
|
||||
// Numeric start already passed (block, d, n, branch): start once here.
|
||||
Address::Line(n) if linenum > *n => {
|
||||
!command.range_started && !matches!(addr2, Address::Line(m) if *m < linenum)
|
||||
}
|
||||
_ => match_address(addr1, reader, pattern, context, &command.location)?,
|
||||
};
|
||||
if starts {
|
||||
command.range_started = true;
|
||||
if match_address(addr1, reader, pattern, context, &command.location)? {
|
||||
match addr2 {
|
||||
Address::Line(n) if linenum >= *n => {
|
||||
context.last_address = true;
|
||||
@@ -475,7 +467,7 @@ fn flush_appends(output: &mut OutputBuffer, context: &mut ProcessingContext) ->
|
||||
for elem in &context.append_elements {
|
||||
match elem {
|
||||
AppendElement::Text(text) => {
|
||||
output.write_raw(text.as_ref())?;
|
||||
output.write_bytes(text.as_ref())?;
|
||||
}
|
||||
AppendElement::Path(path) => {
|
||||
output.copy_file(path)?;
|
||||
@@ -575,7 +567,9 @@ fn list(
|
||||
) -> UResult<()> {
|
||||
// Special case for an empty pattern space
|
||||
if line.is_empty() {
|
||||
output.write_str("$\n")?;
|
||||
if line.is_newline_terminated() {
|
||||
output.write_str("$\n")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -660,13 +654,12 @@ fn process_file(
|
||||
// Set the script command from which to start.
|
||||
let mut current: Option<Rc<RefCell<Command>>> =
|
||||
if let Some(action) = context.input_action.take() {
|
||||
// Continue processing the `N` or `n` command.
|
||||
if let NextLine::Append(mut combined_lines) = action.next_line {
|
||||
combined_lines.push(b'\n');
|
||||
combined_lines.extend_from_slice(pattern.as_bytes());
|
||||
// Continue processing the `N` command.
|
||||
let mut combined_lines = action.prepend;
|
||||
combined_lines.push(b'\n');
|
||||
combined_lines.extend_from_slice(pattern.as_bytes());
|
||||
|
||||
pattern.set_to_bytes(combined_lines, pattern.is_newline_terminated());
|
||||
}
|
||||
pattern.set_to_bytes(combined_lines, pattern.is_newline_terminated());
|
||||
action.next_command
|
||||
} else {
|
||||
// Start from the script top.
|
||||
@@ -744,7 +737,7 @@ fn process_file(
|
||||
}
|
||||
CommandData::Text(cmd_bytes) => {
|
||||
let shell_out = shell_stdout(cmd_bytes.to_vec(), &command, context)?;
|
||||
output.write_raw(&shell_out)?;
|
||||
output.write_bytes(&shell_out)?;
|
||||
}
|
||||
_ => panic!("invalid 'e' command data"),
|
||||
},
|
||||
@@ -786,17 +779,7 @@ fn process_file(
|
||||
list(output, &pattern, width, &command.location, context)?;
|
||||
}
|
||||
'n' => {
|
||||
// Print the pattern space and continue with the next
|
||||
// command once the next line is read, as `N` does.
|
||||
if !context.quiet {
|
||||
write_chunk(output, context, &pattern)?;
|
||||
}
|
||||
flush_appends(output, context)?;
|
||||
context.input_action = Some(InputAction {
|
||||
next_command: command.next.clone(),
|
||||
next_line: NextLine::Replace,
|
||||
});
|
||||
continue 'lines;
|
||||
break;
|
||||
}
|
||||
'N' => {
|
||||
flush_appends(output, context)?;
|
||||
@@ -806,7 +789,7 @@ fn process_file(
|
||||
// to perform when the next line is read.
|
||||
context.input_action = Some(InputAction {
|
||||
next_command: command.next.clone(),
|
||||
next_line: NextLine::Append(pattern.as_bytes().to_vec()),
|
||||
prepend: pattern.as_bytes().to_vec(),
|
||||
});
|
||||
continue 'lines;
|
||||
}
|
||||
@@ -835,10 +818,7 @@ fn process_file(
|
||||
i32::try_from(*extract_variant!(command, Number)).unwrap_or(i32::MAX),
|
||||
);
|
||||
context.stop_processing = true;
|
||||
context.quit_silently = true;
|
||||
context.quiet = true;
|
||||
// Like GNU sed, discard text queued by `a`, `r` and `R`.
|
||||
context.append_elements.clear();
|
||||
break;
|
||||
}
|
||||
'R' => {
|
||||
@@ -954,20 +934,18 @@ fn process_file(
|
||||
flush_appends(output, context)?;
|
||||
|
||||
if context.stop_processing {
|
||||
if !context.quit_silently {
|
||||
output.flush_pending_newline()?;
|
||||
}
|
||||
output.flush_pending_newline()?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any n or N command remains.
|
||||
// Take them even with -n, so that they do not carry over to the next file.
|
||||
// Handle any N command remains.
|
||||
// Take it even with -n, so that it does not carry over to the next file.
|
||||
if context.separate
|
||||
&& let Some(action) = context.input_action.take()
|
||||
&& let NextLine::Append(mut pending) = action.next_line
|
||||
&& !context.quiet
|
||||
{
|
||||
let mut pending = action.prepend;
|
||||
pending.push(b'\n');
|
||||
output.write_bytes(&pending)?;
|
||||
if context.unbuffered {
|
||||
@@ -983,10 +961,13 @@ fn reset_latched_address_ranges(range_commands: &mut [Rc<RefCell<Command>>]) {
|
||||
for cmd_rc in range_commands.iter() {
|
||||
let mut cmd = cmd_rc.borrow_mut();
|
||||
|
||||
// Check for address-spec line 0 pre-latch extension.
|
||||
let pre_latched = matches!(cmd.addr1, Some(Address::Line(0)));
|
||||
cmd.start_line = if pre_latched { Some(0) } else { None };
|
||||
cmd.range_started = pre_latched;
|
||||
cmd.start_line =
|
||||
// Check for address-spec line 0 pre-latch extension.
|
||||
if let Some(addr1) = &cmd.addr1 && matches!(addr1, Address::Line(0)) {
|
||||
Some(0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,8 +1005,8 @@ pub fn process_all_files(
|
||||
&& !context.separate
|
||||
&& !context.quiet
|
||||
&& let Some(action) = context.input_action.take()
|
||||
&& let NextLine::Append(mut pending) = action.next_line
|
||||
{
|
||||
let mut pending = action.prepend;
|
||||
pending.push(b'\n');
|
||||
output.write_bytes(&pending)?;
|
||||
}
|
||||
|
||||
@@ -40,12 +40,7 @@ enum State {
|
||||
input_name: String, // Input description (path or script string)
|
||||
line_number: usize, // Current line number
|
||||
},
|
||||
// All scripts have been processed; keeps the last position for errors
|
||||
// raised at the end of the script.
|
||||
Done {
|
||||
input_name: String,
|
||||
line_number: usize,
|
||||
},
|
||||
Done, // All scripts have been processed
|
||||
}
|
||||
|
||||
impl ScriptLineProvider {
|
||||
@@ -60,18 +55,16 @@ impl ScriptLineProvider {
|
||||
/// Return the currently processed script line number.
|
||||
pub fn get_line_number(&self) -> usize {
|
||||
match &self.state {
|
||||
State::Active { line_number, .. } | State::Done { line_number, .. } => *line_number,
|
||||
State::NotStarted => 0,
|
||||
State::Active { line_number, .. } => *line_number,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the currently processed script descriptive name.
|
||||
pub fn get_input_name(&self) -> &str {
|
||||
match &self.state {
|
||||
State::Active { input_name, .. } | State::Done { input_name, .. } => {
|
||||
input_name.as_str()
|
||||
}
|
||||
State::NotStarted => "",
|
||||
State::Active { input_name, .. } => input_name.as_str(),
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +94,7 @@ impl ScriptLineProvider {
|
||||
return Ok(Some(line));
|
||||
}
|
||||
}
|
||||
State::Done { .. } => {
|
||||
State::Done => {
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
@@ -115,10 +108,7 @@ impl ScriptLineProvider {
|
||||
// Move to the next available script source.
|
||||
fn advance_source(&mut self, next_index: usize) -> UResult<()> {
|
||||
if next_index >= self.sources.len() {
|
||||
self.state = State::Done {
|
||||
input_name: self.get_input_name().to_string(),
|
||||
line_number: self.get_line_number(),
|
||||
};
|
||||
self.state = State::Done;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -161,14 +151,7 @@ impl fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
State::NotStarted => f.debug_struct("NotStarted").finish(),
|
||||
State::Done {
|
||||
input_name,
|
||||
line_number,
|
||||
} => f
|
||||
.debug_struct("Done")
|
||||
.field("input_name", input_name)
|
||||
.field("line_number", line_number)
|
||||
.finish(),
|
||||
State::Done => f.debug_struct("Done").finish(),
|
||||
State::Active {
|
||||
index,
|
||||
input_name,
|
||||
|
||||
+15
-443
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user