Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(env): use unix paths in windows bash, zsh, fish #724

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/commands/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use crate::directories;
use crate::fs::symlink_dir;
use crate::outln;
use crate::path_ext::PathExt;
use crate::shell::{infer_shell, Shell, AVAILABLE_SHELLS};
use crate::shell::{infer_shell, Shell, AVAILABLE_SHELLS, PATH_FORMATTERS, PathFormatter};
use colored::Colorize;
use std::fmt::Debug;
use thiserror::Error;
use crate::unixpath::infer_formatter;

#[derive(clap::Parser, Debug, Default)]
pub struct Env {
Expand All @@ -21,6 +22,10 @@ pub struct Env {
/// Print the script to change Node versions every directory change
#[clap(long)]
use_on_cd: bool,
/// Unix path formatter to use, cygwin by default
#[clap(long)]
#[clap(possible_values = PATH_FORMATTERS)]
path_formatter: Option<PathFormatter>,
}

fn generate_symlink_path() -> String {
Expand Down Expand Up @@ -63,16 +68,21 @@ impl Command for Env {
.shell
.or_else(&infer_shell)
.ok_or(Error::CantInferShell)?;

let path_formatter = self
.path_formatter
.unwrap_or_else(&infer_formatter);

let multishell_path = make_symlink(config)?;
let binary_path = if cfg!(windows) {
multishell_path.clone()
} else {
multishell_path.join("bin")
};
println!("{}", shell.path(&binary_path)?);
println!("{}", shell.path(&binary_path, &path_formatter)?);
println!(
"{}",
shell.set_env_var("FNM_MULTISHELL_PATH", multishell_path.to_str().unwrap())
shell.set_env_var("FNM_MULTISHELL_PATH", &shell.format_path(&multishell_path, &path_formatter)?)
);
println!(
"{}",
Expand All @@ -83,7 +93,7 @@ impl Command for Env {
);
println!(
"{}",
shell.set_env_var("FNM_DIR", config.base_dir_with_default().to_str().unwrap())
shell.set_env_var("FNM_DIR", &shell.format_path(&config.base_dir_with_default(), &path_formatter)?)
);
println!(
"{}",
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod http;
mod installed_versions;
mod lts;
mod path_ext;
mod unixpath;
mod remote_node_index;
mod shell;
mod system_info;
Expand All @@ -35,6 +36,8 @@ mod version_files;
mod log_level;
mod default_version;
mod directories;
mod wsl;


fn main() {
env_logger::init();
Expand Down
14 changes: 9 additions & 5 deletions src/shell/bash.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use crate::version_file_strategy::VersionFileStrategy;

use super::shell::Shell;
use anyhow::Result;
use indoc::{formatdoc, indoc};
use std::path::Path;
use crate::shell::PathFormatter;
use crate::unixpath::to_unix_path;

#[derive(Debug)]
pub struct Bash;
Expand All @@ -12,11 +15,8 @@ impl Shell for Bash {
clap_complete::Shell::Bash
}

fn path(&self, path: &Path) -> anyhow::Result<String> {
let path = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Can't convert path to string"))?;
Ok(format!("export PATH={:?}:$PATH", path))
fn path(&self, path: &Path, formatter: &PathFormatter) -> anyhow::Result<String> {
Ok(format!("export PATH={}:$PATH", &self.format_path(path, formatter)?))
}

fn set_env_var(&self, name: &str, value: &str) -> String {
Expand Down Expand Up @@ -51,4 +51,8 @@ impl Shell for Bash {
autoload_hook = autoload_hook
))
}

fn format_path(&self, path: &Path, formatter: &PathFormatter) -> Result<String> {
to_unix_path(path, formatter)
}
}
16 changes: 10 additions & 6 deletions src/shell/fish.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use crate::version_file_strategy::VersionFileStrategy;

use super::shell::Shell;
use anyhow::Result;
use indoc::{formatdoc, indoc};
use std::path::Path;
use crate::shell::PathFormatter;
use crate::unixpath::to_unix_path;

#[derive(Debug)]
pub struct Fish;
Expand All @@ -12,18 +15,15 @@ impl Shell for Fish {
clap_complete::Shell::Fish
}

fn path(&self, path: &Path) -> anyhow::Result<String> {
let path = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Can't convert path to string"))?;
Ok(format!("set -gx PATH {:?} $PATH;", path))
fn path(&self, path: &Path, formatter: &PathFormatter) -> Result<String> {
Ok(format!("set -gx PATH {} $PATH;", &self.format_path(path, formatter)?))
}

fn set_env_var(&self, name: &str, value: &str) -> String {
format!("set -gx {name} {value:?};", name = name, value = value)
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> Result<String> {
let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!(
r#"
Expand All @@ -46,4 +46,8 @@ impl Shell for Fish {
autoload_hook = autoload_hook
))
}

fn format_path(&self, path: &Path, formatter: &PathFormatter) -> Result<String> {
to_unix_path(path, formatter)
}
}
2 changes: 1 addition & 1 deletion src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ pub use bash::Bash;
pub use fish::Fish;
pub use infer::infer_shell;
pub use powershell::PowerShell;
pub use shell::{Shell, AVAILABLE_SHELLS};
pub use shell::{Shell, AVAILABLE_SHELLS, PATH_FORMATTERS, PathFormatter};
pub use windows_cmd::WindowsCmd;
pub use zsh::Zsh;
32 changes: 28 additions & 4 deletions src/shell/powershell.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
use std::ffi::OsString;
use crate::version_file_strategy::VersionFileStrategy;

use super::Shell;
use crate::shell::PathFormatter;
use anyhow::Result;
use indoc::{formatdoc, indoc};
use std::path::Path;
use crate::unixpath::to_unix_path;
use crate::wsl::is_wsl;

#[derive(Debug)]
pub struct PowerShell;

impl Shell for PowerShell {
fn path(&self, path: &Path) -> anyhow::Result<String> {
fn path(&self, path: &Path, _formatter: &PathFormatter) -> Result<String> {
let current_path =
std::env::var_os("PATH").ok_or_else(|| anyhow::anyhow!("Can't read PATH env var"))?;
let mut split_paths: Vec<_> = std::env::split_paths(&current_path).collect();
split_paths.insert(0, path.to_path_buf());
let new_path = std::env::join_paths(split_paths)
.map_err(|source| anyhow::anyhow!("Can't join paths: {}", source))?;

let new_path = if is_wsl() {
split_paths
.iter()
.map(|path| to_unix_path(path, &PathFormatter::Wsl))
.collect::<Result<Vec<_>, _>>()
.map(|vec| vec.join(":"))
.map(OsString::from)
} else {
std::env::join_paths(split_paths)
.map_err(|err| anyhow::anyhow!("Error joining paths: {}", err))
}.map_err(|err| anyhow::anyhow!("Can't join paths: {}", err))?;

let new_path = new_path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Can't read PATH"))?;
Expand All @@ -25,7 +41,7 @@ impl Shell for PowerShell {
format!(r#"$env:{} = "{}""#, name, value)
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> Result<String> {
let autoload_hook = match config.version_file_strategy() {
VersionFileStrategy::Local => indoc!(
r#"
Expand All @@ -48,4 +64,12 @@ impl Shell for PowerShell {
fn to_clap_shell(&self) -> clap_complete::Shell {
clap_complete::Shell::PowerShell
}

fn format_path(&self, path: &Path, _formatter: &PathFormatter) -> Result<String> {
if is_wsl() {
to_unix_path(path, &PathFormatter::Wsl)
} else {
Ok(path.to_str().unwrap().to_string())
}
}
}
30 changes: 27 additions & 3 deletions src/shell/shell.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use std::fmt::Debug;
use std::path::Path;
use std::str::FromStr;
use anyhow::Result;

pub trait Shell: Debug {
fn path(&self, path: &Path) -> anyhow::Result<String>;
fn path(&self, path: &Path, formatter: &PathFormatter) -> Result<String>;
fn set_env_var(&self, name: &str, value: &str) -> String;
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String>;
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> Result<String>;
fn rehash(&self) -> Option<String> {
None
}
fn to_clap_shell(&self) -> clap_complete::Shell;
fn format_path(&self, path: &Path, formatter: &PathFormatter) -> Result<String>;
}

#[cfg(windows)]
Expand All @@ -17,7 +20,7 @@ pub const AVAILABLE_SHELLS: &[&str; 5] = &["cmd", "powershell", "bash", "zsh", "
#[cfg(unix)]
pub const AVAILABLE_SHELLS: &[&str; 4] = &["bash", "zsh", "fish", "powershell"];

impl std::str::FromStr for Box<dyn Shell> {
impl FromStr for Box<dyn Shell> {
type Err = String;

fn from_str(s: &str) -> Result<Box<dyn Shell>, Self::Err> {
Expand All @@ -37,3 +40,24 @@ impl From<Box<dyn Shell>> for clap_complete::Shell {
shell.to_clap_shell()
}
}

pub const PATH_FORMATTERS: &[&str; 2] = &["cygwin", "wsl"];

#[derive(Debug)]
pub enum PathFormatter {
Cygwin,
Wsl,
None,
}

impl FromStr for PathFormatter {
type Err = String;
fn from_str(s: &str) -> Result<PathFormatter, Self::Err> {
match s {
"cygwin" => Ok(Self::Cygwin),
"wsl" => Ok(Self::Wsl),
"none" => Ok(Self::None),
formatter => Err(format!("I don't know the Unix path formatter {:?}", formatter)),
}
}
}
13 changes: 10 additions & 3 deletions src/shell/windows_cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use super::shell::Shell;
use anyhow::Result;
use std::path::Path;
use crate::shell::PathFormatter;

#[derive(Debug)]
pub struct WindowsCmd;
Expand All @@ -10,7 +12,7 @@ impl Shell for WindowsCmd {
panic!("Shell completion is not supported for Windows Command Prompt. Maybe try using PowerShell for a better experience?");
}

fn path(&self, path: &Path) -> anyhow::Result<String> {
fn path(&self, path: &Path, _formatter: &PathFormatter) -> Result<String> {
let current_path =
std::env::var_os("path").ok_or_else(|| anyhow::anyhow!("Can't read PATH env var"))?;
let mut split_paths: Vec<_> = std::env::split_paths(&current_path).collect();
Expand All @@ -27,7 +29,7 @@ impl Shell for WindowsCmd {
format!("SET {}={}", name, value)
}

fn use_on_cd(&self, config: &crate::config::FnmConfig) -> anyhow::Result<String> {
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> Result<String> {
let path = config.base_dir_with_default().join("cd.cmd");
create_cd_file_at(&path).map_err(|source| {
anyhow::anyhow!(
Expand All @@ -41,9 +43,14 @@ impl Shell for WindowsCmd {
.ok_or_else(|| anyhow::anyhow!("Can't read path to cd.cmd"))?;
Ok(format!("doskey cd={} $*", path,))
}

fn format_path(&self, path: &Path, _formatter: &PathFormatter) -> Result<String> {
// cmd only runs on Windows, so we don't need to do path conversion
Ok(path.to_str().unwrap().to_string())
}
}

fn create_cd_file_at(path: &std::path::Path) -> std::io::Result<()> {
fn create_cd_file_at(path: &Path) -> std::io::Result<()> {
use std::io::Write;
let cmd_contents = include_bytes!("./cd.cmd");
let mut file = std::fs::File::create(path)?;
Expand Down
14 changes: 9 additions & 5 deletions src/shell/zsh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use crate::version_file_strategy::VersionFileStrategy;
use super::shell::Shell;
use indoc::{formatdoc, indoc};
use std::path::Path;
use anyhow::Result;
use crate::shell::PathFormatter;
use crate::unixpath::to_unix_path;

#[derive(Debug)]
pub struct Zsh;
Expand All @@ -12,11 +15,8 @@ impl Shell for Zsh {
clap_complete::Shell::Zsh
}

fn path(&self, path: &Path) -> anyhow::Result<String> {
let path = path
.to_str()
.ok_or_else(|| anyhow::anyhow!("Path is not valid UTF-8"))?;
Ok(format!("export PATH={:?}:$PATH", path))
fn path(&self, path: &Path, formatter: &PathFormatter) -> Result<String> {
Ok(format!("export PATH={}:$PATH", &self.format_path(path, formatter)?))
}

fn set_env_var(&self, name: &str, value: &str) -> String {
Expand Down Expand Up @@ -51,4 +51,8 @@ impl Shell for Zsh {
autoload_hook = autoload_hook
))
}

fn format_path(&self, path: &Path, formatter: &PathFormatter) -> Result<String> {
to_unix_path(path, formatter)
}
}
Loading