1 Commits
Author SHA1 Message Date
Sylvestre Ledru c099152810 sed: underline the offending script character at a terminal
When stderr is a terminal (or UUTILS_DIAG=always), quote the offending
script line and underline the faulty character, in the layout uucore's
diagnostics use. Pipes and the test suite still get the single-line
message.

Both error constructors handle this, so no call site changes.
ScriptLocation now carries the line text, captured only when diagnostics
are on and shared between commands from the same line, so semantic
errors raised after compilation can still be drawn.

Anything that cannot be drawn (non-UTF-8 script, empty line, no line
recorded) leaves the message unchanged.
2026-09-27 20:03:49 +02:00
13 changed files with 797 additions and 1533 deletions

No files matched your search

+1 -1
View File
@@ -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
+2 -2
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -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 }
-2
View File
@@ -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
+390 -375
View File
File diff suppressed because it is too large. Load diff
+4 -17
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)?;
}
+8 -25
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff