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

quoting_style: use and return OsStrings

This exposes the non-UTF-8 functionality to callers. Support in `argument`,
`spec`, and `wc` are implemented, as their usage is simple. A wrapper only
returning valid unicode is used in `ls`, since proper handling of OsStrings
there is more involved (outputs that escape non-unicode work now though).
This commit is contained in:
Justin Tracey 2024-11-22 18:13:16 -05:00
parent 2331600f4c
commit 43229ae104
No known key found for this signature in database
GPG key ID: 62B84F5ABDDDCE54
5 changed files with 76 additions and 35 deletions

View file

@ -21,7 +21,7 @@ use std::os::windows::fs::MetadataExt;
use std::{ use std::{
cmp::Reverse, cmp::Reverse,
error::Error, error::Error,
ffi::OsString, ffi::{OsStr, OsString},
fmt::{Display, Write as FmtWrite}, fmt::{Display, Write as FmtWrite},
fs::{self, DirEntry, FileType, Metadata, ReadDir}, fs::{self, DirEntry, FileType, Metadata, ReadDir},
io::{stdout, BufWriter, ErrorKind, Stdout, Write}, io::{stdout, BufWriter, ErrorKind, Stdout, Write},
@ -55,7 +55,7 @@ use uucore::libc::{dev_t, major, minor};
#[cfg(unix)] #[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::line_ending::LineEnding; use uucore::line_ending::LineEnding;
use uucore::quoting_style::{escape_dir_name, escape_name, QuotingStyle}; use uucore::quoting_style::{self, QuotingStyle};
use uucore::{ use uucore::{
display::Quotable, display::Quotable,
error::{set_exit_code, UError, UResult}, error::{set_exit_code, UError, UResult},
@ -2048,7 +2048,11 @@ impl PathData {
/// file11 /// file11
/// ``` /// ```
fn show_dir_name(path_data: &PathData, out: &mut BufWriter<Stdout>, config: &Config) { fn show_dir_name(path_data: &PathData, out: &mut BufWriter<Stdout>, config: &Config) {
let escaped_name = escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style); // FIXME: replace this with appropriate behavior for literal unprintable bytes
let escaped_name =
quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style)
.to_string_lossy()
.to_string();
let name = if config.hyperlink && !config.dired { let name = if config.hyperlink && !config.dired {
create_hyperlink(&escaped_name, path_data) create_hyperlink(&escaped_name, path_data)
@ -3002,7 +3006,6 @@ use std::sync::Mutex;
#[cfg(unix)] #[cfg(unix)]
use uucore::entries; use uucore::entries;
use uucore::fs::FileInformation; use uucore::fs::FileInformation;
use uucore::quoting_style;
#[cfg(unix)] #[cfg(unix)]
fn cached_uid2usr(uid: u32) -> String { fn cached_uid2usr(uid: u32) -> String {
@ -3542,3 +3545,10 @@ fn calculate_padding_collection(
padding_collections padding_collections
} }
// FIXME: replace this with appropriate behavior for literal unprintable bytes
fn escape_name(name: &OsStr, style: &QuotingStyle) -> String {
quoting_style::escape_name(name, style)
.to_string_lossy()
.to_string()
}

View file

@ -13,7 +13,7 @@ mod word_count;
use std::{ use std::{
borrow::{Borrow, Cow}, borrow::{Borrow, Cow},
cmp::max, cmp::max,
ffi::OsString, ffi::{OsStr, OsString},
fs::{self, File}, fs::{self, File},
io::{self, Write}, io::{self, Write},
iter, iter,
@ -28,7 +28,7 @@ use utf8::{BufReadDecoder, BufReadDecoderError};
use uucore::{ use uucore::{
error::{FromIo, UError, UResult}, error::{FromIo, UError, UResult},
format_usage, help_about, help_usage, format_usage, help_about, help_usage,
quoting_style::{escape_name, QuotingStyle}, quoting_style::{self, QuotingStyle},
shortcut_value_parser::ShortcutValueParser, shortcut_value_parser::ShortcutValueParser,
show, show,
}; };
@ -259,7 +259,7 @@ impl<'a> Input<'a> {
match self { match self {
Self::Path(path) => Some(match path.to_str() { Self::Path(path) => Some(match path.to_str() {
Some(s) if !s.contains('\n') => Cow::Borrowed(s), Some(s) if !s.contains('\n') => Cow::Borrowed(s),
_ => Cow::Owned(escape_name(path.as_os_str(), QS_ESCAPE)), _ => Cow::Owned(escape_name_wrapper(path.as_os_str())),
}), }),
Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)), Self::Stdin(StdinKind::Explicit) => Some(Cow::Borrowed(STDIN_REPR)),
Self::Stdin(StdinKind::Implicit) => None, Self::Stdin(StdinKind::Implicit) => None,
@ -269,7 +269,7 @@ impl<'a> Input<'a> {
/// Converts input into the form that appears in errors. /// Converts input into the form that appears in errors.
fn path_display(&self) -> String { fn path_display(&self) -> String {
match self { match self {
Self::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE), Self::Path(path) => escape_name_wrapper(path.as_os_str()),
Self::Stdin(_) => String::from("standard input"), Self::Stdin(_) => String::from("standard input"),
} }
} }
@ -361,7 +361,7 @@ impl WcError {
Some((input, idx)) => { Some((input, idx)) => {
let path = match input { let path = match input {
Input::Stdin(_) => STDIN_REPR.into(), Input::Stdin(_) => STDIN_REPR.into(),
Input::Path(path) => escape_name(path.as_os_str(), QS_ESCAPE).into(), Input::Path(path) => escape_name_wrapper(path.as_os_str()).into(),
}; };
Self::ZeroLengthFileNameCtx { path, idx } Self::ZeroLengthFileNameCtx { path, idx }
} }
@ -761,7 +761,9 @@ fn files0_iter_file<'a>(path: &Path) -> UResult<impl Iterator<Item = InputIterIt
Err(e) => Err(e.map_err_context(|| { Err(e) => Err(e.map_err_context(|| {
format!( format!(
"cannot open {} for reading", "cannot open {} for reading",
escape_name(path.as_os_str(), QS_QUOTE_ESCAPE) quoting_style::escape_name(path.as_os_str(), QS_QUOTE_ESCAPE)
.into_string()
.expect("All escaped names with the escaping option return valid strings.")
) )
})), })),
} }
@ -793,9 +795,9 @@ fn files0_iter<'a>(
Ok(Input::Path(PathBuf::from(s).into())) Ok(Input::Path(PathBuf::from(s).into()))
} }
} }
Err(e) => Err(e.map_err_context(|| { Err(e) => Err(e
format!("{}: read error", escape_name(&err_path, QS_ESCAPE)) .map_err_context(|| format!("{}: read error", escape_name_wrapper(&err_path)))
}) as Box<dyn UError>), as Box<dyn UError>),
}), }),
); );
// Loop until there is an error; yield that error and then nothing else. // Loop until there is an error; yield that error and then nothing else.
@ -808,6 +810,12 @@ fn files0_iter<'a>(
}) })
} }
fn escape_name_wrapper(name: &OsStr) -> String {
quoting_style::escape_name(name, QS_ESCAPE)
.into_string()
.expect("All escaped names with the escaping option return valid strings.")
}
fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> {
let mut total_word_count = WordCount::default(); let mut total_word_count = WordCount::default();
let mut num_inputs: usize = 0; let mut num_inputs: usize = 0;

View file

@ -112,7 +112,8 @@ fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T
Default::default() Default::default()
} }
ParseError::PartialMatch(v, rest) => { ParseError::PartialMatch(v, rest) => {
if input.starts_with('\'') { let bytes = input.as_encoded_bytes();
if !bytes.is_empty() && bytes[0] == b'\'' {
show_warning!( show_warning!(
"{}: character(s) following character constant have been ignored", "{}: character(s) following character constant have been ignored",
&rest, &rest,

View file

@ -353,20 +353,20 @@ impl Spec {
writer.write_all(&parsed).map_err(FormatError::IoError) writer.write_all(&parsed).map_err(FormatError::IoError)
} }
Self::QuotedString => { Self::QuotedString => {
let s = args.get_str(); let s = escape_name(
writer args.get_str().as_ref(),
.write_all( &QuotingStyle::Shell {
escape_name( escape: true,
s.as_ref(), always_quote: false,
&QuotingStyle::Shell { show_control: false,
escape: true, },
always_quote: false, );
show_control: false, #[cfg(unix)]
}, let bytes = std::os::unix::ffi::OsStringExt::into_vec(s);
) #[cfg(not(unix))]
.as_bytes(), let bytes = s.to_string_lossy().as_bytes().to_owned();
)
.map_err(FormatError::IoError) writer.write_all(&bytes).map_err(FormatError::IoError)
} }
Self::SignedInt { Self::SignedInt {
width, width,

View file

@ -6,8 +6,10 @@
//! Set of functions for escaping names according to different quoting styles. //! Set of functions for escaping names according to different quoting styles.
use std::char::from_digit; use std::char::from_digit;
use std::ffi::OsStr; use std::ffi::{OsStr, OsString};
use std::fmt; use std::fmt;
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
// These are characters with special meaning in the shell (e.g. bash). // These are characters with special meaning in the shell (e.g. bash).
// The first const contains characters that only have a special meaning when they appear at the beginning of a name. // The first const contains characters that only have a special meaning when they appear at the beginning of a name.
@ -459,17 +461,37 @@ fn escape_name_inner(name: &[u8], style: &QuotingStyle, dirname: bool) -> Vec<u8
} }
/// Escape a filename with respect to the given style. /// Escape a filename with respect to the given style.
pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> OsString {
let name = name.to_string_lossy(); #[cfg(unix)]
String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false)).to_string() {
let name = name.as_bytes();
OsStringExt::from_vec(escape_name_inner(name, style, false))
}
#[cfg(not(unix))]
{
let name = name.to_string_lossy();
String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, false))
.to_string()
.into()
}
} }
/// Escape a directory name with respect to the given style. /// Escape a directory name with respect to the given style.
/// This is mainly meant to be used for ls' directory name printing and is not /// This is mainly meant to be used for ls' directory name printing and is not
/// likely to be used elsewhere. /// likely to be used elsewhere.
pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> String { pub fn escape_dir_name(dir_name: &OsStr, style: &QuotingStyle) -> OsString {
let dir_name = dir_name.to_string_lossy(); #[cfg(unix)]
String::from_utf8_lossy(&escape_name_inner(dir_name.as_bytes(), style, true)).to_string() {
let name = dir_name.as_bytes();
OsStringExt::from_vec(escape_name_inner(name, style, true))
}
#[cfg(not(unix))]
{
let name = dir_name.to_string_lossy();
String::from_utf8_lossy(&escape_name_inner(name.as_bytes(), style, true))
.to_string()
.into()
}
} }
impl fmt::Display for QuotingStyle { impl fmt::Display for QuotingStyle {