mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
env: support string args by "-S", "-vS" or "--split-strings"
This commit is contained in:
@@ -25,6 +25,7 @@ sudoedit
|
||||
tcsh
|
||||
tzselect
|
||||
urandom
|
||||
VARNAME
|
||||
wtmp
|
||||
zsh
|
||||
|
||||
|
||||
Generated
+25
-11
@@ -547,7 +547,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"regex",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
@@ -559,7 +559,7 @@ checksum = "3e1a2532e4ed4ea13031c13bc7bc0dbca4aae32df48e9d77f0d1e743179f2ea1"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -574,7 +574,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -942,7 +942,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1791,7 +1791,7 @@ dependencies = [
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -1902,9 +1902,23 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
@@ -2039,9 +2053,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.23"
|
||||
version = "2.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
|
||||
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3296,7 +3310,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3318,7 +3332,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.23",
|
||||
"syn 2.0.32",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
Vendored
+357
-187
File diff suppressed because it is too large
Load Diff
Vendored
+325
@@ -0,0 +1,325 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// This module contains classes and functions for dealing with the differences
|
||||
// between operating systems regarding the lossless processing of OsStr/OsString.
|
||||
// In contrast to existing crates with similar purpose, this module does not use any
|
||||
// `unsafe` features or functions.
|
||||
// Due to a suboptimal design aspect of OsStr/OsString on windows, we need to
|
||||
// encode/decode to wide chars on windows operating system.
|
||||
// This prevents borrowing from OsStr on windows. Anyway, if optimally used,#
|
||||
// this conversion needs to be done only once in the beginning and at the end.
|
||||
|
||||
use std::ffi::OsString;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::prelude::*;
|
||||
use std::{borrow::Cow, ffi::OsStr};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use u16 as NativeIntCharU;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use u8 as NativeIntCharU;
|
||||
|
||||
pub type NativeCharInt = NativeIntCharU;
|
||||
pub type NativeIntStr = [NativeCharInt];
|
||||
pub type NativeIntString = Vec<NativeCharInt>;
|
||||
|
||||
pub struct NCvt;
|
||||
|
||||
pub trait Convert<From, To> {
|
||||
fn convert(f: From) -> To;
|
||||
}
|
||||
|
||||
// ================ str/String =================
|
||||
|
||||
impl<'a> Convert<&'a str, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: &'a str) -> Cow<'a, NativeIntStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(f.encode_utf16().collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Cow::Borrowed(f.as_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<&'a String, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: &'a String) -> Cow<'a, NativeIntStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(f.encode_utf16().collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Cow::Borrowed(f.as_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<String, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: String) -> Cow<'a, NativeIntStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(f.encode_utf16().collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Cow::Owned(f.into_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================ OsStr/OsString =================
|
||||
|
||||
impl<'a> Convert<&'a OsStr, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: &'a OsStr) -> Cow<'a, NativeIntStr> {
|
||||
to_native_int_representation(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<&'a OsString, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: &'a OsString) -> Cow<'a, NativeIntStr> {
|
||||
to_native_int_representation(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<OsString, Cow<'a, NativeIntStr>> for NCvt {
|
||||
fn convert(f: OsString) -> Cow<'a, NativeIntStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(f.encode_wide().collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Cow::Owned(f.into_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Vec<Str/String> =================
|
||||
|
||||
impl<'a> Convert<&'a Vec<&'a str>, Vec<Cow<'a, NativeIntStr>>> for NCvt {
|
||||
fn convert(f: &'a Vec<&'a str>) -> Vec<Cow<'a, NativeIntStr>> {
|
||||
f.iter().map(|x| Self::convert(*x)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<Vec<&'a str>, Vec<Cow<'a, NativeIntStr>>> for NCvt {
|
||||
fn convert(f: Vec<&'a str>) -> Vec<Cow<'a, NativeIntStr>> {
|
||||
f.iter().map(|x| Self::convert(*x)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<&'a Vec<String>, Vec<Cow<'a, NativeIntStr>>> for NCvt {
|
||||
fn convert(f: &'a Vec<String>) -> Vec<Cow<'a, NativeIntStr>> {
|
||||
f.iter().map(Self::convert).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Convert<Vec<String>, Vec<Cow<'a, NativeIntStr>>> for NCvt {
|
||||
fn convert(f: Vec<String>) -> Vec<Cow<'a, NativeIntStr>> {
|
||||
f.into_iter().map(Self::convert).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_native_int_representation(input: &OsStr) -> Cow<'_, NativeIntStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(input.encode_wide().collect())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Cow::Borrowed(input.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // needed on windows
|
||||
pub fn from_native_int_representation(input: Cow<'_, NativeIntStr>) -> Cow<'_, OsStr> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Cow::Owned(OsString::from_wide(&input))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
match input {
|
||||
Cow::Borrowed(borrow) => Cow::Borrowed(OsStr::from_bytes(borrow)),
|
||||
Cow::Owned(own) => Cow::Owned(OsString::from_vec(own)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)] // needed on windows
|
||||
pub fn from_native_int_representation_owned(input: NativeIntString) -> OsString {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
OsString::from_wide(&input)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
OsString::from_vec(input)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_single_native_int_value(c: &char) -> Option<NativeCharInt> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let mut buf = [0u16, 0];
|
||||
let s = c.encode_utf16(&mut buf);
|
||||
if s.len() == 1 {
|
||||
Some(buf[0])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let mut buf = [0u8, 0, 0, 0];
|
||||
let s = c.encode_utf8(&mut buf);
|
||||
if s.len() == 1 {
|
||||
Some(buf[0])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_char_from_native_int(ni: NativeCharInt) -> Option<(char, NativeCharInt)> {
|
||||
let c_opt;
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
c_opt = char::decode_utf16([ni; 1]).next().unwrap().ok();
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
c_opt = std::str::from_utf8(&[ni; 1])
|
||||
.ok()
|
||||
.map(|x| x.chars().next().unwrap());
|
||||
};
|
||||
|
||||
if let Some(c) = c_opt {
|
||||
return Some((c, ni));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub struct NativeStr<'a> {
|
||||
native: Cow<'a, NativeIntStr>,
|
||||
}
|
||||
|
||||
impl<'a> NativeStr<'a> {
|
||||
pub fn new(str: &'a OsStr) -> Self {
|
||||
Self {
|
||||
native: to_native_int_representation(str),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native(&self) -> Cow<'a, NativeIntStr> {
|
||||
self.native.clone()
|
||||
}
|
||||
|
||||
pub fn into_native(self) -> Cow<'a, NativeIntStr> {
|
||||
self.native
|
||||
}
|
||||
|
||||
pub fn contains(&self, x: &char) -> Option<bool> {
|
||||
let n_c = get_single_native_int_value(x)?;
|
||||
Some(self.native.contains(&n_c))
|
||||
}
|
||||
|
||||
pub fn slice(&self, from: usize, to: usize) -> Cow<'a, OsStr> {
|
||||
let result = self.match_cow(|b| Ok::<_, ()>(&b[from..to]), |o| Ok(o[from..to].to_vec()));
|
||||
result.unwrap()
|
||||
}
|
||||
|
||||
pub fn split_once(&self, pred: &char) -> Option<(Cow<'a, OsStr>, Cow<'a, OsStr>)> {
|
||||
let n_c = get_single_native_int_value(pred)?;
|
||||
let p = self.native.iter().position(|&x| x == n_c)?;
|
||||
let before = self.slice(0, p);
|
||||
let after = self.slice(p + 1, self.native.len());
|
||||
Some((before, after))
|
||||
}
|
||||
|
||||
pub fn split_at(&self, pos: usize) -> (Cow<'a, OsStr>, Cow<'a, OsStr>) {
|
||||
let before = self.slice(0, pos);
|
||||
let after = self.slice(pos, self.native.len());
|
||||
(before, after)
|
||||
}
|
||||
|
||||
pub fn strip_prefix(&self, prefix: &OsStr) -> Option<Cow<'a, OsStr>> {
|
||||
let n_prefix = to_native_int_representation(prefix);
|
||||
let result = self.match_cow(
|
||||
|b| b.strip_prefix(&*n_prefix).ok_or(()),
|
||||
|o| o.strip_prefix(&*n_prefix).map(|x| x.to_vec()).ok_or(()),
|
||||
);
|
||||
result.ok()
|
||||
}
|
||||
|
||||
pub fn strip_prefix_native(&self, prefix: &OsStr) -> Option<Cow<'a, NativeIntStr>> {
|
||||
let n_prefix = to_native_int_representation(prefix);
|
||||
let result = self.match_cow_native(
|
||||
|b| b.strip_prefix(&*n_prefix).ok_or(()),
|
||||
|o| o.strip_prefix(&*n_prefix).map(|x| x.to_vec()).ok_or(()),
|
||||
);
|
||||
result.ok()
|
||||
}
|
||||
|
||||
fn match_cow<FnBorrow, FnOwned, Err>(
|
||||
&self,
|
||||
f_borrow: FnBorrow,
|
||||
f_owned: FnOwned,
|
||||
) -> Result<Cow<'a, OsStr>, Err>
|
||||
where
|
||||
FnBorrow: FnOnce(&'a [NativeCharInt]) -> Result<&'a [NativeCharInt], Err>,
|
||||
FnOwned: FnOnce(&Vec<NativeCharInt>) -> Result<Vec<NativeCharInt>, Err>,
|
||||
{
|
||||
match &self.native {
|
||||
Cow::Borrowed(b) => {
|
||||
let slice = f_borrow(b);
|
||||
let os_str = slice.map(|x| from_native_int_representation(Cow::Borrowed(x)));
|
||||
os_str
|
||||
}
|
||||
Cow::Owned(o) => {
|
||||
let slice = f_owned(o);
|
||||
let os_str = slice.map(from_native_int_representation_owned);
|
||||
os_str.map(Cow::Owned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_cow_native<FnBorrow, FnOwned, Err>(
|
||||
&self,
|
||||
f_borrow: FnBorrow,
|
||||
f_owned: FnOwned,
|
||||
) -> Result<Cow<'a, NativeIntStr>, Err>
|
||||
where
|
||||
FnBorrow: FnOnce(&'a [NativeCharInt]) -> Result<&'a [NativeCharInt], Err>,
|
||||
FnOwned: FnOnce(&Vec<NativeCharInt>) -> Result<Vec<NativeCharInt>, Err>,
|
||||
{
|
||||
match &self.native {
|
||||
Cow::Borrowed(b) => {
|
||||
let slice = f_borrow(b);
|
||||
slice.map(Cow::Borrowed)
|
||||
}
|
||||
Cow::Owned(o) => {
|
||||
let slice = f_owned(o);
|
||||
slice.map(Cow::Owned)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::string_parser;
|
||||
|
||||
/// An error returned when string arg splitting fails.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ParseError {
|
||||
MissingClosingQuote {
|
||||
pos: usize,
|
||||
c: char,
|
||||
},
|
||||
InvalidBackslashAtEndOfStringInMinusS {
|
||||
pos: usize,
|
||||
quoting: String,
|
||||
},
|
||||
BackslashCNotAllowedInDoubleQuotes {
|
||||
pos: usize,
|
||||
},
|
||||
InvalidSequenceBackslashXInMinusS {
|
||||
pos: usize,
|
||||
c: char,
|
||||
},
|
||||
ParsingOfVariableNameFailed {
|
||||
pos: usize,
|
||||
msg: String,
|
||||
},
|
||||
InternalError {
|
||||
pos: usize,
|
||||
sub_err: string_parser::Error,
|
||||
},
|
||||
ReachedEnd,
|
||||
ContinueWithDelimiter,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(format!("{:?}", self).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ParseError {}
|
||||
|
||||
impl From<string_parser::Error> for ParseError {
|
||||
fn from(value: string_parser::Error) -> Self {
|
||||
Self::InternalError {
|
||||
pos: value.peek_position,
|
||||
sub_err: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+375
@@ -0,0 +1,375 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
//
|
||||
// This file is based on work from Tomasz Miąsko who published it as "shell_words" crate,
|
||||
// licensed under the Apache License, Version 2.0 <LICENSE-APACHE>
|
||||
// or the MIT license <LICENSE-MIT>, at your option.
|
||||
//
|
||||
//! Process command line according to parsing rules of original GNU env.
|
||||
//! Even though it looks quite like a POSIX syntax, the original
|
||||
//! "shell_words" implementation had to be adapted significantly.
|
||||
//!
|
||||
//! Apart from the grammar differences, there is a new feature integrated: $VARIABLE expansion.
|
||||
//!
|
||||
//! [GNU env] <https://www.gnu.org/software/coreutils/manual/html_node/env-invocation.html#g_t_002dS_002f_002d_002dsplit_002dstring-syntax>
|
||||
// spell-checker:ignore (words) Tomasz Miąsko rntfv FFFD varname
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::native_int_str::from_native_int_representation;
|
||||
use crate::native_int_str::NativeCharInt;
|
||||
use crate::native_int_str::NativeIntStr;
|
||||
use crate::native_int_str::NativeIntString;
|
||||
use crate::parse_error::ParseError;
|
||||
use crate::string_expander::StringExpander;
|
||||
use crate::string_parser::StringParser;
|
||||
use crate::variable_parser::VariableParser;
|
||||
|
||||
const BACKSLASH: char = '\\';
|
||||
const DOUBLE_QUOTES: char = '\"';
|
||||
const SINGLE_QUOTES: char = '\'';
|
||||
const NEW_LINE: char = '\n';
|
||||
const DOLLAR: char = '$';
|
||||
|
||||
const REPLACEMENTS: [(char, char); 9] = [
|
||||
('r', '\r'),
|
||||
('n', '\n'),
|
||||
('t', '\t'),
|
||||
('f', '\x0C'),
|
||||
('v', '\x0B'),
|
||||
('_', ' '),
|
||||
('#', '#'),
|
||||
('$', '$'),
|
||||
('"', '"'),
|
||||
];
|
||||
|
||||
const ASCII_WHITESPACE_CHARS: [char; 6] = [' ', '\t', '\r', '\n', '\x0B', '\x0C'];
|
||||
|
||||
pub struct SplitIterator<'a> {
|
||||
expander: StringExpander<'a>,
|
||||
words: Vec<Vec<NativeCharInt>>,
|
||||
}
|
||||
|
||||
impl<'a> SplitIterator<'a> {
|
||||
pub fn new(s: &'a NativeIntStr) -> Self {
|
||||
Self {
|
||||
expander: StringExpander::new(s),
|
||||
words: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_one(&mut self) -> Result<(), ParseError> {
|
||||
self.expander
|
||||
.get_parser_mut()
|
||||
.consume_one_ascii_or_all_non_ascii()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn take_one(&mut self) -> Result<(), ParseError> {
|
||||
Ok(self.expander.take_one()?)
|
||||
}
|
||||
|
||||
fn get_current_char(&self) -> Option<char> {
|
||||
self.expander.peek().ok()
|
||||
}
|
||||
|
||||
fn push_char_to_word(&mut self, c: char) {
|
||||
self.expander.put_one_char(c);
|
||||
}
|
||||
|
||||
fn push_word_to_words(&mut self) {
|
||||
let word = self.expander.take_collected_output();
|
||||
self.words.push(word);
|
||||
}
|
||||
|
||||
fn get_parser(&self) -> &StringParser<'a> {
|
||||
self.expander.get_parser()
|
||||
}
|
||||
|
||||
fn get_parser_mut(&mut self) -> &mut StringParser<'a> {
|
||||
self.expander.get_parser_mut()
|
||||
}
|
||||
|
||||
fn substitute_variable<'x>(&'x mut self) -> Result<(), ParseError> {
|
||||
let mut var_parse = VariableParser::<'a, '_> {
|
||||
parser: self.get_parser_mut(),
|
||||
};
|
||||
|
||||
let (name, default) = var_parse.parse_variable()?;
|
||||
|
||||
let varname_os_str_cow = from_native_int_representation(Cow::Borrowed(name));
|
||||
let value = std::env::var_os(varname_os_str_cow);
|
||||
match (&value, default) {
|
||||
(None, None) => {} // do nothing, just replace it with ""
|
||||
(Some(value), _) => {
|
||||
self.expander.put_string(value);
|
||||
}
|
||||
(None, Some(default)) => {
|
||||
self.expander.put_native_string(default);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_and_replace_ascii_escape_code(&mut self, c: char) -> Result<bool, ParseError> {
|
||||
if let Some(replace) = REPLACEMENTS.iter().find(|&x| x.0 == c) {
|
||||
self.skip_one()?;
|
||||
self.push_char_to_word(replace.1);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn make_invalid_sequence_backslash_xin_minus_s(&self, c: char) -> ParseError {
|
||||
ParseError::InvalidSequenceBackslashXInMinusS {
|
||||
pos: self.expander.get_parser().get_peek_position(),
|
||||
c,
|
||||
}
|
||||
}
|
||||
|
||||
fn state_root(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.state_delimiter() {
|
||||
Err(ParseError::ContinueWithDelimiter) => {}
|
||||
Err(ParseError::ReachedEnd) => return Ok(()),
|
||||
result => return result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_delimiter(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => return Ok(()),
|
||||
Some('#') => {
|
||||
self.skip_one()?;
|
||||
self.state_comment()?;
|
||||
}
|
||||
Some(BACKSLASH) => {
|
||||
self.skip_one()?;
|
||||
self.state_delimiter_backslash()?;
|
||||
}
|
||||
Some(c) if ASCII_WHITESPACE_CHARS.contains(&c) => {
|
||||
self.skip_one()?;
|
||||
}
|
||||
Some(_) => {
|
||||
// Don't consume char. Will be done in unquoted state.
|
||||
self.state_unquoted()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_delimiter_backslash(&mut self) -> Result<(), ParseError> {
|
||||
match self.get_current_char() {
|
||||
None => Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
quoting: "Delimiter".into(),
|
||||
}),
|
||||
Some('_') | Some(NEW_LINE) => {
|
||||
self.skip_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(DOLLAR) | Some(BACKSLASH) | Some('#') | Some(SINGLE_QUOTES)
|
||||
| Some(DOUBLE_QUOTES) => {
|
||||
self.take_one()?;
|
||||
self.state_unquoted()
|
||||
}
|
||||
Some('c') => Err(ParseError::ReachedEnd),
|
||||
Some(c) if self.check_and_replace_ascii_escape_code(c)? => self.state_unquoted(),
|
||||
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_unquoted(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => {
|
||||
self.push_word_to_words();
|
||||
return Err(ParseError::ReachedEnd);
|
||||
}
|
||||
Some(DOLLAR) => {
|
||||
self.substitute_variable()?;
|
||||
}
|
||||
Some(SINGLE_QUOTES) => {
|
||||
self.skip_one()?;
|
||||
self.state_single_quoted()?;
|
||||
}
|
||||
Some(DOUBLE_QUOTES) => {
|
||||
self.skip_one()?;
|
||||
self.state_double_quoted()?;
|
||||
}
|
||||
Some(BACKSLASH) => {
|
||||
self.skip_one()?;
|
||||
self.state_unquoted_backslash()?;
|
||||
}
|
||||
Some(c) if ASCII_WHITESPACE_CHARS.contains(&c) => {
|
||||
self.push_word_to_words();
|
||||
self.skip_one()?;
|
||||
return Ok(());
|
||||
}
|
||||
Some(_) => {
|
||||
self.take_one()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_unquoted_backslash(&mut self) -> Result<(), ParseError> {
|
||||
match self.get_current_char() {
|
||||
None => Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
quoting: "Unquoted".into(),
|
||||
}),
|
||||
Some(NEW_LINE) => {
|
||||
self.skip_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some('_') => {
|
||||
self.skip_one()?;
|
||||
self.push_word_to_words();
|
||||
Err(ParseError::ContinueWithDelimiter)
|
||||
}
|
||||
Some('c') => {
|
||||
self.push_word_to_words();
|
||||
Err(ParseError::ReachedEnd)
|
||||
}
|
||||
Some(DOLLAR) | Some(BACKSLASH) | Some(SINGLE_QUOTES) | Some(DOUBLE_QUOTES) => {
|
||||
self.take_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(c) if self.check_and_replace_ascii_escape_code(c)? => Ok(()),
|
||||
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_single_quoted(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => {
|
||||
return Err(ParseError::MissingClosingQuote {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
c: '\'',
|
||||
})
|
||||
}
|
||||
Some(SINGLE_QUOTES) => {
|
||||
self.skip_one()?;
|
||||
return Ok(());
|
||||
}
|
||||
Some(BACKSLASH) => {
|
||||
self.skip_one()?;
|
||||
self.split_single_quoted_backslash()?;
|
||||
}
|
||||
Some(_) => {
|
||||
self.take_one()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_single_quoted_backslash(&mut self) -> Result<(), ParseError> {
|
||||
match self.get_current_char() {
|
||||
None => Err(ParseError::MissingClosingQuote {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
c: '\'',
|
||||
}),
|
||||
Some(NEW_LINE) => {
|
||||
self.skip_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(SINGLE_QUOTES) | Some(BACKSLASH) => {
|
||||
self.take_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(c) if REPLACEMENTS.iter().any(|&x| x.0 == c) => {
|
||||
// See GNU test-suite e11: In single quotes, \t remains as it is.
|
||||
// Comparing with GNU behavior: \a is not accepted and issues an error.
|
||||
// So apparently only known sequences are allowed, even though they are not expanded.... bug of GNU?
|
||||
self.push_char_to_word(BACKSLASH);
|
||||
self.take_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_double_quoted(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => {
|
||||
return Err(ParseError::MissingClosingQuote {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
c: '"',
|
||||
})
|
||||
}
|
||||
Some(DOLLAR) => {
|
||||
self.substitute_variable()?;
|
||||
}
|
||||
Some(DOUBLE_QUOTES) => {
|
||||
self.skip_one()?;
|
||||
return Ok(());
|
||||
}
|
||||
Some(BACKSLASH) => {
|
||||
self.skip_one()?;
|
||||
self.state_double_quoted_backslash()?;
|
||||
}
|
||||
Some(_) => {
|
||||
self.take_one()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_double_quoted_backslash(&mut self) -> Result<(), ParseError> {
|
||||
match self.get_current_char() {
|
||||
None => Err(ParseError::MissingClosingQuote {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
c: '"',
|
||||
}),
|
||||
Some(NEW_LINE) => {
|
||||
self.skip_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some(DOUBLE_QUOTES) | Some(DOLLAR) | Some(BACKSLASH) => {
|
||||
self.take_one()?;
|
||||
Ok(())
|
||||
}
|
||||
Some('c') => Err(ParseError::BackslashCNotAllowedInDoubleQuotes {
|
||||
pos: self.get_parser().get_peek_position(),
|
||||
}),
|
||||
Some(c) if self.check_and_replace_ascii_escape_code(c)? => Ok(()),
|
||||
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||
}
|
||||
}
|
||||
|
||||
fn state_comment(&mut self) -> Result<(), ParseError> {
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => return Err(ParseError::ReachedEnd),
|
||||
Some(NEW_LINE) => {
|
||||
self.skip_one()?;
|
||||
return Ok(());
|
||||
}
|
||||
Some(_) => {
|
||||
self.get_parser_mut().skip_until_char_or_end(NEW_LINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(mut self) -> Result<Vec<NativeIntString>, ParseError> {
|
||||
self.state_root()?;
|
||||
Ok(self.words)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(s: &NativeIntStr) -> Result<Vec<NativeIntString>, ParseError> {
|
||||
let splitted_args = SplitIterator::new(s).split()?;
|
||||
Ok(splitted_args)
|
||||
}
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
mem,
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
native_int_str::{to_native_int_representation, NativeCharInt, NativeIntStr},
|
||||
string_parser::{Chunk, Error, StringParser},
|
||||
};
|
||||
|
||||
/// This class makes parsing and word collection more convenient.
|
||||
///
|
||||
/// It manages an "output" buffer that is automatically filled.
|
||||
/// It provides "skip_one" and "take_one" that focus on
|
||||
/// working with ASCII separators. Thus they will skip or take
|
||||
/// all consecutive non-ascii char sequences at once.
|
||||
pub struct StringExpander<'a> {
|
||||
parser: StringParser<'a>,
|
||||
output: Vec<NativeCharInt>,
|
||||
}
|
||||
|
||||
impl<'a> StringExpander<'a> {
|
||||
pub fn new(input: &'a NativeIntStr) -> Self {
|
||||
Self {
|
||||
parser: StringParser::new(input),
|
||||
output: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_at(input: &'a NativeIntStr, pos: usize) -> Self {
|
||||
Self {
|
||||
parser: StringParser::new_at(input, pos),
|
||||
output: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_parser(&self) -> &StringParser<'a> {
|
||||
&self.parser
|
||||
}
|
||||
|
||||
pub fn get_parser_mut(&mut self) -> &mut StringParser<'a> {
|
||||
&mut self.parser
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Result<char, Error> {
|
||||
self.parser.peek()
|
||||
}
|
||||
|
||||
pub fn skip_one(&mut self) -> Result<(), Error> {
|
||||
self.get_parser_mut().consume_one_ascii_or_all_non_ascii()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_peek_position(&self) -> usize {
|
||||
self.get_parser().get_peek_position()
|
||||
}
|
||||
|
||||
pub fn take_one(&mut self) -> Result<(), Error> {
|
||||
let chunks = self.parser.consume_one_ascii_or_all_non_ascii()?;
|
||||
for chunk in chunks {
|
||||
match chunk {
|
||||
Chunk::InvalidEncoding(invalid) => self.output.extend(invalid),
|
||||
Chunk::ValidSingleIntChar((_c, ni)) => self.output.push(ni),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn put_one_char(&mut self, c: char) {
|
||||
let os_str = OsString::from(c.to_string());
|
||||
self.put_string(os_str);
|
||||
}
|
||||
|
||||
pub fn put_string<S: AsRef<OsStr>>(&mut self, os_str: S) {
|
||||
let native = to_native_int_representation(os_str.as_ref());
|
||||
self.output.extend(native.deref());
|
||||
}
|
||||
|
||||
pub fn put_native_string(&mut self, n_str: &NativeIntStr) {
|
||||
self.output.extend(n_str);
|
||||
}
|
||||
|
||||
pub fn take_collected_output(&mut self) -> Vec<NativeCharInt> {
|
||||
mem::take(&mut self.output)
|
||||
}
|
||||
}
|
||||
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
//
|
||||
// spell-checker:ignore (words) splitted FFFD
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::{borrow::Cow, ffi::OsStr};
|
||||
|
||||
use crate::native_int_str::{
|
||||
from_native_int_representation, get_char_from_native_int, get_single_native_int_value,
|
||||
NativeCharInt, NativeIntStr,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Error {
|
||||
pub peek_position: usize,
|
||||
pub err_type: ErrorType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ErrorType {
|
||||
EndOfInput,
|
||||
InternalError,
|
||||
}
|
||||
|
||||
/// Provides a valid char or a invalid sequence of bytes.
|
||||
///
|
||||
/// Invalid byte sequences can't be splitted in any meaningful way.
|
||||
/// Thus, they need to be consumed as one piece.
|
||||
pub enum Chunk<'a> {
|
||||
InvalidEncoding(&'a NativeIntStr),
|
||||
ValidSingleIntChar((char, NativeCharInt)),
|
||||
}
|
||||
|
||||
/// This class makes parsing a OsString char by char more convenient.
|
||||
///
|
||||
/// It also allows to capturing of intermediate positions for later splitting.
|
||||
pub struct StringParser<'a> {
|
||||
input: &'a NativeIntStr,
|
||||
pointer: usize,
|
||||
remaining: &'a NativeIntStr,
|
||||
}
|
||||
|
||||
impl<'a> StringParser<'a> {
|
||||
pub fn new(input: &'a NativeIntStr) -> Self {
|
||||
let mut instance = Self {
|
||||
input,
|
||||
pointer: 0,
|
||||
remaining: input,
|
||||
};
|
||||
instance.set_pointer(0);
|
||||
instance
|
||||
}
|
||||
|
||||
pub fn new_at(input: &'a NativeIntStr, pos: usize) -> Self {
|
||||
let mut instance = Self::new(input);
|
||||
instance.set_pointer(pos);
|
||||
instance
|
||||
}
|
||||
|
||||
pub fn get_input(&self) -> &'a NativeIntStr {
|
||||
self.input
|
||||
}
|
||||
|
||||
pub fn get_peek_position(&self) -> usize {
|
||||
self.pointer
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Result<char, Error> {
|
||||
self.peek_char_at_pointer(self.pointer)
|
||||
}
|
||||
|
||||
fn make_err(&self, err_type: ErrorType) -> Error {
|
||||
Error {
|
||||
peek_position: self.get_peek_position(),
|
||||
err_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_char_at_pointer(&self, at_pointer: usize) -> Result<char, Error> {
|
||||
let split = self.input.split_at(at_pointer).1;
|
||||
if split.is_empty() {
|
||||
return Err(self.make_err(ErrorType::EndOfInput));
|
||||
}
|
||||
if let Some((c, _ni)) = get_char_from_native_int(split[0]) {
|
||||
Ok(c)
|
||||
} else {
|
||||
Ok('\u{FFFD}')
|
||||
}
|
||||
}
|
||||
|
||||
fn get_chunk_with_length_at(&self, pointer: usize) -> Result<(Chunk<'a>, usize), Error> {
|
||||
let (_before, after) = self.input.split_at(pointer);
|
||||
if after.is_empty() {
|
||||
return Err(self.make_err(ErrorType::EndOfInput));
|
||||
}
|
||||
|
||||
if let Some(c_ni) = get_char_from_native_int(after[0]) {
|
||||
Ok((Chunk::ValidSingleIntChar(c_ni), 1))
|
||||
} else {
|
||||
let mut i = 1;
|
||||
while i < after.len() {
|
||||
if let Some(_c) = get_char_from_native_int(after[i]) {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let chunk = &after[0..i];
|
||||
Ok((Chunk::InvalidEncoding(chunk), chunk.len()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_chunk(&self) -> Option<Chunk<'a>> {
|
||||
return self
|
||||
.get_chunk_with_length_at(self.pointer)
|
||||
.ok()
|
||||
.map(|(chunk, _)| chunk);
|
||||
}
|
||||
|
||||
pub fn consume_chunk(&mut self) -> Result<Chunk<'a>, Error> {
|
||||
let (chunk, len) = self.get_chunk_with_length_at(self.pointer)?;
|
||||
self.set_pointer(self.pointer + len);
|
||||
Ok(chunk)
|
||||
}
|
||||
|
||||
pub fn consume_one_ascii_or_all_non_ascii(&mut self) -> Result<Vec<Chunk<'a>>, Error> {
|
||||
let mut result = Vec::<Chunk<'a>>::new();
|
||||
loop {
|
||||
let data = self.consume_chunk()?;
|
||||
let was_ascii = if let Chunk::ValidSingleIntChar((c, _ni)) = &data {
|
||||
c.is_ascii()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
result.push(data);
|
||||
if was_ascii {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
match self.peek_chunk() {
|
||||
Some(Chunk::ValidSingleIntChar((c, _ni))) if c.is_ascii() => return Ok(result),
|
||||
None => return Ok(result),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn skip_multiple(&mut self, skip_byte_count: usize) {
|
||||
let end_ptr = self.pointer + skip_byte_count;
|
||||
self.set_pointer(end_ptr);
|
||||
}
|
||||
|
||||
pub fn skip_until_char_or_end(&mut self, c: char) {
|
||||
let native_rep = get_single_native_int_value(&c).unwrap();
|
||||
let pos = self.remaining.iter().position(|x| *x == native_rep);
|
||||
|
||||
if let Some(pos) = pos {
|
||||
self.set_pointer(self.pointer + pos);
|
||||
} else {
|
||||
self.set_pointer(self.input.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substring(&self, range: &std::ops::Range<usize>) -> &'a NativeIntStr {
|
||||
let (_before1, after1) = self.input.split_at(range.start);
|
||||
let (middle, _after2) = after1.split_at(range.end - range.start);
|
||||
middle
|
||||
}
|
||||
|
||||
pub fn peek_remaining(&self) -> Cow<'a, OsStr> {
|
||||
from_native_int_representation(Cow::Borrowed(self.remaining))
|
||||
}
|
||||
|
||||
pub fn set_pointer(&mut self, new_pointer: usize) {
|
||||
self.pointer = new_pointer;
|
||||
let (_before, after) = self.input.split_at(self.pointer);
|
||||
self.remaining = after;
|
||||
}
|
||||
}
|
||||
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{native_int_str::NativeIntStr, parse_error::ParseError, string_parser::StringParser};
|
||||
|
||||
pub struct VariableParser<'a, 'b> {
|
||||
pub parser: &'b mut StringParser<'a>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> VariableParser<'a, 'b> {
|
||||
fn get_current_char(&self) -> Option<char> {
|
||||
self.parser.peek().ok()
|
||||
}
|
||||
|
||||
fn check_variable_name_start(&self) -> Result<(), ParseError> {
|
||||
if let Some(c) = self.get_current_char() {
|
||||
if c.is_ascii_digit() {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: self.parser.get_peek_position(),
|
||||
msg: format!("Unexpected character: '{}', expected variable name must not start with 0..9", c) });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn skip_one(&mut self) -> Result<(), ParseError> {
|
||||
self.parser.consume_chunk()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_braced_variable_name(
|
||||
&mut self,
|
||||
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), ParseError> {
|
||||
let pos_start = self.parser.get_peek_position();
|
||||
|
||||
self.check_variable_name_start()?;
|
||||
|
||||
let (varname_end, default_end);
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: self.parser.get_peek_position(), msg: "Missing closing brace".into() })
|
||||
},
|
||||
Some(c) if !c.is_ascii() || c.is_ascii_alphanumeric() || c == '_' => {
|
||||
self.skip_one()?;
|
||||
}
|
||||
Some(':') => {
|
||||
varname_end = self.parser.get_peek_position();
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: self.parser.get_peek_position(),
|
||||
msg: "Missing closing brace after default value".into() })
|
||||
},
|
||||
Some('}') => {
|
||||
default_end = Some(self.parser.get_peek_position());
|
||||
self.skip_one()?;
|
||||
break
|
||||
},
|
||||
Some(_) => {
|
||||
self.skip_one()?;
|
||||
},
|
||||
}
|
||||
}
|
||||
break;
|
||||
},
|
||||
Some('}') => {
|
||||
varname_end = self.parser.get_peek_position();
|
||||
default_end = None;
|
||||
self.skip_one()?;
|
||||
break;
|
||||
},
|
||||
Some(c) => {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: self.parser.get_peek_position(),
|
||||
msg: format!("Unexpected character: '{}', expected a closing brace ('}}') or colon (':')", c)
|
||||
})
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let default_opt = if let Some(default_end) = default_end {
|
||||
Some(self.parser.substring(&Range {
|
||||
start: varname_end + 1,
|
||||
end: default_end,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let varname = self.parser.substring(&Range {
|
||||
start: pos_start,
|
||||
end: varname_end,
|
||||
});
|
||||
|
||||
Ok((varname, default_opt))
|
||||
}
|
||||
|
||||
fn parse_unbraced_variable_name(&mut self) -> Result<&'a NativeIntStr, ParseError> {
|
||||
let pos_start = self.parser.get_peek_position();
|
||||
|
||||
self.check_variable_name_start()?;
|
||||
|
||||
loop {
|
||||
match self.get_current_char() {
|
||||
None => break,
|
||||
Some(c) if c.is_ascii_alphanumeric() || c == '_' => {
|
||||
self.skip_one()?;
|
||||
}
|
||||
Some(_) => break,
|
||||
};
|
||||
}
|
||||
|
||||
let pos_end = self.parser.get_peek_position();
|
||||
|
||||
if pos_end == pos_start {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: pos_start,
|
||||
msg: "Missing variable name".into(),
|
||||
});
|
||||
}
|
||||
|
||||
let varname = self.parser.substring(&Range {
|
||||
start: pos_start,
|
||||
end: pos_end,
|
||||
});
|
||||
|
||||
Ok(varname)
|
||||
}
|
||||
|
||||
pub fn parse_variable(
|
||||
&mut self,
|
||||
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), ParseError> {
|
||||
self.skip_one()?;
|
||||
|
||||
let (name, default) = match self.get_current_char() {
|
||||
None => {
|
||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
||||
pos: self.parser.get_peek_position(),
|
||||
msg: "missing variable name".into(),
|
||||
})
|
||||
}
|
||||
Some('{') => {
|
||||
self.skip_one()?;
|
||||
self.parse_braced_variable_name()?
|
||||
}
|
||||
Some(_) => (self.parse_unbraced_variable_name()?, None),
|
||||
};
|
||||
|
||||
Ok((name, default))
|
||||
}
|
||||
}
|
||||
+950
-2
File diff suppressed because it is too large
Load Diff
+17
-7
@@ -15,7 +15,6 @@ use pretty_assertions::assert_eq;
|
||||
use rlimit::setrlimit;
|
||||
#[cfg(feature = "sleep")]
|
||||
use rstest::rstest;
|
||||
#[cfg(unix)]
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
#[cfg(not(windows))]
|
||||
@@ -352,6 +351,11 @@ impl CmdResult {
|
||||
std::str::from_utf8(&self.stderr).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the program's standard error as a string slice, automatically handling invalid utf8
|
||||
pub fn stderr_str_lossy(&self) -> Cow<'_, str> {
|
||||
String::from_utf8_lossy(&self.stderr)
|
||||
}
|
||||
|
||||
/// Returns the program's standard error as a string
|
||||
/// consumes self
|
||||
pub fn stderr_move_str(self) -> String {
|
||||
@@ -372,6 +376,14 @@ impl CmdResult {
|
||||
|
||||
#[track_caller]
|
||||
pub fn code_is(&self, expected_code: i32) -> &Self {
|
||||
let fails = self.code() != expected_code;
|
||||
if fails {
|
||||
eprintln!(
|
||||
"stdout:\n{}\nstderr:\n{}",
|
||||
self.stdout_str(),
|
||||
self.stderr_str()
|
||||
);
|
||||
}
|
||||
assert_eq!(self.code(), expected_code);
|
||||
self
|
||||
}
|
||||
@@ -395,10 +407,8 @@ impl CmdResult {
|
||||
pub fn success(&self) -> &Self {
|
||||
assert!(
|
||||
self.succeeded(),
|
||||
"Command was expected to succeed. Exit code: {}.\nstdout = {}\n stderr = {}",
|
||||
self.exit_status()
|
||||
.code()
|
||||
.map_or("n/a".to_string(), |code| code.to_string()),
|
||||
"Command was expected to succeed. code: {}\nstdout = {}\n stderr = {}",
|
||||
self.code(),
|
||||
self.stdout_str(),
|
||||
self.stderr_str()
|
||||
);
|
||||
@@ -2674,7 +2684,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
||||
let (stdout, stderr): (String, String) = if cfg!(target_os = "linux") {
|
||||
(
|
||||
result.stdout_str().to_string(),
|
||||
result.stderr_str().to_string(),
|
||||
result.stderr_str_lossy().to_string(),
|
||||
)
|
||||
} else {
|
||||
// `host_name_for` added prefix, strip 'g' prefix from results:
|
||||
@@ -2682,7 +2692,7 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
||||
let to = &from[1..];
|
||||
(
|
||||
result.stdout_str().replace(&from, to),
|
||||
result.stderr_str().replace(&from, to),
|
||||
result.stderr_str_lossy().replace(&from, to),
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
echo Hello Windows World!
|
||||
@@ -221,6 +221,8 @@ grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs -r sed -Ei
|
||||
# we should not regress our project just to match what GNU is going.
|
||||
# So, do some changes on the fly
|
||||
|
||||
patch -N -r - -d "$path_GNU" -p 1 -i "`realpath \"$path_UUTILS/util/gnu-patches/tests_env_env-S.pl.patch\"`" || true
|
||||
|
||||
sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh
|
||||
|
||||
sed -i -e "s|rm: cannot remove 'a/b'|rm: cannot remove 'a'|g" tests/rm/fail-2eperm.sh
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
diff --git a/tests/env/env-S.pl b/tests/env/env-S.pl
|
||||
index 710ca82cf..af7cf6efa 100755
|
||||
--- a/tests/env/env-S.pl
|
||||
+++ b/tests/env/env-S.pl
|
||||
@@ -209,27 +209,28 @@ my @Tests =
|
||||
{ERR=>"$prog: no terminating quote in -S string\n"}],
|
||||
['err5', q[-S'A=B\\q'], {EXIT=>125},
|
||||
{ERR=>"$prog: invalid sequence '\\q' in -S\n"}],
|
||||
- ['err6', q[-S'A=$B'], {EXIT=>125},
|
||||
- {ERR=>"$prog: only \${VARNAME} expansion is supported, error at: \$B\n"}],
|
||||
+ ['err6', q[-S'A=$B echo hello'], {EXIT=>0},
|
||||
+ {OUT=>"hello"}],
|
||||
['err7', q[-S'A=${B'], {EXIT=>125},
|
||||
- {ERR=>"$prog: only \${VARNAME} expansion is supported, " .
|
||||
- "error at: \${B\n"}],
|
||||
+ {ERR=>"$prog" . qq[: variable name issue (at 5): Missing closing brace\n]}],
|
||||
['err8', q[-S'A=${B%B}'], {EXIT=>125},
|
||||
- {ERR=>"$prog: only \${VARNAME} expansion is supported, " .
|
||||
- "error at: \${B%B}\n"}],
|
||||
+ {ERR=>"$prog" . qq[: variable name issue (at 5): Unexpected character: '%', expected a closing brace ('}') or colon (':')\n]}],
|
||||
['err9', q[-S'A=${9B}'], {EXIT=>125},
|
||||
- {ERR=>"$prog: only \${VARNAME} expansion is supported, " .
|
||||
- "error at: \${9B}\n"}],
|
||||
+ {ERR=>"$prog" . qq[: variable name issue (at 4): Unexpected character: '9', expected variable name must not start with 0..9\n]}],
|
||||
|
||||
# Test incorrect shebang usage (extraneous whitespace).
|
||||
['err_sp2', q['-v -S cat -n'], {EXIT=>125},
|
||||
- {ERR=>"env: invalid option -- ' '\n" .
|
||||
- "env: use -[v]S to pass options in shebang lines\n" .
|
||||
- "Try 'env --help' for more information.\n"}],
|
||||
+ {ERR=>"$prog: error: unexpected argument '- ' found\n\n" .
|
||||
+ " tip: to pass '- ' as a value, use '-- - '\n\n" .
|
||||
+ "Usage: $prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n\n" .
|
||||
+ "For more information, try '--help'.\n" .
|
||||
+ "$prog: use -[v]S to pass options in shebang lines\n"}],
|
||||
['err_sp3', q['-v -S cat -n'], {EXIT=>125}, # embedded tab after -v
|
||||
- {ERR=>"env: invalid option -- '\t'\n" .
|
||||
- "env: use -[v]S to pass options in shebang lines\n" .
|
||||
- "Try 'env --help' for more information.\n"}],
|
||||
+ {ERR=>"$prog: error: unexpected argument '-\t' found\n\n" .
|
||||
+ " tip: to pass '-\t' as a value, use '-- -\t'\n\n" .
|
||||
+ "Usage: $prog [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n\n" .
|
||||
+ "For more information, try '--help'.\n" .
|
||||
+ "$prog: use -[v]S to pass options in shebang lines\n"}],
|
||||
|
||||
# Also diagnose incorrect shebang usage when failing to exec.
|
||||
# This typically happens with:
|
||||
Reference in New Issue
Block a user