Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-stdx/src/env.rs')
| -rw-r--r-- | helix-stdx/src/env.rs | 178 |
1 files changed, 16 insertions, 162 deletions
diff --git a/helix-stdx/src/env.rs b/helix-stdx/src/env.rs index 9e7781a0..90a0aee8 100644 --- a/helix-stdx/src/env.rs +++ b/helix-stdx/src/env.rs @@ -1,166 +1,53 @@ -//! Functions for working with the host environment. use std::{ - borrow::Cow, - ffi::{OsStr, OsString}, + ffi::OsStr, path::{Path, PathBuf}, sync::RwLock, }; -use once_cell::sync::Lazy; - -// We keep the CWD as a static so that we can access it in places where we don't have access to the Editor static CWD: RwLock<Option<PathBuf>> = RwLock::new(None); -/// Get the current working directory. -/// This information is managed internally as the call to std::env::current_dir -/// might fail if the cwd has been deleted. +// Get the current working directory. +// This information is managed internally as the call to std::env::current_dir +// might fail if the cwd has been deleted. pub fn current_working_dir() -> PathBuf { if let Some(path) = &*CWD.read().unwrap() { return path.clone(); } - // implementation of crossplatform pwd -L - // we want pwd -L so that symlinked directories are handled correctly - let mut cwd = std::env::current_dir().expect("Couldn't determine current working directory"); - - let pwd = std::env::var_os("PWD"); - #[cfg(windows)] - let pwd = pwd.or_else(|| std::env::var_os("CD")); - - if let Some(pwd) = pwd.map(PathBuf::from) { - if pwd.canonicalize().ok().as_ref() == Some(&cwd) { - cwd = pwd; - } - } - let mut dst = CWD.write().unwrap(); - *dst = Some(cwd.clone()); + let path = std::env::current_dir() + .map(crate::path::normalize) + .expect("Couldn't determine current working directory"); + let mut cwd = CWD.write().unwrap(); + *cwd = Some(path.clone()); - cwd + path } -/// Update the current working directory. -pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<Option<PathBuf>> { +pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> { let path = crate::path::canonicalize(path); std::env::set_current_dir(&path)?; let mut cwd = CWD.write().unwrap(); - - Ok(cwd.replace(path)) + *cwd = Some(path); + Ok(()) } -/// Checks if the given environment variable is set. pub fn env_var_is_set(env_var_name: &str) -> bool { std::env::var_os(env_var_name).is_some() } -/// Checks if a binary with the given name exists. pub fn binary_exists<T: AsRef<OsStr>>(binary_name: T) -> bool { which::which(binary_name).is_ok() } -/// Attempts to find a binary of the given name. See [which](https://linux.die.net/man/1/which). pub fn which<T: AsRef<OsStr>>( binary_name: T, ) -> Result<std::path::PathBuf, ExecutableNotFoundError> { - let binary_name = binary_name.as_ref(); - which::which(binary_name).map_err(|err| ExecutableNotFoundError { - command: binary_name.to_string_lossy().into_owned(), + which::which(binary_name.as_ref()).map_err(|err| ExecutableNotFoundError { + command: binary_name.as_ref().to_string_lossy().into_owned(), inner: err, }) } -fn find_brace_end(src: &[u8]) -> Option<usize> { - use regex_automata::meta::Regex; - - static REGEX: Lazy<Regex> = Lazy::new(|| Regex::builder().build("[{}]").unwrap()); - let mut depth = 0; - for mat in REGEX.find_iter(src) { - let pos = mat.start(); - match src[pos] { - b'{' => depth += 1, - b'}' if depth == 0 => return Some(pos), - b'}' => depth -= 1, - _ => unreachable!(), - } - } - None -} - -fn expand_impl(src: &OsStr, mut resolve: impl FnMut(&OsStr) -> Option<OsString>) -> Cow<'_, OsStr> { - use regex_automata::meta::Regex; - - static REGEX: Lazy<Regex> = Lazy::new(|| { - Regex::builder() - .build_many(&[ - r"\$\{([^\}:]+):-", - r"\$\{([^\}:]+):=", - r"\$\{([^\}-]+)-", - r"\$\{([^\}=]+)=", - r"\$\{([^\}]+)", - r"\$(\w+)", - ]) - .unwrap() - }); - - let bytes = src.as_encoded_bytes(); - let mut res = Vec::with_capacity(bytes.len()); - let mut pos = 0; - for captures in REGEX.captures_iter(bytes) { - let mat = captures.get_match().unwrap(); - let pattern_id = mat.pattern().as_usize(); - let mut range = mat.range(); - // A pattern may match multiple times on a single variable, for example `${HOME:-$HOME}`: - // `${HOME:-` matches and also the default value (`$HOME`). Skip past any variables which - // have already been expanded. - if range.start < pos { - continue; - } - let var = &bytes[captures.get_group(1).unwrap().range()]; - let default = if pattern_id != 5 { - let Some(bracket_pos) = find_brace_end(&bytes[range.end..]) else { - break; - }; - let default = &bytes[range.end..range.end + bracket_pos]; - range.end += bracket_pos + 1; - default - } else { - &[] - }; - // safety: this is a codepoint aligned substring of an osstr (always valid) - let var = unsafe { OsStr::from_encoded_bytes_unchecked(var) }; - let expansion = resolve(var); - let expansion = match &expansion { - Some(val) => { - if val.is_empty() && pattern_id < 2 { - default - } else { - val.as_encoded_bytes() - } - } - None => default, - }; - res.extend_from_slice(&bytes[pos..range.start]); - pos = range.end; - res.extend_from_slice(expansion); - } - if pos == 0 { - src.into() - } else { - res.extend_from_slice(&bytes[pos..]); - // safety: this is a composition of valid osstr (and codepoint aligned slices which are also valid) - unsafe { OsString::from_encoded_bytes_unchecked(res) }.into() - } -} - -/// performs substitution of enviorment variables. Supports the following (POSIX) syntax: -/// -/// * `$<var>`, `${<var>}` -/// * `${<var>:-<default>}`, `${<var>-<default>}` -/// * `${<var>:=<default>}`, `${<var>=default}` -/// -pub fn expand<S: AsRef<OsStr> + ?Sized>(src: &S) -> Cow<'_, OsStr> { - expand_impl(src.as_ref(), |var| std::env::var_os(var)) -} - #[derive(Debug)] pub struct ExecutableNotFoundError { command: String, @@ -177,9 +64,7 @@ impl std::error::Error for ExecutableNotFoundError {} #[cfg(test)] mod tests { - use std::ffi::{OsStr, OsString}; - - use super::{current_working_dir, expand_impl, set_current_working_dir}; + use super::{current_working_dir, set_current_working_dir}; #[test] fn current_dir_is_set() { @@ -192,35 +77,4 @@ mod tests { let cwd = current_working_dir(); assert_eq!(cwd, new_path); } - - macro_rules! assert_env_expand { - ($env: expr, $lhs: expr, $rhs: expr) => { - assert_eq!(&*expand_impl($lhs.as_ref(), $env), OsStr::new($rhs)); - }; - } - - /// paths that should work on all platforms - #[test] - fn test_env_expand() { - let env = |var: &OsStr| -> Option<OsString> { - match var.to_str().unwrap() { - "FOO" => Some("foo".into()), - "EMPTY" => Some("".into()), - _ => None, - } - }; - assert_env_expand!(env, "pass_trough", "pass_trough"); - assert_env_expand!(env, "$FOO", "foo"); - assert_env_expand!(env, "bar/$FOO/baz", "bar/foo/baz"); - assert_env_expand!(env, "bar/${FOO}/baz", "bar/foo/baz"); - assert_env_expand!(env, "baz/${BAR:-bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${FOO:-$FOO}/foo", "baz/foo/foo"); - assert_env_expand!(env, "baz/${BAR:=bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${BAR-bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${BAR=bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${EMPTY:-bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${EMPTY:=bar}/foo", "baz/bar/foo"); - assert_env_expand!(env, "baz/${EMPTY-bar}/foo", "baz//foo"); - assert_env_expand!(env, "baz/${EMPTY=bar}/foo", "baz//foo"); - } } |