diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 4a61f2710..ea5084a34 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -5,13 +5,11 @@ //spell-checker:ignore TAOCP use clap::{crate_version, Arg, Command}; use std::collections::{HashMap, HashSet, VecDeque}; -use std::fmt::Write; -use std::fs::File; -use std::io::{stdin, BufReader, Read}; +use std::fmt::Display; use std::path::Path; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{format_usage, help_about, help_usage}; +use uucore::error::{UError, UResult}; +use uucore::{format_usage, help_about, help_usage, show}; const ABOUT: &str = help_about!("tsort.md"); const USAGE: &str = help_usage!("tsort.md"); @@ -20,6 +18,43 @@ mod options { pub const FILE: &str = "file"; } +#[derive(Debug)] +enum TsortError { + /// The input file is actually a directory. + IsDir(String), + + /// The number of tokens in the input data is odd. + /// + /// The list of edges must be even because each edge has two + /// components: a source node and a target node. + NumTokensOdd(String), + + /// The graph contains a cycle. + Loop(String), + + /// A particular node in a cycle. (This is mainly used for printing.) + LoopNode(String), +} + +impl std::error::Error for TsortError {} + +impl UError for TsortError {} + +impl Display for TsortError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::IsDir(d) => write!(f, "{d}: read error: Is a directory"), + Self::NumTokensOdd(i) => write!( + f, + "{}: input contains an odd number of tokens", + i.maybe_quote() + ), + Self::Loop(i) => write!(f, "{i}: input contains a loop:"), + Self::LoopNode(v) => write!(f, "{v}"), + } + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; @@ -28,61 +63,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .get_one::(options::FILE) .expect("Value is required by clap"); - let mut stdin_buf; - let mut file_buf; - let mut reader = BufReader::new(if input == "-" { - stdin_buf = stdin(); - &mut stdin_buf as &mut dyn Read + let data = if input == "-" { + let stdin = std::io::stdin(); + std::io::read_to_string(stdin)? } else { let path = Path::new(&input); if path.is_dir() { - return Err(USimpleError::new( - 1, - format!("{input}: read error: Is a directory"), - )); + return Err(TsortError::IsDir(input.to_string()).into()); } - file_buf = File::open(path).map_err_context(|| input.to_string())?; - &mut file_buf as &mut dyn Read - }); + std::fs::read_to_string(path)? + }; - let mut input_buffer = String::new(); - reader.read_to_string(&mut input_buffer)?; + // Create the directed graph from pairs of tokens in the input data. let mut g = Graph::default(); - - for line in input_buffer.lines() { - let tokens: Vec<_> = line.split_whitespace().collect(); - if tokens.is_empty() { - break; - } - for ab in tokens.chunks(2) { - match ab.len() { - 2 => g.add_edge(ab[0], ab[1]), - _ => { - return Err(USimpleError::new( - 1, - format!( - "{}: input contains an odd number of tokens", - input.maybe_quote() - ), - )) - } - } + for ab in data.split_whitespace().collect::>().chunks(2) { + match ab { + [a, b] => g.add_edge(a, b), + _ => return Err(TsortError::NumTokensOdd(input.to_string()).into()), } } match g.run_tsort() { Err(cycle) => { - let mut error_message = format!( - "{}: {}: input contains a loop:\n", - uucore::util_name(), - input - ); + show!(TsortError::Loop(input.to_string())); for node in &cycle { - writeln!(error_message, "{}: {}", uucore::util_name(), node).unwrap(); + show!(TsortError::LoopNode(node.to_string())); } - eprint!("{}", error_message); println!("{}", cycle.join("\n")); - Err(USimpleError::new(1, "")) + Ok(()) } Ok(ordering) => { println!("{}", ordering.join("\n")); diff --git a/tests/by-util/test_tsort.rs b/tests/by-util/test_tsort.rs index 49809e0df..f86add294 100644 --- a/tests/by-util/test_tsort.rs +++ b/tests/by-util/test_tsort.rs @@ -75,3 +75,11 @@ fn test_error_on_dir() { .fails() .stderr_contains("tsort: tsort_test_dir: read error: Is a directory"); } + +#[test] +fn test_split_on_any_whitespace() { + new_ucmd!() + .pipe_in("a\nb\n") + .succeeds() + .stdout_only("a\nb\n"); +}