diff --git a/crates/deno_task_shell/src/grammar.pest b/crates/deno_task_shell/src/grammar.pest index 0b270ee..8909557 100644 --- a/crates/deno_task_shell/src/grammar.pest +++ b/crates/deno_task_shell/src/grammar.pest @@ -7,16 +7,37 @@ COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* } // Basic tokens QUOTED_WORD = { DOUBLE_QUOTED | SINGLE_QUOTED } -UNQUOTED_PENDING_WORD = ${ !OPERATOR ~ - (!(WHITESPACE | NEWLINE) ~ ( +UNQUOTED_PENDING_WORD = ${ + (TILDE_PREFIX ~ (!(OPERATOR | WHITESPACE | NEWLINE) ~ ( EXIT_STATUS | UNQUOTED_ESCAPE_CHAR | SUB_COMMAND | ("$" ~ "{" ~ VARIABLE ~ "}" | "$" ~ VARIABLE) | UNQUOTED_CHAR | QUOTED_WORD - ) -)+ } + ))*) + | + (!(OPERATOR | WHITESPACE | NEWLINE) ~ ( + EXIT_STATUS | + UNQUOTED_ESCAPE_CHAR | + SUB_COMMAND | + ("$" ~ "{" ~ VARIABLE ~ "}" | "$" ~ VARIABLE) | + UNQUOTED_CHAR | + QUOTED_WORD + ))+ +} + +TILDE_PREFIX = ${ + "~" ~ (!(OPERATOR | WHITESPACE | NEWLINE | "/") ~ ( + (!("\"" | "'" | "$" | "\\" | "/") ~ ANY) + ))* +} + +ASSIGNMENT_TILDE_PREFIX = ${ + "~" ~ (!(OPERATOR | WHITESPACE | NEWLINE | "/" | ":") ~ + (!("\"" | "'" | "$" | "\\" | "/") ~ ANY) + )* +} FILE_NAME_PENDING_WORD = ${ (!(WHITESPACE | OPERATOR | NEWLINE) ~ (UNQUOTED_ESCAPE_CHAR | ("$" ~ VARIABLE) | UNQUOTED_CHAR | QUOTED_WORD))+ } @@ -41,7 +62,12 @@ DOUBLE_QUOTED = @{ "\"" ~ QUOTED_PENDING_WORD ~ "\"" } SINGLE_QUOTED = @{ "'" ~ (!"'" ~ ANY)* ~ "'" } NAME = ${ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } -ASSIGNMENT_WORD = { NAME ~ "=" ~ UNQUOTED_PENDING_WORD? } +ASSIGNMENT_WORD = { NAME ~ "=" ~ ASSIGNMENT_VALUE? } +ASSIGNMENT_VALUE = ${ + ASSIGNMENT_TILDE_PREFIX ~ + ((":" ~ ASSIGNMENT_TILDE_PREFIX) | (!":" ~ UNQUOTED_PENDING_WORD))* | + UNQUOTED_PENDING_WORD +} IO_NUMBER = @{ ASCII_DIGIT+ } // Special tokens @@ -59,6 +85,7 @@ DLESSDASH = { "<<-" } CLOBBER = { ">|" } AMPERSAND = { "&" } EXIT_STATUS = ${ "$?" } +TILDE = ${ "~" } // Operators diff --git a/crates/deno_task_shell/src/parser.rs b/crates/deno_task_shell/src/parser.rs index 61bd0a6..9032f53 100644 --- a/crates/deno_task_shell/src/parser.rs +++ b/crates/deno_task_shell/src/parser.rs @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. MIT license. -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use pest::iterators::Pair; use pest::Parser; use pest_derive::Parser; @@ -214,6 +214,23 @@ impl EnvVar { } } +#[cfg_attr(feature = "serialization", derive(serde::Serialize))] +#[cfg_attr(feature = "serialization", serde(rename_all = "camelCase"))] +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TildePrefix { + pub user: Option, +} + +impl TildePrefix { + pub fn only_tilde(self) -> bool { + self.user.is_none() + } + + pub fn new(user: Option) -> Self { + TildePrefix { user } + } +} + #[cfg_attr(feature = "serialization", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq, Clone)] pub struct Word(Vec); @@ -261,6 +278,8 @@ pub enum WordPart { Command(SequentialList), /// Quoted string (ex. `"hello"` or `'test'`) Quoted(Vec), + /// Tilde prefix (ex. `~user/path` or `~/bin`) + Tilde(TildePrefix), } #[cfg_attr(feature = "serialization", derive(serde::Serialize))] @@ -730,6 +749,10 @@ fn parse_word(pair: Pair) -> Result { let quoted = parse_quoted_word(part)?; parts.push(quoted); } + Rule::TILDE_PREFIX => { + let tilde_prefix = parse_tilde_prefix(part)?; + parts.push(tilde_prefix); + } _ => { return Err(anyhow::anyhow!( "Unexpected rule in UNQUOTED_PENDING_WORD: {:?}", @@ -795,6 +818,17 @@ fn parse_word(pair: Pair) -> Result { } } +fn parse_tilde_prefix(pair: Pair) -> Result { + let tilde_prefix_str = pair.as_str(); + let user = if tilde_prefix_str.len() > 1 { + Some(tilde_prefix_str[1..].to_string()) + } else { + None + }; + let tilde_prefix = TildePrefix::new(user); + Ok(WordPart::Tilde(tilde_prefix)) +} + fn parse_quoted_word(pair: Pair) -> Result { let mut parts = Vec::new(); let inner = pair.into_inner().next().unwrap(); @@ -863,7 +897,7 @@ fn parse_env_var(pair: Pair) -> Result { // Get the value of the environment variable let word_value = if let Some(value) = parts.next() { - parse_word(value)? + parse_assignment_value(value).context("Failed to parse assignment value")? } else { Word::new_empty() }; @@ -874,6 +908,32 @@ fn parse_env_var(pair: Pair) -> Result { }) } +fn parse_assignment_value(pair: Pair) -> Result { + let mut parts = Vec::new(); + + for part in pair.into_inner() { + match part.as_rule() { + Rule::ASSIGNMENT_TILDE_PREFIX => { + let tilde_prefix = + parse_tilde_prefix(part).context("Failed to parse tilde prefix")?; + parts.push(tilde_prefix); + } + Rule::UNQUOTED_PENDING_WORD => { + let word_parts = parse_word(part)?; + parts.extend(word_parts.into_parts()); + } + _ => { + return Err(anyhow::anyhow!( + "Unexpected rule in assignment value: {:?}", + part.as_rule() + )) + } + } + } + + Ok(Word::new(parts)) +} + fn parse_io_redirect(pair: Pair) -> Result { let mut inner = pair.into_inner(); diff --git a/crates/deno_task_shell/src/shell/execute.rs b/crates/deno_task_shell/src/shell/execute.rs index cd988ef..62daa5d 100644 --- a/crates/deno_task_shell/src/shell/execute.rs +++ b/crates/deno_task_shell/src/shell/execute.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::path::Path; use std::rc::Rc; +use anyhow::Context; use futures::future; use futures::future::LocalBoxFuture; use futures::FutureExt; @@ -729,6 +730,8 @@ pub enum EvaluateWordTextError { }, #[error("glob: no matches found '{}'", pattern)] NoFilesMatched { pattern: String }, + #[error("Failed to get home directory")] + FailedToGetHomeDirectory(anyhow::Error), } impl EvaluateWordTextError { @@ -738,6 +741,12 @@ impl EvaluateWordTextError { } } +impl From for EvaluateWordTextError { + fn from(err: anyhow::Error) -> Self { + Self::FailedToGetHomeDirectory(err) + } +} + fn evaluate_word_parts( parts: Vec, state: &ShellState, @@ -896,6 +905,18 @@ fn evaluate_word_parts( current_text.push(TextPart::Quoted(text)); continue; } + WordPart::Tilde(tilde_prefix) => { + if tilde_prefix.only_tilde() { + let home_str = dirs::home_dir() + .context("Failed to get home directory")? + .display() + .to_string(); + current_text.push(TextPart::Text(home_str)); + } else { + todo!("tilde expansion with user name is not supported"); + } + continue; + } }; // This text needs to be turned into a vector of strings. diff --git a/scripts/tilde_expansion.sh b/scripts/tilde_expansion.sh new file mode 100644 index 0000000..155cb69 --- /dev/null +++ b/scripts/tilde_expansion.sh @@ -0,0 +1,7 @@ +ls ~/Desktop + +echo ~$var + +echo ~\/bin + +echo ~parsabahraminejad/Desktop