From 12fda6ac635f3bb22c6132b95e1a9f58b522edca Mon Sep 17 00:00:00 2001 From: HasanAlrimawi Date: Wed, 27 Nov 2024 14:46:13 +0200 Subject: [PATCH 1/3] fix: support tilde expansion --- src/parser.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++- src/shell/which.rs | 9 +++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index a6ad40f..d0c5eb6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,10 @@ // Copyright 2018-2024 the Deno authors. MIT license. +use crate::which; use anyhow::bail; use anyhow::Result; use monch::*; +use std::path::PathBuf; // Shell grammar rules this is loosely based on: // https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_10_02 @@ -758,6 +760,25 @@ fn parse_word_parts( ) } + fn expand_tilde(result: &mut Vec) { + if let Some(WordPart::Text(text)) = result.last_mut() { + if text.contains(" ~ ") + || text.contains("~/") + || (text.find("~").unwrap_or(0) == text.len() - 1) + { + let temp = text.clone().replace( + "~", + which::home_dir() + .unwrap_or(PathBuf::from("~")) + .to_str() + .unwrap_or("~"), + ); + text.clear(); + text.push_str(temp.as_str()); + } + } + } + move |input| { enum PendingPart<'a> { Char(char), @@ -791,7 +812,7 @@ fn parse_word_parts( if_true(next_char, |&c| match mode { ParseWordPartsMode::DoubleQuotes => c != '"', ParseWordPartsMode::Unquoted => { - !c.is_whitespace() && !"~(){}<>|&;\"'".contains(c) + !c.is_whitespace() && !"(){}<>|&;\"'".contains(c) } }), PendingPart::Char, @@ -828,6 +849,10 @@ fn parse_word_parts( } } + if mode == ParseWordPartsMode::Unquoted { + expand_tilde(&mut result); + } + Ok((input, result)) } } @@ -1507,6 +1532,70 @@ mod test { } } + #[test] + fn test_tilde_unquoted_expansion() { + let home_dir = which::home_dir() + .unwrap_or(PathBuf::from("~")) + .to_str() + .unwrap_or("~") + .to_string(); + run_test( + parse_sequential_list, + "echo ~", + Ok(SequentialList { + items: vec![SequentialListItem { + is_async: false, + sequence: Sequence::Pipeline(Pipeline { + negated: false, + inner: PipelineInner::Command(Command { + inner: CommandInner::Simple(SimpleCommand { + env_vars: [].to_vec(), + args: [ + Word([WordPart::Text("echo".to_string())].to_vec()), + Word([WordPart::Text(home_dir)].to_vec()), + ] + .to_vec(), + }), + redirect: None, + }), + }), + }], + }), + ); + } + + #[test] + fn test_tilde_as_char() { + run_test( + parse_sequential_list, + "echo \"~\"", + Ok(SequentialList { + items: vec![SequentialListItem { + is_async: false, + sequence: Sequence::Pipeline(Pipeline { + negated: false, + inner: PipelineInner::Command(Command { + inner: CommandInner::Simple(SimpleCommand { + env_vars: [].to_vec(), + args: [ + Word([WordPart::Text("echo".to_string())].to_vec()), + Word( + [WordPart::Quoted( + [WordPart::Text("~".to_string())].to_vec(), + )] + .to_vec(), + ), + ] + .to_vec(), + }), + redirect: None, + }), + }), + }], + }), + ); + } + #[test] fn test_redirects() { let expected = Ok(Command { diff --git a/src/shell/which.rs b/src/shell/which.rs index 884f54d..62a457a 100644 --- a/src/shell/which.rs +++ b/src/shell/which.rs @@ -25,6 +25,15 @@ impl CommandPathResolutionError { } } +pub fn home_dir() -> Option { + if let Some(userprofile) = std::env::var_os("USERPROFILE") { + if !userprofile.is_empty() { + return Some(PathBuf::from(userprofile)); + } + } + return None; +} + /// Resolves a command name to an absolute path. pub fn resolve_command_path<'a>( command_name: &str, From d9795137ae96867e1518692c5ccc660e99eca6f8 Mon Sep 17 00:00:00 2001 From: HasanAlrimawi Date: Wed, 27 Nov 2024 14:58:52 +0200 Subject: [PATCH 2/3] clippy related fixes --- src/parser.rs | 6 +++--- src/shell/which.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index d0c5eb6..c52551a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -760,14 +760,14 @@ fn parse_word_parts( ) } - fn expand_tilde(result: &mut Vec) { + fn expand_tilde(result: &mut [WordPart]) { if let Some(WordPart::Text(text)) = result.last_mut() { if text.contains(" ~ ") || text.contains("~/") - || (text.find("~").unwrap_or(0) == text.len() - 1) + || (text.find('~').unwrap_or(0) == text.len() - 1) { let temp = text.clone().replace( - "~", + '~', which::home_dir() .unwrap_or(PathBuf::from("~")) .to_str() diff --git a/src/shell/which.rs b/src/shell/which.rs index 62a457a..c96d49f 100644 --- a/src/shell/which.rs +++ b/src/shell/which.rs @@ -31,7 +31,7 @@ pub fn home_dir() -> Option { return Some(PathBuf::from(userprofile)); } } - return None; + None } /// Resolves a command name to an absolute path. From 2496396ce18e8cada1c47f0f2c8f0adf4b756114 Mon Sep 17 00:00:00 2001 From: HasanAlrimawi Date: Wed, 27 Nov 2024 16:52:41 +0200 Subject: [PATCH 3/3] changes so the CI succeeds --- src/parser.rs | 14 +++++++++++--- src/shell/which.rs | 9 --------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index c52551a..55c16df 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. MIT license. -use crate::which; use anyhow::bail; use anyhow::Result; use monch::*; @@ -340,6 +339,15 @@ pub fn parse(input: &str) -> Result { } } +fn home_dir() -> Option { + if let Some(userprofile) = std::env::var_os("USERPROFILE") { + if !userprofile.is_empty() { + return Some(PathBuf::from(userprofile)); + } + } + None +} + fn parse_sequential_list(input: &str) -> ParseResult { let (input, items) = separated_list( terminated(parse_sequential_list_item, skip_whitespace), @@ -768,7 +776,7 @@ fn parse_word_parts( { let temp = text.clone().replace( '~', - which::home_dir() + home_dir() .unwrap_or(PathBuf::from("~")) .to_str() .unwrap_or("~"), @@ -1534,7 +1542,7 @@ mod test { #[test] fn test_tilde_unquoted_expansion() { - let home_dir = which::home_dir() + let home_dir = home_dir() .unwrap_or(PathBuf::from("~")) .to_str() .unwrap_or("~") diff --git a/src/shell/which.rs b/src/shell/which.rs index c96d49f..884f54d 100644 --- a/src/shell/which.rs +++ b/src/shell/which.rs @@ -25,15 +25,6 @@ impl CommandPathResolutionError { } } -pub fn home_dir() -> Option { - if let Some(userprofile) = std::env::var_os("USERPROFILE") { - if !userprofile.is_empty() { - return Some(PathBuf::from(userprofile)); - } - } - None -} - /// Resolves a command name to an absolute path. pub fn resolve_command_path<'a>( command_name: &str,