Browse Source

Allow aliasing to the system version (#556)

remotes/origin/add-with-shims
Gal Schlezinger 3 years ago committed by GitHub
parent
commit
8d0d78ca24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/alias.rs
  2. 20
      src/choose_version_for_user_input.rs
  3. 3
      src/commands/env.rs
  4. 24
      src/commands/unalias.rs
  5. 11
      src/commands/use.rs
  6. 4
      src/fs.rs
  7. 3
      src/shell/shell.rs
  8. 4
      src/shell/zsh.rs
  9. 4
      src/system_version.rs
  10. 21
      src/version.rs
  11. 14
      tests/feature_tests/mod.rs
  12. 17
      tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Bash.snap
  13. 14
      tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Fish.snap
  14. 15
      tests/feature_tests/snapshots/e2e__feature_tests__alias_system__PowerShell.snap
  15. 15
      tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Zsh.snap

23
src/alias.rs

@ -1,5 +1,6 @@
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::fs::{remove_symlink_dir, symlink_dir}; use crate::fs::{remove_symlink_dir, shallow_read_symlink, symlink_dir};
use crate::system_version;
use crate::version::Version; use crate::version::Version;
use std::convert::TryInto; use std::convert::TryInto;
use std::path::PathBuf; use std::path::PathBuf;
@ -12,15 +13,10 @@ pub fn create_alias(
let aliases_dir = config.aliases_dir(); let aliases_dir = config.aliases_dir();
std::fs::create_dir_all(&aliases_dir)?; std::fs::create_dir_all(&aliases_dir)?;
let version_dir = version let version_dir = version.installation_path(config);
.installation_path(config)
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?;
let alias_dir = aliases_dir.join(common_name); let alias_dir = aliases_dir.join(common_name);
if alias_dir.exists() { remove_symlink_dir(&alias_dir).ok();
remove_symlink_dir(&alias_dir)?;
}
symlink_dir(&version_dir, &alias_dir)?; symlink_dir(&version_dir, &alias_dir)?;
Ok(()) Ok(())
@ -44,7 +40,12 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path {
type Error = std::io::Error; type Error = std::io::Error;
fn try_into(self) -> Result<StoredAlias, Self::Error> { fn try_into(self) -> Result<StoredAlias, Self::Error> {
let destination_path = std::fs::canonicalize(&self)?; let shallow_self = shallow_read_symlink(self)?;
let destination_path = if shallow_self == system_version::path() {
shallow_self
} else {
std::fs::canonicalize(&shallow_self)?
};
Ok(StoredAlias { Ok(StoredAlias {
alias_path: PathBuf::from(self), alias_path: PathBuf::from(self),
destination_path, destination_path,
@ -54,6 +55,9 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path {
impl StoredAlias { impl StoredAlias {
pub fn s_ver(&self) -> &str { pub fn s_ver(&self) -> &str {
if self.destination_path == system_version::path() {
system_version::display_name()
} else {
self.destination_path self.destination_path
.parent() .parent()
.unwrap() .unwrap()
@ -62,6 +66,7 @@ impl StoredAlias {
.to_str() .to_str()
.unwrap() .unwrap()
} }
}
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
self.alias_path self.alias_path

20
src/choose_version_for_user_input.rs

@ -1,4 +1,5 @@
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::fs;
use crate::installed_versions; use crate::installed_versions;
use crate::system_version; use crate::system_version;
use crate::user_version::UserVersion; use crate::user_version::UserVersion;
@ -8,6 +9,7 @@ use log::info;
use snafu::{ensure, ResultExt, Snafu}; use snafu::{ensure, ResultExt, Snafu};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct ApplicableVersion { pub struct ApplicableVersion {
path: PathBuf, path: PathBuf,
version: Version, version: Version,
@ -31,13 +33,28 @@ pub fn choose_version_for_user_input<'a>(
installed_versions::list(config.installations_dir()).context(VersionListing)?; installed_versions::list(config.installations_dir()).context(VersionListing)?;
let result = if let UserVersion::Full(Version::Bypassed) = requested_version { let result = if let UserVersion::Full(Version::Bypassed) = requested_version {
info!("Bypassing fnm: using {} node", "system".cyan()); info!(
"Bypassing fnm: using {} node",
system_version::display_name().cyan()
);
Some(ApplicableVersion { Some(ApplicableVersion {
path: system_version::path(), path: system_version::path(),
version: Version::Bypassed, version: Version::Bypassed,
}) })
} else if let Some(alias_name) = requested_version.alias_name() { } else if let Some(alias_name) = requested_version.alias_name() {
let alias_path = config.aliases_dir().join(&alias_name); let alias_path = config.aliases_dir().join(&alias_name);
let system_path = system_version::path();
if matches!(fs::shallow_read_symlink(&alias_path), Ok(shallow_path) if shallow_path == system_path)
{
info!(
"Bypassing fnm: using {} node",
system_version::display_name().cyan()
);
Some(ApplicableVersion {
path: alias_path,
version: Version::Bypassed,
})
} else {
ensure!( ensure!(
alias_path.exists(), alias_path.exists(),
CantFindVersion { CantFindVersion {
@ -49,6 +66,7 @@ pub fn choose_version_for_user_input<'a>(
path: alias_path, path: alias_path,
version: Version::Alias(alias_name), version: Version::Alias(alias_name),
}) })
}
} else { } else {
let current_version = requested_version.to_version(&all_versions, config); let current_version = requested_version.to_version(&all_versions, config);
current_version.map(|version| { current_version.map(|version| {

3
src/commands/env.rs

@ -84,6 +84,9 @@ impl Command for Env {
if self.use_on_cd { if self.use_on_cd {
println!("{}", shell.use_on_cd(config)); println!("{}", shell.use_on_cd(config));
} }
if let Some(v) = shell.rehash() {
println!("{}", v);
}
Ok(()) Ok(())
} }
} }

24
src/commands/unalias.rs

@ -1,7 +1,9 @@
use super::command::Command; use super::command::Command;
use crate::config::FnmConfig;
use crate::fs::remove_symlink_dir; use crate::fs::remove_symlink_dir;
use snafu::{ensure, ResultExt, Snafu}; use crate::user_version::UserVersion;
use crate::version::Version;
use crate::{choose_version_for_user_input, config::FnmConfig};
use snafu::{OptionExt, ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
@ -13,15 +15,17 @@ impl Command for Unalias {
type Error = Error; type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let alias_path = config.aliases_dir().join(&self.requested_alias); let requested_version = choose_version_for_user_input::choose_version_for_user_input(
ensure!( &UserVersion::Full(Version::Alias(self.requested_alias.clone())),
alias_path.exists(), config,
AliasNotFound { )
requested_alias: self.requested_alias .ok()
} .flatten()
); .with_context(|| AliasNotFound {
requested_alias: self.requested_alias,
})?;
remove_symlink_dir(&alias_path).context(CantDeleteSymlink)?; remove_symlink_dir(&requested_version.path()).context(CantDeleteSymlink)?;
Ok(()) Ok(())
} }

11
src/commands/use.rs

@ -38,11 +38,16 @@ impl Command for Use {
.context(CantInferVersion)?; .context(CantInferVersion)?;
let version_path = if let UserVersion::Full(Version::Bypassed) = requested_version { let version_path = if let UserVersion::Full(Version::Bypassed) = requested_version {
outln!(config#Info, "Bypassing fnm: using {} node", "system".cyan()); outln!(config#Info, "Bypassing fnm: using {} node", system_version::display_name().cyan());
system_version::path() system_version::path()
} else if let Some(alias_name) = requested_version.alias_name() { } else if let Some(alias_name) = requested_version.alias_name() {
let alias_path = config.aliases_dir().join(&alias_name); let alias_path = config.aliases_dir().join(&alias_name);
if alias_path.exists() { let system_path = system_version::path();
if matches!(fs::shallow_read_symlink(&alias_path), Ok(shallow_path) if shallow_path == system_path)
{
outln!(config#Info, "Bypassing fnm: using {} node", system_version::display_name().cyan());
system_path
} else if alias_path.exists() {
outln!(config#Info, "Using Node for alias {}", alias_name.cyan()); outln!(config#Info, "Using Node for alias {}", alias_name.cyan());
alias_path alias_path
} else { } else {
@ -164,6 +169,8 @@ fn warn_if_multishell_path_not_in_path_env_var(
pub enum Error { pub enum Error {
#[snafu(display("Can't create the symlink: {}", source))] #[snafu(display("Can't create the symlink: {}", source))]
SymlinkingCreationIssue { source: std::io::Error }, SymlinkingCreationIssue { source: std::io::Error },
#[snafu(display("Can't read the symlink: {}", source))]
SymlinkReadFailed { source: std::io::Error },
#[snafu(display("{}", source))] #[snafu(display("{}", source))]
InstallError { source: <Install as Command>::Error }, InstallError { source: <Install as Command>::Error },
#[snafu(display("Can't get locally installed versions: {}", source))] #[snafu(display("Can't get locally installed versions: {}", source))]

4
src/fs.rs

@ -23,3 +23,7 @@ pub fn remove_symlink_dir<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
std::fs::remove_file(path)?; std::fs::remove_file(path)?;
Ok(()) Ok(())
} }
pub fn shallow_read_symlink<P: AsRef<Path>>(path: P) -> std::io::Result<std::path::PathBuf> {
std::fs::read_link(path)
}

3
src/shell/shell.rs

@ -5,6 +5,9 @@ pub trait Shell: Debug {
fn path(&self, path: &Path) -> String; fn path(&self, path: &Path) -> String;
fn set_env_var(&self, name: &str, value: &str) -> String; fn set_env_var(&self, name: &str, value: &str) -> String;
fn use_on_cd(&self, config: &crate::config::FnmConfig) -> String; fn use_on_cd(&self, config: &crate::config::FnmConfig) -> String;
fn rehash(&self) -> Option<String> {
None
}
fn to_structopt_shell(&self) -> structopt::clap::Shell; fn to_structopt_shell(&self) -> structopt::clap::Shell;
} }

4
src/shell/zsh.rs

@ -18,6 +18,10 @@ impl Shell for Zsh {
format!("export {}={:?}", name, value) format!("export {}={:?}", name, value)
} }
fn rehash(&self) -> Option<String> {
Some("rehash".to_string())
}
fn use_on_cd(&self, _config: &crate::config::FnmConfig) -> String { fn use_on_cd(&self, _config: &crate::config::FnmConfig) -> String {
indoc!( indoc!(
r#" r#"

4
src/system_version.rs

@ -9,3 +9,7 @@ pub fn path() -> PathBuf {
PathBuf::from(path_as_string) PathBuf::from(path_as_string)
} }
pub fn display_name() -> &'static str {
"system"
}

21
src/version.rs

@ -1,6 +1,7 @@
use crate::alias; use crate::alias;
use crate::config; use crate::config;
use crate::lts::LtsType; use crate::lts::LtsType;
use crate::system_version;
use std::str::FromStr; use std::str::FromStr;
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)] #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
@ -18,7 +19,7 @@ fn first_letter_is_number(s: &str) -> bool {
impl Version { impl Version {
pub fn parse<S: AsRef<str>>(version_str: S) -> Result<Self, semver::Error> { pub fn parse<S: AsRef<str>>(version_str: S) -> Result<Self, semver::Error> {
let lowercased = version_str.as_ref().to_lowercase(); let lowercased = version_str.as_ref().to_lowercase();
if lowercased == "system" { if lowercased == system_version::display_name() {
Ok(Self::Bypassed) Ok(Self::Bypassed)
} else if lowercased.starts_with("lts-") || lowercased.starts_with("lts/") { } else if lowercased.starts_with("lts-") || lowercased.starts_with("lts/") {
let lts_type = LtsType::from(&lowercased[4..]); let lts_type = LtsType::from(&lowercased[4..]);
@ -54,32 +55,26 @@ impl Version {
format!("{}", self) format!("{}", self)
} }
pub fn installation_path(&self, config: &config::FnmConfig) -> Option<std::path::PathBuf> { pub fn installation_path(&self, config: &config::FnmConfig) -> std::path::PathBuf {
match self { match self {
Self::Bypassed => None, Self::Bypassed => system_version::path(),
v @ (Self::Lts(_) | Self::Alias(_)) => { v @ (Self::Lts(_) | Self::Alias(_)) => {
Some(config.aliases_dir().join(v.alias_name().unwrap())) config.aliases_dir().join(v.alias_name().unwrap())
} }
v @ Self::Semver(_) => Some( v @ Self::Semver(_) => config
config
.installations_dir() .installations_dir()
.join(v.v_str()) .join(v.v_str())
.join("installation"), .join("installation"),
),
} }
} }
pub fn root_path(&self, config: &config::FnmConfig) -> Option<std::path::PathBuf> { pub fn root_path(&self, config: &config::FnmConfig) -> Option<std::path::PathBuf> {
match self.installation_path(config) { let path = self.installation_path(config);
None => None,
Some(path) => {
let mut canon_path = path.canonicalize().ok()?; let mut canon_path = path.canonicalize().ok()?;
canon_path.pop(); canon_path.pop();
Some(canon_path) Some(canon_path)
} }
} }
}
}
impl<'de> serde::Deserialize<'de> for Version { impl<'de> serde::Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@ -94,7 +89,7 @@ impl<'de> serde::Deserialize<'de> for Version {
impl std::fmt::Display for Version { impl std::fmt::Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Bypassed => write!(f, "system"), Self::Bypassed => write!(f, "{}", system_version::display_name()),
Self::Lts(lts) => write!(f, "lts-{}", lts), Self::Lts(lts) => write!(f, "lts-{}", lts),
Self::Semver(semver) => write!(f, "v{}", semver), Self::Semver(semver) => write!(f, "v{}", semver),
Self::Alias(alias) => write!(f, "{}", alias), Self::Alias(alias) => write!(f, "{}", alias),

14
tests/feature_tests/mod.rs

@ -238,3 +238,17 @@ mod unalias_error {
.then(OutputContains::new(IgnoreErrors::new(GetStderr::new(Call::new("fnm", vec!["unalias", "lts"]))), "Requested alias lts not found")) .then(OutputContains::new(IgnoreErrors::new(GetStderr::new(Call::new("fnm", vec!["unalias", "lts"]))), "Requested alias lts not found"))
}); });
} }
mod alias_system {
test_shell!(Bash, Zsh, Fish, PowerShell; {
EvalFnmEnv::default()
.then(Call::new("fnm", vec!["alias", "system", "my_system"]))
.then(OutputContains::new(Call::new("fnm", vec!["ls"]), "my_system"))
.then(Call::new("fnm", vec!["alias", "system", "default"]))
.then(Call::new("fnm", vec!["alias", "my_system", "my_system2"]))
.then(OutputContains::new(Call::new("fnm", vec!["ls"]), "my_system2"))
.then(OutputContains::new(Call::new("fnm", vec!["use", "my_system"]), "Bypassing fnm"))
.then(Call::new("fnm", vec!["unalias", "my_system"]))
.then(OutputContains::new(IgnoreErrors::new(GetStderr::new(Call::new("fnm", vec!["use", "my_system"]))), "Requested version my_system is not currently installed"))
});
}

17
tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Bash.snap

@ -0,0 +1,17 @@
---
source: tests/feature_tests/mod.rs
expression: "&source.trim()"
---
set -e
shopt -s expand_aliases
eval "$(fnm env)"
fnm alias system my_system
fnm ls | grep my_system
fnm alias system default
fnm alias my_system my_system2
fnm ls | grep my_system2
fnm use my_system | grep 'Bypassing fnm'
fnm unalias my_system
fnm use my_system 2>&1 | grep 'Requested version my_system is not currently installed'

14
tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Fish.snap

@ -0,0 +1,14 @@
---
source: tests/feature_tests/mod.rs
expression: "&source.trim()"
---
fnm env | source
fnm alias system my_system
fnm ls | grep my_system
fnm alias system default
fnm alias my_system my_system2
fnm ls | grep my_system2
fnm use my_system | grep 'Bypassing fnm'
fnm unalias my_system
fnm use my_system 2>&1 | grep 'Requested version my_system is not currently installed'

15
tests/feature_tests/snapshots/e2e__feature_tests__alias_system__PowerShell.snap

@ -0,0 +1,15 @@
---
source: tests/feature_tests/mod.rs
expression: "&source.trim()"
---
$ErrorActionPreference = "Stop"
fnm env | Out-String | Invoke-Expression
fnm alias system my_system
$($__out__ = $(fnm ls | Select-String 'my_system'); echo $__out__; if ($__out__ -eq $null){ exit 1 } else { $__out__ })
fnm alias system default
fnm alias my_system my_system2
$($__out__ = $(fnm ls | Select-String 'my_system2'); echo $__out__; if ($__out__ -eq $null){ exit 1 } else { $__out__ })
$($__out__ = $(fnm use my_system | Select-String 'Bypassing fnm'); echo $__out__; if ($__out__ -eq $null){ exit 1 } else { $__out__ })
fnm unalias my_system
$($__out__ = $($($_tmp_err_action = $ErrorActionPreference;$ErrorActionPreference = "Continue";fnm use my_system 2>&1;$ErrorActionPreference = $_tmp_err_action) | Select-String 'Requested version my_system is not currently installed'); echo $__out__; if ($__out__ -eq $null){ exit 1 } else { $__out__ })

15
tests/feature_tests/snapshots/e2e__feature_tests__alias_system__Zsh.snap

@ -0,0 +1,15 @@
---
source: tests/feature_tests/mod.rs
expression: "&source.trim()"
---
set -e
eval "$(fnm env)"
fnm alias system my_system
fnm ls | grep my_system
fnm alias system default
fnm alias my_system my_system2
fnm ls | grep my_system2
fnm use my_system | grep 'Bypassing fnm'
fnm unalias my_system
fnm use my_system 2>&1 | grep 'Requested version my_system is not currently installed'
Loading…
Cancel
Save