1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Merge pull request #3688 from anastygnome/fork

rebase of #3679:  Fix wc gnu test suite compatibility - Closes: #3678
This commit is contained in:
Sylvestre Ledru 2022-07-02 11:21:48 +02:00 committed by GitHub
commit 03b208cb9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 80 additions and 32 deletions

View file

@ -7,9 +7,9 @@
use clap::Command; use clap::Command;
use std::path::Path; use std::path::Path;
use uu_ls::quoting_style::{Quotes, QuotingStyle};
use uu_ls::{options, Config, Format}; use uu_ls::{options, Config, Format};
use uucore::error::UResult; use uucore::error::UResult;
use uucore::quoting_style::{Quotes, QuotingStyle};
#[uucore::main] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {

View file

@ -13,15 +13,11 @@ extern crate uucore;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
// dir and vdir also need access to the quoting_style module
pub mod quoting_style;
use clap::{crate_version, Arg, Command}; use clap::{crate_version, Arg, Command};
use glob::Pattern; use glob::Pattern;
use lscolors::LsColors; use lscolors::LsColors;
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use quoting_style::{escape_name, QuotingStyle};
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
use std::{ use std::{
@ -44,6 +40,7 @@ use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
#[cfg(unix)] #[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::quoting_style::{escape_name, QuotingStyle};
use uucore::{ use uucore::{
display::Quotable, display::Quotable,
error::{set_exit_code, UError, UResult}, error::{set_exit_code, UError, UResult},
@ -2257,6 +2254,7 @@ fn get_inode(metadata: &Metadata) -> String {
use std::sync::Mutex; use std::sync::Mutex;
#[cfg(unix)] #[cfg(unix)]
use uucore::entries; use uucore::entries;
use uucore::quoting_style;
#[cfg(unix)] #[cfg(unix)]
fn cached_uid2usr(uid: u32) -> String { fn cached_uid2usr(uid: u32) -> String {

View file

@ -7,9 +7,9 @@
use clap::Command; use clap::Command;
use std::path::Path; use std::path::Path;
use uu_ls::quoting_style::{Quotes, QuotingStyle};
use uu_ls::{options, Config, Format}; use uu_ls::{options, Config, Format};
use uucore::error::UResult; use uucore::error::UResult;
use uucore::quoting_style::{Quotes, QuotingStyle};
#[uucore::main] #[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {

View file

@ -5,6 +5,8 @@
// * For the full copyright and license information, please view the LICENSE // * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // * file that was distributed with this source code.
// cSpell:ignore wc wc's
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -26,10 +28,10 @@ use std::ffi::OsStr;
use std::fmt::Display; use std::fmt::Display;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use uucore::display::{Quotable, Quoted};
use uucore::error::{UError, UResult, USimpleError}; use uucore::error::{UError, UResult, USimpleError};
use uucore::quoting_style::{escape_name, QuotingStyle};
/// The minimum character width for formatting counts when reading from stdin. /// The minimum character width for formatting counts when reading from stdin.
const MINIMUM_WIDTH: usize = 7; const MINIMUM_WIDTH: usize = 7;
@ -40,16 +42,31 @@ struct Settings {
show_lines: bool, show_lines: bool,
show_words: bool, show_words: bool,
show_max_line_length: bool, show_max_line_length: bool,
files0_from_stdin_mode: bool,
title_quoting_style: QuotingStyle,
} }
impl Settings { impl Settings {
fn new(matches: &ArgMatches) -> Self { fn new(matches: &ArgMatches) -> Self {
let title_quoting_style = QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control: false,
};
let files0_from_stdin_mode = match matches.value_of(options::FILES0_FROM) {
Some(files_0_from) => files_0_from == STDIN_REPR,
None => false,
};
let settings = Self { let settings = Self {
show_bytes: matches.is_present(options::BYTES), show_bytes: matches.is_present(options::BYTES),
show_chars: matches.is_present(options::CHAR), show_chars: matches.is_present(options::CHAR),
show_lines: matches.is_present(options::LINES), show_lines: matches.is_present(options::LINES),
show_words: matches.is_present(options::WORDS), show_words: matches.is_present(options::WORDS),
show_max_line_length: matches.is_present(options::MAX_LINE_LENGTH), show_max_line_length: matches.is_present(options::MAX_LINE_LENGTH),
files0_from_stdin_mode,
title_quoting_style,
}; };
if settings.show_bytes if settings.show_bytes
@ -67,6 +84,8 @@ impl Settings {
show_lines: true, show_lines: true,
show_words: true, show_words: true,
show_max_line_length: false, show_max_line_length: false,
files0_from_stdin_mode,
title_quoting_style: settings.title_quoting_style,
} }
} }
@ -126,18 +145,20 @@ impl From<&OsStr> for Input {
impl Input { impl Input {
/// Converts input to title that appears in stats. /// Converts input to title that appears in stats.
fn to_title(&self) -> Option<&Path> { fn to_title(&self, quoting_style: &QuotingStyle) -> Option<String> {
match self { match self {
Input::Path(path) => Some(path), Input::Path(path) => Some(escape_name(&path.clone().into_os_string(), quoting_style)),
Input::Stdin(StdinKind::Explicit) => Some(STDIN_REPR.as_ref()), Input::Stdin(StdinKind::Explicit) => {
Some(escape_name(OsStr::new(STDIN_REPR), quoting_style))
}
Input::Stdin(StdinKind::Implicit) => None, Input::Stdin(StdinKind::Implicit) => None,
} }
} }
fn path_display(&self) -> Quoted<'_> { fn path_display(&self, quoting_style: &QuotingStyle) -> String {
match self { match self {
Input::Path(path) => path.maybe_quote(), Input::Path(path) => escape_name(&path.clone().into_os_string(), quoting_style),
Input::Stdin(_) => "standard input".maybe_quote(), Input::Stdin(_) => escape_name(OsStr::new("standard input"), quoting_style),
} }
} }
} }
@ -417,8 +438,16 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> CountResult {
/// ///
/// Otherwise, the file sizes in the file metadata are summed and the number of /// Otherwise, the file sizes in the file metadata are summed and the number of
/// digits in that total size is returned as the number width /// digits in that total size is returned as the number width
///
/// To mirror GNU wc's behavior a special case is added. If --files0-from is
/// used and input is read from stdin and there is only one calculation enabled
/// columns will not be aligned. This is not exactly GNU wc's behavior, but it
/// is close enough to pass the GNU test suite.
fn compute_number_width(inputs: &[Input], settings: &Settings) -> usize { fn compute_number_width(inputs: &[Input], settings: &Settings) -> usize {
if inputs.is_empty() || (inputs.len() == 1 && settings.number_enabled() == 1) { if inputs.is_empty()
|| (inputs.len() == 1 && settings.number_enabled() == 1)
|| (settings.files0_from_stdin_mode && settings.number_enabled() == 1)
{
return 1; return 1;
} }
@ -458,29 +487,34 @@ fn wc(inputs: &[Input], settings: &Settings) -> UResult<()> {
CountResult::Interrupted(word_count, error) => { CountResult::Interrupted(word_count, error) => {
show!(USimpleError::new( show!(USimpleError::new(
1, 1,
format!("{}: {}", input.path_display(), error) format!(
"{}: {}",
input.path_display(&settings.title_quoting_style),
error
)
)); ));
word_count word_count
} }
CountResult::Failure(error) => { CountResult::Failure(error) => {
show!(USimpleError::new( show!(USimpleError::new(
1, 1,
format!("{}: {}", input.path_display(), error) format!(
"{}: {}",
input.path_display(&settings.title_quoting_style),
error
)
)); ));
continue; continue;
} }
}; };
total_word_count += word_count; total_word_count += word_count;
let result = word_count.with_title(input.to_title()); let result = word_count.with_title(input.to_title(&settings.title_quoting_style));
if let Err(err) = print_stats(settings, &result, number_width) { if let Err(err) = print_stats(settings, &result, number_width) {
show!(USimpleError::new( show!(USimpleError::new(
1, 1,
format!( format!(
"failed to print result for {}: {}", "failed to print result for {}: {}",
result &result.title.unwrap_or_else(|| String::from("<stdin>")),
.title
.unwrap_or_else(|| "<stdin>".as_ref())
.maybe_quote(),
err, err,
), ),
)); ));
@ -488,7 +522,7 @@ fn wc(inputs: &[Input], settings: &Settings) -> UResult<()> {
} }
if num_inputs > 1 { if num_inputs > 1 {
let total_result = total_word_count.with_title(Some("total".as_ref())); let total_result = total_word_count.with_title(Some(String::from("total")));
if let Err(err) = print_stats(settings, &total_result, number_width) { if let Err(err) = print_stats(settings, &total_result, number_width) {
show!(USimpleError::new( show!(USimpleError::new(
1, 1,
@ -524,9 +558,8 @@ fn print_stats(
if settings.show_max_line_length { if settings.show_max_line_length {
columns.push(format!("{:1$}", result.count.max_line_length, number_width)); columns.push(format!("{:1$}", result.count.max_line_length, number_width));
} }
if let Some(title) = &result.title {
if let Some(title) = result.title { columns.push(title.clone());
columns.push(title.maybe_quote().to_string());
} }
writeln!(io::stdout().lock(), "{}", columns.join(" ")) writeln!(io::stdout().lock(), "{}", columns.join(" "))

View file

@ -1,6 +1,5 @@
use std::cmp::max; use std::cmp::max;
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign};
use std::path::Path;
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub struct WordCount { pub struct WordCount {
@ -32,7 +31,7 @@ impl AddAssign for WordCount {
} }
impl WordCount { impl WordCount {
pub fn with_title(self, title: Option<&Path>) -> TitledWordCount { pub fn with_title(self, title: Option<String>) -> TitledWordCount {
TitledWordCount { title, count: self } TitledWordCount { title, count: self }
} }
} }
@ -42,7 +41,7 @@ impl WordCount {
/// The reason we don't simply include title in the `WordCount` struct is that /// The reason we don't simply include title in the `WordCount` struct is that
/// it would result in unnecessary copying of `String`. /// it would result in unnecessary copying of `String`.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct TitledWordCount<'a> { pub struct TitledWordCount {
pub title: Option<&'a Path>, pub title: Option<String>,
pub count: WordCount, pub count: WordCount,
} }

View file

@ -24,6 +24,7 @@ pub use crate::mods::display;
pub use crate::mods::error; pub use crate::mods::error;
pub use crate::mods::os; pub use crate::mods::os;
pub use crate::mods::panic; pub use crate::mods::panic;
pub use crate::mods::quoting_style;
pub use crate::mods::ranges; pub use crate::mods::ranges;
pub use crate::mods::version_cmp; pub use crate::mods::version_cmp;

View file

@ -7,3 +7,5 @@ pub mod os;
pub mod panic; pub mod panic;
pub mod ranges; pub mod ranges;
pub mod version_cmp; pub mod version_cmp;
// dir and vdir also need access to the quoting_style module
pub mod quoting_style;

View file

@ -256,7 +256,7 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) {
(escaped_str, must_quote) (escaped_str, must_quote)
} }
pub(super) fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String {
match style { match style {
QuotingStyle::Literal { show_control } => { QuotingStyle::Literal { show_control } => {
if !show_control { if !show_control {
@ -314,9 +314,10 @@ pub(super) fn escape_name(name: &OsStr, style: &QuotingStyle) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::quoting_style::{escape_name, Quotes, QuotingStyle};
// spell-checker:ignore (tests/words) one\'two one'two // spell-checker:ignore (tests/words) one\'two one'two
use crate::quoting_style::{escape_name, Quotes, QuotingStyle};
fn get_style(s: &str) -> QuotingStyle { fn get_style(s: &str) -> QuotingStyle {
match s { match s {
"literal" => QuotingStyle::Literal { "literal" => QuotingStyle::Literal {

View file

@ -119,6 +119,20 @@ fn test_single_all_counts() {
.stdout_is(" 5 57 302 302 66 alice_in_wonderland.txt\n"); .stdout_is(" 5 57 302 302 66 alice_in_wonderland.txt\n");
} }
#[cfg(unix)]
#[test]
fn test_gnu_compatible_quotation() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("some-dir1");
at.touch("some-dir1/12\n34.txt");
scene
.ucmd()
.args(&["some-dir1/12\n34.txt"])
.run()
.stdout_is("0 0 0 'some-dir1/12'$'\\n''34.txt'\n");
}
#[test] #[test]
fn test_multiple_default() { fn test_multiple_default() {
new_ucmd!() new_ucmd!()