mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +00:00
Merge pull request #7078 from jfinkels/tsort-split-whitespace
tsort: split edge data on any whitespace chars
This commit is contained in:
commit
e3acd5ab07
2 changed files with 62 additions and 46 deletions
|
@ -5,13 +5,11 @@
|
||||||
//spell-checker:ignore TAOCP
|
//spell-checker:ignore TAOCP
|
||||||
use clap::{crate_version, Arg, Command};
|
use clap::{crate_version, Arg, Command};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::fmt::Write;
|
use std::fmt::Display;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{stdin, BufReader, Read};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError};
|
use uucore::error::{UError, UResult};
|
||||||
use uucore::{format_usage, help_about, help_usage};
|
use uucore::{format_usage, help_about, help_usage, show};
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("tsort.md");
|
const ABOUT: &str = help_about!("tsort.md");
|
||||||
const USAGE: &str = help_usage!("tsort.md");
|
const USAGE: &str = help_usage!("tsort.md");
|
||||||
|
@ -20,6 +18,43 @@ mod options {
|
||||||
pub const FILE: &str = "file";
|
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]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().try_get_matches_from(args)?;
|
let matches = uu_app().try_get_matches_from(args)?;
|
||||||
|
@ -28,61 +63,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.get_one::<String>(options::FILE)
|
.get_one::<String>(options::FILE)
|
||||||
.expect("Value is required by clap");
|
.expect("Value is required by clap");
|
||||||
|
|
||||||
let mut stdin_buf;
|
let data = if input == "-" {
|
||||||
let mut file_buf;
|
let stdin = std::io::stdin();
|
||||||
let mut reader = BufReader::new(if input == "-" {
|
std::io::read_to_string(stdin)?
|
||||||
stdin_buf = stdin();
|
|
||||||
&mut stdin_buf as &mut dyn Read
|
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(&input);
|
let path = Path::new(&input);
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
return Err(USimpleError::new(
|
return Err(TsortError::IsDir(input.to_string()).into());
|
||||||
1,
|
|
||||||
format!("{input}: read error: Is a directory"),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
file_buf = File::open(path).map_err_context(|| input.to_string())?;
|
std::fs::read_to_string(path)?
|
||||||
&mut file_buf as &mut dyn Read
|
};
|
||||||
});
|
|
||||||
|
|
||||||
let mut input_buffer = String::new();
|
// Create the directed graph from pairs of tokens in the input data.
|
||||||
reader.read_to_string(&mut input_buffer)?;
|
|
||||||
let mut g = Graph::default();
|
let mut g = Graph::default();
|
||||||
|
for ab in data.split_whitespace().collect::<Vec<&str>>().chunks(2) {
|
||||||
for line in input_buffer.lines() {
|
match ab {
|
||||||
let tokens: Vec<_> = line.split_whitespace().collect();
|
[a, b] => g.add_edge(a, b),
|
||||||
if tokens.is_empty() {
|
_ => return Err(TsortError::NumTokensOdd(input.to_string()).into()),
|
||||||
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()
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match g.run_tsort() {
|
match g.run_tsort() {
|
||||||
Err(cycle) => {
|
Err(cycle) => {
|
||||||
let mut error_message = format!(
|
show!(TsortError::Loop(input.to_string()));
|
||||||
"{}: {}: input contains a loop:\n",
|
|
||||||
uucore::util_name(),
|
|
||||||
input
|
|
||||||
);
|
|
||||||
for node in &cycle {
|
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"));
|
println!("{}", cycle.join("\n"));
|
||||||
Err(USimpleError::new(1, ""))
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(ordering) => {
|
Ok(ordering) => {
|
||||||
println!("{}", ordering.join("\n"));
|
println!("{}", ordering.join("\n"));
|
||||||
|
|
|
@ -75,3 +75,11 @@ fn test_error_on_dir() {
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains("tsort: tsort_test_dir: read error: Is a directory");
|
.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");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue