From 6a1b18ff8517421f00217342ef6d2606e18bf048 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 4 May 2023 13:14:24 +0200 Subject: [PATCH] feat: add a dynamic shell type and simple discovery (#187) * feat: add a dynamic shell type and simple discovery * fix: no need for -c * fix: doc link --- crates/rattler_shell/Cargo.toml | 6 +- crates/rattler_shell/src/activation.rs | 6 +- crates/rattler_shell/src/shell/mod.rs | 120 ++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 17 deletions(-) diff --git a/crates/rattler_shell/Cargo.toml b/crates/rattler_shell/Cargo.toml index b99ec9b8f..56447362a 100644 --- a/crates/rattler_shell/Cargo.toml +++ b/crates/rattler_shell/Cargo.toml @@ -11,11 +11,13 @@ license.workspace = true readme.workspace = true [dependencies] -thiserror = "1.0.30" +enum_dispatch = "0.3.11" indexmap = "1.9.2" +itertools = "0.10.5" +rattler_conda_types = { version = "0.2.0", path = "../rattler_conda_types" } serde_json = { version = "1.0.93", features = ["preserve_order"]} +thiserror = "1.0.30" tracing = "0.1.29" -rattler_conda_types = { version = "0.2.0", path = "../rattler_conda_types" } [dev-dependencies] tempdir = "0.3.7" diff --git a/crates/rattler_shell/src/activation.rs b/crates/rattler_shell/src/activation.rs index de57d3e51..f9be9c573 100644 --- a/crates/rattler_shell/src/activation.rs +++ b/crates/rattler_shell/src/activation.rs @@ -2,6 +2,7 @@ //! This crate provides helper functions to activate and deactivate virtual environments. +use std::ffi::OsStr; use std::{ fs, path::{Path, PathBuf}, @@ -87,7 +88,10 @@ fn collect_scripts(path: &Path, shell_type: &T) -> Result .into_iter() .filter_map(|r| r.ok()) .map(|r| r.path()) - .filter(|path| path.is_file() && path.extension() == Some(shell_type.extension())) + .filter(|path| { + path.is_file() + && path.extension().and_then(OsStr::to_str) == Some(shell_type.extension()) + }) .collect::>(); scripts.sort(); diff --git a/crates/rattler_shell/src/shell/mod.rs b/crates/rattler_shell/src/shell/mod.rs index 7368e1f61..08ad92932 100644 --- a/crates/rattler_shell/src/shell/mod.rs +++ b/crates/rattler_shell/src/shell/mod.rs @@ -1,7 +1,9 @@ //! This module contains the [`Shell`] trait and implementations for various shells. +use enum_dispatch::enum_dispatch; +use itertools::Itertools; +use std::process::Command; use std::{ - ffi::OsStr, fmt::Write, path::{Path, PathBuf}, }; @@ -22,6 +24,7 @@ use std::{ /// /// assert_eq!(script, "export FOO=\"bar\"\n"); /// ``` +#[enum_dispatch(ShellEnum)] pub trait Shell { /// Set an env var by `export`-ing it. fn set_env_var(&self, f: &mut impl Write, env_var: &str, value: &str) -> std::fmt::Result; @@ -32,6 +35,16 @@ pub trait Shell { /// Run a script in the current shell. fn run_script(&self, f: &mut impl Write, path: &Path) -> std::fmt::Result; + /// Executes a command in the current shell. Use [`Self::run_script`] when you want to run + /// another shell script. + fn run_command<'a>( + &self, + f: &mut impl Write, + command: impl IntoIterator + 'a, + ) -> std::fmt::Result { + write!(f, "{}", command.into_iter().join(" ")) + } + /// Set the PATH variable to the given paths. fn set_path(&self, f: &mut impl Write, paths: &[PathBuf]) -> std::fmt::Result { let path = std::env::join_paths(paths).unwrap(); @@ -39,7 +52,10 @@ pub trait Shell { } /// The extension that shell scripts for this interpreter usually use. - fn extension(&self) -> &OsStr; + fn extension(&self) -> &str; + + /// Constructs a [`Command`] that will execute the specified script by this shell. + fn create_run_script_command(&self, path: &Path) -> Command; } /// Convert a native PATH on Windows to a Unix style path usign cygpath. @@ -86,8 +102,8 @@ impl Shell for Bash { writeln!(f, ". \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("sh") + fn extension(&self) -> &str { + "sh" } fn set_path(&self, f: &mut impl Write, paths: &[PathBuf]) -> std::fmt::Result { @@ -101,6 +117,19 @@ impl Shell for Bash { self.set_env_var(f, "PATH", path.to_str().unwrap()) } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("bash"); + + // check if we are on Windows, and if yes, convert native path to unix for (Git) Bash + if cfg!(windows) { + cmd.arg(native_path_to_unix(path.to_str().unwrap()).unwrap()); + } else { + cmd.arg(path); + } + + cmd + } } /// A [`Shell`] implementation for the Zsh shell. @@ -120,8 +149,14 @@ impl Shell for Zsh { writeln!(f, ". \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("zsh") + fn extension(&self) -> &str { + "zsh" + } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("zsh"); + cmd.arg(path); + cmd } } @@ -142,8 +177,14 @@ impl Shell for Xonsh { writeln!(f, "source-bash \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("sh") + fn extension(&self) -> &str { + "sh" + } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("xonsh"); + cmd.arg(path); + cmd } } @@ -164,8 +205,22 @@ impl Shell for CmdExe { writeln!(f, "@CALL \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("bat") + fn run_command<'a>( + &self, + f: &mut impl Write, + command: impl IntoIterator + 'a, + ) -> std::fmt::Result { + write!(f, "@{}", command.into_iter().join(" ")) + } + + fn extension(&self) -> &str { + "bat" + } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("cmd.exe"); + cmd.arg("/D").arg("/C").arg(path); + cmd } } @@ -186,8 +241,14 @@ impl Shell for PowerShell { writeln!(f, ". \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("ps1") + fn extension(&self) -> &str { + "ps1" + } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("powershell"); + cmd.arg(path); + cmd } } @@ -208,8 +269,39 @@ impl Shell for Fish { writeln!(f, "source \"{}\"", path.to_string_lossy()) } - fn extension(&self) -> &OsStr { - OsStr::new("fish") + fn extension(&self) -> &str { + "fish" + } + + fn create_run_script_command(&self, path: &Path) -> Command { + let mut cmd = Command::new("fish"); + cmd.arg(path); + cmd + } +} + +/// A generic [`Shell`] implementation for concrete shell types. +#[enum_dispatch] +#[allow(missing_docs)] +#[derive(Clone)] +pub enum ShellEnum { + Bash, + Zsh, + Xonsh, + CmdExe, + PowerShell, + Fish, +} + +impl ShellEnum { + /// Returns the shell for the current system. + pub fn detect_from_environment() -> Option { + // TODO: Make this a bit better + if cfg!(windows) { + Some(CmdExe.into()) + } else { + Some(Bash.into()) + } } }