From fd730452ca27b67072954b73be8f1aa693f187cf Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sun, 24 Mar 2024 08:09:01 -0700 Subject: [PATCH] expand ~ on windows --- src/builtin/ls.rs | 2 +- src/error.rs | 4 ++++ src/syntax.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/builtin/ls.rs b/src/builtin/ls.rs index 45576d3..e7cf9a7 100644 --- a/src/builtin/ls.rs +++ b/src/builtin/ls.rs @@ -7,7 +7,7 @@ use std::{env, fs, io::Write}; pub struct Ls; impl Call for Ls { - fn call(&self, ctx: &mut Context, args: &[Value]) -> Result { + fn call(&self, ctx: &mut Context, _args: &[Value]) -> Result { let cwd = env::current_dir()?; let dir = fs::read_dir(&cwd)?; for child in dir { diff --git a/src/error.rs b/src/error.rs index 79a107a..0944aa6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use crate::{ lex::Token, + syntax::ExpandError, topo::{Glyph, Position}, }; use std::io; @@ -91,6 +92,9 @@ pub enum ParseError { #[error("dangling")] DanglingElements, + #[error("shell expansion error")] + ExpansionError(#[from] ExpandError), + #[error("you wouldn't parse a semicolon")] WhyParseSemicolon, } diff --git a/src/syntax.rs b/src/syntax.rs index 800ff3b..d22921d 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -5,7 +5,7 @@ use crate::{ parse, run::{Call, Context, Eval, Value}, }; -use std::{collections::HashSet, process}; +use std::{collections::HashSet, env, ffi::OsString, process}; /// The differnt types of nodes that may appear in our AST #[derive(Debug)] @@ -170,7 +170,9 @@ impl TreeBuilder { Element::Command(cmd) } parse::Value::Terminal(Token::Word(word)) => { - Element::Literal(Value::Text(word.to_string())) + let text = word.to_string(); + let text = expand(&text)?; + Element::Literal(Value::Text(text)) } parse::Value::Terminal(Token::Glob(_)) => { todo!() @@ -192,7 +194,49 @@ pub fn parse(source: &str) -> Result { let parser = parse::Parser::new(tokens); let mut parse_tree = parser.parse()?; let mut builder = TreeBuilder::new(); - builder.parse(&mut parse_tree) + let root = builder.parse(&mut parse_tree)?; + Ok(root) +} + +#[cfg(target_os = "windows")] +fn home_dir() -> Result { + env::var_os("HOMEPATH").ok_or(ExpandError::EmptyEnvironmentVar(String::from("HOMEPATH"))) +} + +#[derive(thiserror::Error, Debug)] +pub enum ExpandError { + #[error("environment var is not set: {0}")] + EmptyEnvironmentVar(String), + + #[error("home dir is not utf-8")] + NotUnicode, +} + +fn expand(text: &str) -> Result { + let chars = text.chars(); + let mut escape = false; + let mut s = String::new(); + for c in chars { + if !escape && c == '\\' { + escape = true; + continue; + } + + if escape { + s.push(c); + escape = false; + continue; + } + + if c == '~' { + let p = home_dir()?; + s.push_str(p.to_str().ok_or(ExpandError::NotUnicode)?); + continue; + } + + s.push(c); + } + Ok(s) } #[cfg(test)]