Skip to content

Commit 7bf3de8

Browse files
committed
implement properly
1 parent 4406bd5 commit 7bf3de8

File tree

2 files changed

+17
-67
lines changed

2 files changed

+17
-67
lines changed

src/parser.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -753,30 +753,35 @@ fn parse_word_parts(
753753
or7(
754754
parse_special_shell_var,
755755
parse_escaped_dollar_sign,
756+
parse_escaped_char('~'),
756757
parse_escaped_char('`'),
757758
parse_escaped_char('"'),
758759
parse_escaped_char('('),
759-
parse_escaped_char(')'),
760-
if_true(parse_escaped_char('\''), move |_| {
761-
mode == ParseWordPartsMode::DoubleQuotes
762-
}),
760+
or(
761+
parse_escaped_char(')'),
762+
if_true(parse_escaped_char('\''), move |_| {
763+
mode == ParseWordPartsMode::DoubleQuotes
764+
}),
765+
)
763766
)
764767
}
765768

766769
move |input| {
767770
enum PendingPart<'a> {
768771
Char(char),
769772
Variable(&'a str),
773+
Tilde,
770774
Command(SequentialList),
771775
Parts(Vec<WordPart>),
772776
}
773777

774778
let (input, parts) = many0(or7(
775-
or(
779+
or3(
776780
map(tag("$?"), |_| PendingPart::Variable("?")),
777781
map(first_escaped_char(mode), PendingPart::Char),
782+
map(parse_command_substitution, PendingPart::Command),
778783
),
779-
map(parse_command_substitution, PendingPart::Command),
784+
map(ch('~'), |_| PendingPart::Tilde),
780785
map(preceded(ch('$'), parse_env_var_name), PendingPart::Variable),
781786
|input| {
782787
let (_, _) = ch('`')(input)?;
@@ -796,7 +801,7 @@ fn parse_word_parts(
796801
if_true(next_char, |&c| match mode {
797802
ParseWordPartsMode::DoubleQuotes => c != '"',
798803
ParseWordPartsMode::Unquoted => {
799-
!c.is_whitespace() && !"(){}<>|&;\"'".contains(c)
804+
!c.is_whitespace() && !"~(){}<>|&;\"'".contains(c)
800805
}
801806
}),
802807
PendingPart::Char,
@@ -823,6 +828,7 @@ fn parse_word_parts(
823828
result.push(WordPart::Text(c.to_string()));
824829
}
825830
}
831+
PendingPart::Tilde => result.push(WordPart::Tilde),
826832
PendingPart::Command(s) => result.push(WordPart::Command(s)),
827833
PendingPart::Variable(v) => {
828834
result.push(WordPart::Variable(v.to_string()))

src/shell/execute.rs

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// Copyright 2018-2024 the Deno authors. MIT license.
22

3-
use std::borrow::Cow;
4-
use std::cell::OnceCell;
53
use std::collections::HashMap;
64
use std::ffi::OsStr;
75
use std::ffi::OsString;
@@ -752,8 +750,6 @@ pub enum EvaluateWordTextError {
752750
NotUtf8Pattern { part: OsString },
753751
#[error("glob: no matches found '{}'", pattern)]
754752
NoFilesMatched { pattern: String },
755-
#[error("tilde expansion requires valid utf-8 (text: '{}')", lossy_text)]
756-
TildeExpansionInvalidUtf8 { lossy_text: String },
757753
#[error("invalid utf-8: {}", err)]
758754
InvalidUtf8 {
759755
#[from]
@@ -800,13 +796,9 @@ fn evaluate_word_parts(
800796

801797
fn evaluate_word_text(
802798
state: &ShellState,
803-
mut text_parts: Vec<TextPart>,
799+
text_parts: Vec<TextPart>,
804800
is_quoted: bool,
805801
) -> Result<Vec<OsString>, EvaluateWordTextError> {
806-
if !is_quoted {
807-
tilde_expand_text_parts(state, &mut text_parts)?;
808-
}
809-
810802
if !is_quoted
811803
&& text_parts
812804
.iter()
@@ -894,57 +886,6 @@ fn evaluate_word_parts(
894886
}
895887
}
896888

897-
fn tilde_expand_text_parts(
898-
state: &ShellState,
899-
text_parts: &mut Vec<TextPart>,
900-
) -> Result<(), EvaluateWordTextError> {
901-
let lazy_home_dir: OnceCell<PathBuf> = OnceCell::new();
902-
// tilde expansion
903-
for part in text_parts {
904-
match part {
905-
TextPart::Text(os_string) => {
906-
let text = os_string.to_string_lossy();
907-
let mut was_last_escape = false;
908-
let mut new_text = OsString::new();
909-
let mut last_index = 0;
910-
for (index, c) in text.char_indices() {
911-
match c {
912-
'\\' => {
913-
was_last_escape = true;
914-
}
915-
'~' if was_last_escape => {
916-
new_text.push(&text[last_index..index - 1]);
917-
new_text.push("~");
918-
last_index = index + 1;
919-
}
920-
'~' if !was_last_escape => {
921-
if let Cow::Owned(text) = text {
922-
return Err(EvaluateWordTextError::TildeExpansionInvalidUtf8 { lossy_text: text });
923-
}
924-
new_text.push(&text[last_index..index]);
925-
last_index = index + 1;
926-
// push the home dir
927-
let home_dir = lazy_home_dir.get_or_init(||sys_traits::impls::real_home_dir_with_env(state).unwrap_or_else(|| PathBuf::from("~")));
928-
new_text.push(home_dir);
929-
}
930-
_ => {
931-
was_last_escape = false;
932-
}
933-
}
934-
}
935-
if last_index > 0 {
936-
new_text.push(&text[last_index..]);
937-
*os_string = new_text;
938-
}
939-
},
940-
TextPart::Quoted(_) => {
941-
// do not expand in quotes
942-
},
943-
}
944-
}
945-
Ok(())
946-
}
947-
948889
fn evaluate_word_parts_inner(
949890
parts: Vec<WordPart>,
950891
is_quoted: bool,
@@ -963,6 +904,9 @@ fn evaluate_word_parts(
963904
None
964905
}
965906
WordPart::Variable(name) => state.get_var(OsStr::new(&name)).cloned(),
907+
WordPart::Tilde => {
908+
sys_traits::impls::real_home_dir_with_env(state).map(|s| s.into_os_string())
909+
}
966910
WordPart::Command(list) => Some(
967911
evaluate_command_substitution(
968912
list,

0 commit comments

Comments
 (0)