mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
env: Move to "thiserror" + added errors test case (#7584)
Solved Issue #7535 : Removed parse_errors to follow other commands standard with thiserror
This commit is contained in:
parent
903fa6ae88
commit
aea23408fd
7 changed files with 231 additions and 195 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2727,6 +2727,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"nix",
|
"nix",
|
||||||
"rust-ini",
|
"rust-ini",
|
||||||
|
"thiserror 2.0.12",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
1
src/uu/env/Cargo.toml
vendored
1
src/uu/env/Cargo.toml
vendored
|
@ -19,6 +19,7 @@ path = "src/env.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
rust-ini = { workspace = true }
|
rust-ini = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["signals"] }
|
uucore = { workspace = true, features = ["signals"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
|
129
src/uu/env/src/env.rs
vendored
129
src/uu/env/src/env.rs
vendored
|
@ -6,7 +6,6 @@
|
||||||
// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction
|
// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction
|
||||||
|
|
||||||
pub mod native_int_str;
|
pub mod native_int_str;
|
||||||
pub mod parse_error;
|
|
||||||
pub mod split_iterator;
|
pub mod split_iterator;
|
||||||
pub mod string_expander;
|
pub mod string_expander;
|
||||||
pub mod string_parser;
|
pub mod string_parser;
|
||||||
|
@ -40,6 +39,42 @@ use uucore::line_ending::LineEnding;
|
||||||
use uucore::signals::signal_by_name_or_value;
|
use uucore::signals::signal_by_name_or_value;
|
||||||
use uucore::{format_usage, help_about, help_section, help_usage, show_warning};
|
use uucore::{format_usage, help_about, help_section, help_usage, show_warning};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error, PartialEq)]
|
||||||
|
pub enum EnvError {
|
||||||
|
#[error("no terminating quote in -S string")]
|
||||||
|
EnvMissingClosingQuote(usize, char),
|
||||||
|
#[error("invalid backslash at end of string in -S")]
|
||||||
|
EnvInvalidBackslashAtEndOfStringInMinusS(usize, String),
|
||||||
|
#[error("'\\c' must not appear in double-quoted -S string")]
|
||||||
|
EnvBackslashCNotAllowedInDoubleQuotes(usize),
|
||||||
|
#[error("invalid sequence '\\{}' in -S",.1)]
|
||||||
|
EnvInvalidSequenceBackslashXInMinusS(usize, char),
|
||||||
|
#[error("Missing closing brace")]
|
||||||
|
EnvParsingOfVariableMissingClosingBrace(usize),
|
||||||
|
#[error("Missing variable name")]
|
||||||
|
EnvParsingOfMissingVariable(usize),
|
||||||
|
#[error("Missing closing brace after default value at {}",.0)]
|
||||||
|
EnvParsingOfVariableMissingClosingBraceAfterValue(usize),
|
||||||
|
#[error("Unexpected character: '{}', expected variable name must not start with 0..9",.1)]
|
||||||
|
EnvParsingOfVariableUnexpectedNumber(usize, String),
|
||||||
|
#[error("Unexpected character: '{}', expected a closing brace ('}}') or colon (':')",.1)]
|
||||||
|
EnvParsingOfVariableExceptedBraceOrColon(usize, String),
|
||||||
|
#[error("")]
|
||||||
|
EnvReachedEnd,
|
||||||
|
#[error("")]
|
||||||
|
EnvContinueWithDelimiter,
|
||||||
|
#[error("{}{:?}",.0,.1)]
|
||||||
|
EnvInternalError(usize, string_parser::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<string_parser::Error> for EnvError {
|
||||||
|
fn from(value: string_parser::Error) -> Self {
|
||||||
|
EnvError::EnvInternalError(value.peek_position, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ABOUT: &str = help_about!("env.md");
|
const ABOUT: &str = help_about!("env.md");
|
||||||
const USAGE: &str = help_usage!("env.md");
|
const USAGE: &str = help_usage!("env.md");
|
||||||
const AFTER_HELP: &str = help_section!("after help", "env.md");
|
const AFTER_HELP: &str = help_section!("after help", "env.md");
|
||||||
|
@ -273,20 +308,28 @@ pub fn uu_app() -> Command {
|
||||||
|
|
||||||
pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>> {
|
pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>> {
|
||||||
split_iterator::split(text).map_err(|e| match e {
|
split_iterator::split(text).map_err(|e| match e {
|
||||||
parse_error::ParseError::BackslashCNotAllowedInDoubleQuotes { pos: _ } => {
|
EnvError::EnvBackslashCNotAllowedInDoubleQuotes(_) => USimpleError::new(125, e.to_string()),
|
||||||
USimpleError::new(125, "'\\c' must not appear in double-quoted -S string")
|
EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(_, _) => {
|
||||||
|
USimpleError::new(125, e.to_string())
|
||||||
}
|
}
|
||||||
parse_error::ParseError::InvalidBackslashAtEndOfStringInMinusS { pos: _, quoting: _ } => {
|
EnvError::EnvInvalidSequenceBackslashXInMinusS(_, _) => {
|
||||||
USimpleError::new(125, "invalid backslash at end of string in -S")
|
USimpleError::new(125, e.to_string())
|
||||||
}
|
}
|
||||||
parse_error::ParseError::InvalidSequenceBackslashXInMinusS { pos: _, c } => {
|
EnvError::EnvMissingClosingQuote(_, _) => USimpleError::new(125, e.to_string()),
|
||||||
USimpleError::new(125, format!("invalid sequence '\\{c}' in -S"))
|
EnvError::EnvParsingOfVariableMissingClosingBrace(pos) => {
|
||||||
|
USimpleError::new(125, format!("variable name issue (at {pos}): {}", e))
|
||||||
}
|
}
|
||||||
parse_error::ParseError::MissingClosingQuote { pos: _, c: _ } => {
|
EnvError::EnvParsingOfMissingVariable(pos) => {
|
||||||
USimpleError::new(125, "no terminating quote in -S string")
|
USimpleError::new(125, format!("variable name issue (at {pos}): {}", e))
|
||||||
}
|
}
|
||||||
parse_error::ParseError::ParsingOfVariableNameFailed { pos, msg } => {
|
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(pos) => {
|
||||||
USimpleError::new(125, format!("variable name issue (at {pos}): {msg}",))
|
USimpleError::new(125, format!("variable name issue (at {pos}): {}", e))
|
||||||
|
}
|
||||||
|
EnvError::EnvParsingOfVariableUnexpectedNumber(pos, _) => {
|
||||||
|
USimpleError::new(125, format!("variable name issue (at {pos}): {}", e))
|
||||||
|
}
|
||||||
|
EnvError::EnvParsingOfVariableExceptedBraceOrColon(pos, _) => {
|
||||||
|
USimpleError::new(125, format!("variable name issue (at {pos}): {}", e))
|
||||||
}
|
}
|
||||||
_ => USimpleError::new(125, format!("Error: {e:?}")),
|
_ => USimpleError::new(125, format!("Error: {e:?}")),
|
||||||
})
|
})
|
||||||
|
@ -771,4 +814,68 @@ mod tests {
|
||||||
parse_args_from_str(&NCvt::convert(r#"-i A='B \' C'"#)).unwrap()
|
parse_args_from_str(&NCvt::convert(r#"-i A='B \' C'"#)).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_cases() {
|
||||||
|
// Test EnvBackslashCNotAllowedInDoubleQuotes
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"sh -c "echo \c""#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"'\\c' must not appear in double-quoted -S string"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test EnvInvalidBackslashAtEndOfStringInMinusS
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"sh -c "echo \"#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"no terminating quote in -S string"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test EnvInvalidSequenceBackslashXInMinusS
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"sh -c "echo \x""#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("invalid sequence '\\x' in -S")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test EnvMissingClosingQuote
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"sh -c "echo "#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
"no terminating quote in -S string"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test variable-related errors
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"echo ${FOO"#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("variable name issue (at 10): Missing closing brace")
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"echo ${FOO:-value"#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(
|
||||||
|
result
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("variable name issue (at 17): Missing closing brace after default value")
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"echo ${1FOO}"#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("variable name issue (at 7): Unexpected character: '1', expected variable name must not start with 0..9"));
|
||||||
|
|
||||||
|
let result = parse_args_from_str(&NCvt::convert(r#"echo ${FOO?}"#));
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().to_string().contains("variable name issue (at 10): Unexpected character: '?', expected a closing brace ('}') or colon (':')"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/uu/env/src/parse_error.rs
vendored
55
src/uu/env/src/parse_error.rs
vendored
|
@ -1,55 +0,0 @@
|
||||||
// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
110
src/uu/env/src/split_iterator.rs
vendored
110
src/uu/env/src/split_iterator.rs
vendored
|
@ -20,11 +20,11 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::EnvError;
|
||||||
use crate::native_int_str::NativeCharInt;
|
use crate::native_int_str::NativeCharInt;
|
||||||
use crate::native_int_str::NativeIntStr;
|
use crate::native_int_str::NativeIntStr;
|
||||||
use crate::native_int_str::NativeIntString;
|
use crate::native_int_str::NativeIntString;
|
||||||
use crate::native_int_str::from_native_int_representation;
|
use crate::native_int_str::from_native_int_representation;
|
||||||
use crate::parse_error::ParseError;
|
|
||||||
use crate::string_expander::StringExpander;
|
use crate::string_expander::StringExpander;
|
||||||
use crate::string_parser::StringParser;
|
use crate::string_parser::StringParser;
|
||||||
use crate::variable_parser::VariableParser;
|
use crate::variable_parser::VariableParser;
|
||||||
|
@ -62,14 +62,14 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_one(&mut self) -> Result<(), ParseError> {
|
fn skip_one(&mut self) -> Result<(), EnvError> {
|
||||||
self.expander
|
self.expander
|
||||||
.get_parser_mut()
|
.get_parser_mut()
|
||||||
.consume_one_ascii_or_all_non_ascii()?;
|
.consume_one_ascii_or_all_non_ascii()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_one(&mut self) -> Result<(), ParseError> {
|
fn take_one(&mut self) -> Result<(), EnvError> {
|
||||||
Ok(self.expander.take_one()?)
|
Ok(self.expander.take_one()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ impl<'a> SplitIterator<'a> {
|
||||||
self.expander.get_parser_mut()
|
self.expander.get_parser_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn substitute_variable<'x>(&'x mut self) -> Result<(), ParseError> {
|
fn substitute_variable<'x>(&'x mut self) -> Result<(), EnvError> {
|
||||||
let mut var_parse = VariableParser::<'a, '_> {
|
let mut var_parse = VariableParser::<'a, '_> {
|
||||||
parser: self.get_parser_mut(),
|
parser: self.get_parser_mut(),
|
||||||
};
|
};
|
||||||
|
@ -116,7 +116,7 @@ impl<'a> SplitIterator<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_and_replace_ascii_escape_code(&mut self, c: char) -> Result<bool, ParseError> {
|
fn check_and_replace_ascii_escape_code(&mut self, c: char) -> Result<bool, EnvError> {
|
||||||
if let Some(replace) = REPLACEMENTS.iter().find(|&x| x.0 == c) {
|
if let Some(replace) = REPLACEMENTS.iter().find(|&x| x.0 == c) {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
self.push_char_to_word(replace.1);
|
self.push_char_to_word(replace.1);
|
||||||
|
@ -126,24 +126,24 @@ impl<'a> SplitIterator<'a> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_invalid_sequence_backslash_xin_minus_s(&self, c: char) -> ParseError {
|
fn make_invalid_sequence_backslash_xin_minus_s(&self, c: char) -> EnvError {
|
||||||
ParseError::InvalidSequenceBackslashXInMinusS {
|
EnvError::EnvInvalidSequenceBackslashXInMinusS(
|
||||||
pos: self.expander.get_parser().get_peek_position(),
|
self.expander.get_parser().get_peek_position(),
|
||||||
c,
|
c,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_root(&mut self) -> Result<(), ParseError> {
|
fn state_root(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.state_delimiter() {
|
match self.state_delimiter() {
|
||||||
Err(ParseError::ContinueWithDelimiter) => {}
|
Err(EnvError::EnvContinueWithDelimiter) => {}
|
||||||
Err(ParseError::ReachedEnd) => return Ok(()),
|
Err(EnvError::EnvReachedEnd) => return Ok(()),
|
||||||
result => return result,
|
result => return result,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_delimiter(&mut self) -> Result<(), ParseError> {
|
fn state_delimiter(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
|
@ -166,12 +166,12 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_delimiter_backslash(&mut self) -> Result<(), ParseError> {
|
fn state_delimiter_backslash(&mut self) -> Result<(), EnvError> {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
None => Err(EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
quoting: "Delimiter".into(),
|
"Delimiter".into(),
|
||||||
}),
|
)),
|
||||||
Some('_') | Some(NEW_LINE) => {
|
Some('_') | Some(NEW_LINE) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -181,18 +181,18 @@ impl<'a> SplitIterator<'a> {
|
||||||
self.take_one()?;
|
self.take_one()?;
|
||||||
self.state_unquoted()
|
self.state_unquoted()
|
||||||
}
|
}
|
||||||
Some('c') => Err(ParseError::ReachedEnd),
|
Some('c') => Err(EnvError::EnvReachedEnd),
|
||||||
Some(c) if self.check_and_replace_ascii_escape_code(c)? => self.state_unquoted(),
|
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)),
|
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_unquoted(&mut self) -> Result<(), ParseError> {
|
fn state_unquoted(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
self.push_word_to_words();
|
self.push_word_to_words();
|
||||||
return Err(ParseError::ReachedEnd);
|
return Err(EnvError::EnvReachedEnd);
|
||||||
}
|
}
|
||||||
Some(DOLLAR) => {
|
Some(DOLLAR) => {
|
||||||
self.substitute_variable()?;
|
self.substitute_variable()?;
|
||||||
|
@ -221,12 +221,12 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_unquoted_backslash(&mut self) -> Result<(), ParseError> {
|
fn state_unquoted_backslash(&mut self) -> Result<(), EnvError> {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
None => Err(EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
quoting: "Unquoted".into(),
|
"Unquoted".into(),
|
||||||
}),
|
)),
|
||||||
Some(NEW_LINE) => {
|
Some(NEW_LINE) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -234,11 +234,11 @@ impl<'a> SplitIterator<'a> {
|
||||||
Some('_') => {
|
Some('_') => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
self.push_word_to_words();
|
self.push_word_to_words();
|
||||||
Err(ParseError::ContinueWithDelimiter)
|
Err(EnvError::EnvContinueWithDelimiter)
|
||||||
}
|
}
|
||||||
Some('c') => {
|
Some('c') => {
|
||||||
self.push_word_to_words();
|
self.push_word_to_words();
|
||||||
Err(ParseError::ReachedEnd)
|
Err(EnvError::EnvReachedEnd)
|
||||||
}
|
}
|
||||||
Some(DOLLAR) | Some(BACKSLASH) | Some(SINGLE_QUOTES) | Some(DOUBLE_QUOTES) => {
|
Some(DOLLAR) | Some(BACKSLASH) | Some(SINGLE_QUOTES) | Some(DOUBLE_QUOTES) => {
|
||||||
self.take_one()?;
|
self.take_one()?;
|
||||||
|
@ -249,14 +249,14 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_single_quoted(&mut self) -> Result<(), ParseError> {
|
fn state_single_quoted(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::MissingClosingQuote {
|
return Err(EnvError::EnvMissingClosingQuote(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
c: '\'',
|
'\'',
|
||||||
});
|
));
|
||||||
}
|
}
|
||||||
Some(SINGLE_QUOTES) => {
|
Some(SINGLE_QUOTES) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
|
@ -273,12 +273,12 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_single_quoted_backslash(&mut self) -> Result<(), ParseError> {
|
fn split_single_quoted_backslash(&mut self) -> Result<(), EnvError> {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => Err(ParseError::MissingClosingQuote {
|
None => Err(EnvError::EnvMissingClosingQuote(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
c: '\'',
|
'\'',
|
||||||
}),
|
)),
|
||||||
Some(NEW_LINE) => {
|
Some(NEW_LINE) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -299,14 +299,14 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_double_quoted(&mut self) -> Result<(), ParseError> {
|
fn state_double_quoted(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::MissingClosingQuote {
|
return Err(EnvError::EnvMissingClosingQuote(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
c: '"',
|
'"',
|
||||||
});
|
));
|
||||||
}
|
}
|
||||||
Some(DOLLAR) => {
|
Some(DOLLAR) => {
|
||||||
self.substitute_variable()?;
|
self.substitute_variable()?;
|
||||||
|
@ -326,12 +326,12 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_double_quoted_backslash(&mut self) -> Result<(), ParseError> {
|
fn state_double_quoted_backslash(&mut self) -> Result<(), EnvError> {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => Err(ParseError::MissingClosingQuote {
|
None => Err(EnvError::EnvMissingClosingQuote(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
c: '"',
|
'"',
|
||||||
}),
|
)),
|
||||||
Some(NEW_LINE) => {
|
Some(NEW_LINE) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -340,18 +340,18 @@ impl<'a> SplitIterator<'a> {
|
||||||
self.take_one()?;
|
self.take_one()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some('c') => Err(ParseError::BackslashCNotAllowedInDoubleQuotes {
|
Some('c') => Err(EnvError::EnvBackslashCNotAllowedInDoubleQuotes(
|
||||||
pos: self.get_parser().get_peek_position(),
|
self.get_parser().get_peek_position(),
|
||||||
}),
|
)),
|
||||||
Some(c) if self.check_and_replace_ascii_escape_code(c)? => 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)),
|
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state_comment(&mut self) -> Result<(), ParseError> {
|
fn state_comment(&mut self) -> Result<(), EnvError> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => return Err(ParseError::ReachedEnd),
|
None => return Err(EnvError::EnvReachedEnd),
|
||||||
Some(NEW_LINE) => {
|
Some(NEW_LINE) => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -363,13 +363,13 @@ impl<'a> SplitIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split(mut self) -> Result<Vec<NativeIntString>, ParseError> {
|
pub fn split(mut self) -> Result<Vec<NativeIntString>, EnvError> {
|
||||||
self.state_root()?;
|
self.state_root()?;
|
||||||
Ok(self.words)
|
Ok(self.words)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn split(s: &NativeIntStr) -> Result<Vec<NativeIntString>, ParseError> {
|
pub fn split(s: &NativeIntStr) -> Result<Vec<NativeIntString>, EnvError> {
|
||||||
let split_args = SplitIterator::new(s).split()?;
|
let split_args = SplitIterator::new(s).split()?;
|
||||||
Ok(split_args)
|
Ok(split_args)
|
||||||
}
|
}
|
||||||
|
|
61
src/uu/env/src/variable_parser.rs
vendored
61
src/uu/env/src/variable_parser.rs
vendored
|
@ -5,7 +5,8 @@
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{native_int_str::NativeIntStr, parse_error::ParseError, string_parser::StringParser};
|
use crate::EnvError;
|
||||||
|
use crate::{native_int_str::NativeIntStr, string_parser::StringParser};
|
||||||
|
|
||||||
pub struct VariableParser<'a, 'b> {
|
pub struct VariableParser<'a, 'b> {
|
||||||
pub parser: &'b mut StringParser<'a>,
|
pub parser: &'b mut StringParser<'a>,
|
||||||
|
@ -16,28 +17,26 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
self.parser.peek().ok()
|
self.parser.peek().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_variable_name_start(&self) -> Result<(), ParseError> {
|
fn check_variable_name_start(&self) -> Result<(), EnvError> {
|
||||||
if let Some(c) = self.get_current_char() {
|
if let Some(c) = self.get_current_char() {
|
||||||
if c.is_ascii_digit() {
|
if c.is_ascii_digit() {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(EnvError::EnvParsingOfVariableUnexpectedNumber(
|
||||||
pos: self.parser.get_peek_position(),
|
self.parser.get_peek_position(),
|
||||||
msg: format!(
|
c.to_string(),
|
||||||
"Unexpected character: '{c}', expected variable name must not start with 0..9"
|
));
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_one(&mut self) -> Result<(), ParseError> {
|
fn skip_one(&mut self) -> Result<(), EnvError> {
|
||||||
self.parser.consume_chunk()?;
|
self.parser.consume_chunk()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_braced_variable_name(
|
fn parse_braced_variable_name(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), ParseError> {
|
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> {
|
||||||
let pos_start = self.parser.get_peek_position();
|
let pos_start = self.parser.get_peek_position();
|
||||||
|
|
||||||
self.check_variable_name_start()?;
|
self.check_variable_name_start()?;
|
||||||
|
@ -46,10 +45,9 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(EnvError::EnvParsingOfVariableMissingClosingBrace(
|
||||||
pos: self.parser.get_peek_position(),
|
self.parser.get_peek_position(),
|
||||||
msg: "Missing closing brace".into(),
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Some(c) if !c.is_ascii() || c.is_ascii_alphanumeric() || c == '_' => {
|
Some(c) if !c.is_ascii() || c.is_ascii_alphanumeric() || c == '_' => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
|
@ -59,10 +57,11 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
loop {
|
loop {
|
||||||
match self.get_current_char() {
|
match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(
|
||||||
pos: self.parser.get_peek_position(),
|
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(
|
||||||
msg: "Missing closing brace after default value".into(),
|
self.parser.get_peek_position(),
|
||||||
});
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Some('}') => {
|
Some('}') => {
|
||||||
default_end = Some(self.parser.get_peek_position());
|
default_end = Some(self.parser.get_peek_position());
|
||||||
|
@ -83,12 +82,10 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(EnvError::EnvParsingOfVariableExceptedBraceOrColon(
|
||||||
pos: self.parser.get_peek_position(),
|
self.parser.get_peek_position(),
|
||||||
msg: format!(
|
c.to_string(),
|
||||||
"Unexpected character: '{c}', expected a closing brace ('}}') or colon (':')"
|
));
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -110,7 +107,7 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
Ok((varname, default_opt))
|
Ok((varname, default_opt))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_unbraced_variable_name(&mut self) -> Result<&'a NativeIntStr, ParseError> {
|
fn parse_unbraced_variable_name(&mut self) -> Result<&'a NativeIntStr, EnvError> {
|
||||||
let pos_start = self.parser.get_peek_position();
|
let pos_start = self.parser.get_peek_position();
|
||||||
|
|
||||||
self.check_variable_name_start()?;
|
self.check_variable_name_start()?;
|
||||||
|
@ -128,10 +125,7 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
let pos_end = self.parser.get_peek_position();
|
let pos_end = self.parser.get_peek_position();
|
||||||
|
|
||||||
if pos_end == pos_start {
|
if pos_end == pos_start {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(EnvError::EnvParsingOfMissingVariable(pos_start));
|
||||||
pos: pos_start,
|
|
||||||
msg: "Missing variable name".into(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let varname = self.parser.substring(&Range {
|
let varname = self.parser.substring(&Range {
|
||||||
|
@ -144,15 +138,14 @@ impl<'a> VariableParser<'a, '_> {
|
||||||
|
|
||||||
pub fn parse_variable(
|
pub fn parse_variable(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), ParseError> {
|
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
|
|
||||||
let (name, default) = match self.get_current_char() {
|
let (name, default) = match self.get_current_char() {
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::ParsingOfVariableNameFailed {
|
return Err(EnvError::EnvParsingOfMissingVariable(
|
||||||
pos: self.parser.get_peek_position(),
|
self.parser.get_peek_position(),
|
||||||
msg: "missing variable name".into(),
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Some('{') => {
|
Some('{') => {
|
||||||
self.skip_one()?;
|
self.skip_one()?;
|
||||||
|
|
|
@ -1017,10 +1017,12 @@ mod tests_split_iterator {
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use env::native_int_str::{Convert, NCvt, from_native_int_representation_owned};
|
use env::{
|
||||||
use env::parse_error::ParseError;
|
EnvError,
|
||||||
|
native_int_str::{Convert, NCvt, from_native_int_representation_owned},
|
||||||
|
};
|
||||||
|
|
||||||
fn split(input: &str) -> Result<Vec<OsString>, ParseError> {
|
fn split(input: &str) -> Result<Vec<OsString>, EnvError> {
|
||||||
::env::split_iterator::split(&NCvt::convert(input)).map(|vec| {
|
::env::split_iterator::split(&NCvt::convert(input)).map(|vec| {
|
||||||
vec.into_iter()
|
vec.into_iter()
|
||||||
.map(from_native_int_representation_owned)
|
.map(from_native_int_representation_owned)
|
||||||
|
@ -1127,24 +1129,24 @@ mod tests_split_iterator {
|
||||||
fn split_trailing_backslash() {
|
fn split_trailing_backslash() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("\\"),
|
split("\\"),
|
||||||
Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
Err(EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(
|
||||||
pos: 1,
|
1,
|
||||||
quoting: "Delimiter".into()
|
"Delimiter".into()
|
||||||
})
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split(" \\"),
|
split(" \\"),
|
||||||
Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
Err(EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(
|
||||||
pos: 2,
|
2,
|
||||||
quoting: "Delimiter".into()
|
"Delimiter".into()
|
||||||
})
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("a\\"),
|
split("a\\"),
|
||||||
Err(ParseError::InvalidBackslashAtEndOfStringInMinusS {
|
Err(EnvError::EnvInvalidBackslashAtEndOfStringInMinusS(
|
||||||
pos: 2,
|
2,
|
||||||
quoting: "Unquoted".into()
|
"Unquoted".into()
|
||||||
})
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1152,26 +1154,14 @@ mod tests_split_iterator {
|
||||||
fn split_errors() {
|
fn split_errors() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("'abc"),
|
split("'abc"),
|
||||||
Err(ParseError::MissingClosingQuote { pos: 4, c: '\'' })
|
Err(EnvError::EnvMissingClosingQuote(4, '\''))
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
split("\""),
|
|
||||||
Err(ParseError::MissingClosingQuote { pos: 1, c: '"' })
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
split("'\\"),
|
|
||||||
Err(ParseError::MissingClosingQuote { pos: 2, c: '\'' })
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
split("'\\"),
|
|
||||||
Err(ParseError::MissingClosingQuote { pos: 2, c: '\'' })
|
|
||||||
);
|
);
|
||||||
|
assert_eq!(split("\""), Err(EnvError::EnvMissingClosingQuote(1, '"')));
|
||||||
|
assert_eq!(split("'\\"), Err(EnvError::EnvMissingClosingQuote(2, '\'')));
|
||||||
|
assert_eq!(split("'\\"), Err(EnvError::EnvMissingClosingQuote(2, '\'')));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split(r#""$""#),
|
split(r#""$""#),
|
||||||
Err(ParseError::ParsingOfVariableNameFailed {
|
Err(EnvError::EnvParsingOfMissingVariable(2)),
|
||||||
pos: 2,
|
|
||||||
msg: "Missing variable name".into()
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,26 +1169,25 @@ mod tests_split_iterator {
|
||||||
fn split_error_fail_with_unknown_escape_sequences() {
|
fn split_error_fail_with_unknown_escape_sequences() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("\\a"),
|
split("\\a"),
|
||||||
Err(ParseError::InvalidSequenceBackslashXInMinusS { pos: 1, c: 'a' })
|
Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(1, 'a'))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("\"\\a\""),
|
split("\"\\a\""),
|
||||||
Err(ParseError::InvalidSequenceBackslashXInMinusS { pos: 2, c: 'a' })
|
Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a'))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split("'\\a'"),
|
split("'\\a'"),
|
||||||
Err(ParseError::InvalidSequenceBackslashXInMinusS { pos: 2, c: 'a' })
|
Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a'))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split(r#""\a""#),
|
split(r#""\a""#),
|
||||||
Err(ParseError::InvalidSequenceBackslashXInMinusS { pos: 2, c: 'a' })
|
Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a'))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
split(r"\🦉"),
|
split(r"\🦉"),
|
||||||
Err(ParseError::InvalidSequenceBackslashXInMinusS {
|
Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(
|
||||||
pos: 1,
|
1, '\u{FFFD}'
|
||||||
c: '\u{FFFD}'
|
))
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue