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. 37
      src/alias.rs
  2. 42
      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. 33
      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

37
src/alias.rs

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
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 std::convert::TryInto;
use std::path::PathBuf;
@ -12,15 +13,10 @@ pub fn create_alias( @@ -12,15 +13,10 @@ pub fn create_alias(
let aliases_dir = config.aliases_dir();
std::fs::create_dir_all(&aliases_dir)?;
let version_dir = version
.installation_path(config)
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?;
let version_dir = version.installation_path(config);
let alias_dir = aliases_dir.join(common_name);
if alias_dir.exists() {
remove_symlink_dir(&alias_dir)?;
}
remove_symlink_dir(&alias_dir).ok();
symlink_dir(&version_dir, &alias_dir)?;
Ok(())
@ -44,7 +40,12 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path { @@ -44,7 +40,12 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path {
type Error = std::io::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 {
alias_path: PathBuf::from(self),
destination_path,
@ -54,13 +55,17 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path { @@ -54,13 +55,17 @@ impl std::convert::TryInto<StoredAlias> for &std::path::Path {
impl StoredAlias {
pub fn s_ver(&self) -> &str {
self.destination_path
.parent()
.unwrap()
.file_name()
.expect("must have basename")
.to_str()
.unwrap()
if self.destination_path == system_version::path() {
system_version::display_name()
} else {
self.destination_path
.parent()
.unwrap()
.file_name()
.expect("must have basename")
.to_str()
.unwrap()
}
}
pub fn name(&self) -> &str {

42
src/choose_version_for_user_input.rs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
use crate::config::FnmConfig;
use crate::fs;
use crate::installed_versions;
use crate::system_version;
use crate::user_version::UserVersion;
@ -8,6 +9,7 @@ use log::info; @@ -8,6 +9,7 @@ use log::info;
use snafu::{ensure, ResultExt, Snafu};
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct ApplicableVersion {
path: PathBuf,
version: Version,
@ -31,24 +33,40 @@ pub fn choose_version_for_user_input<'a>( @@ -31,24 +33,40 @@ pub fn choose_version_for_user_input<'a>(
installed_versions::list(config.installations_dir()).context(VersionListing)?;
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 {
path: system_version::path(),
version: Version::Bypassed,
})
} else if let Some(alias_name) = requested_version.alias_name() {
let alias_path = config.aliases_dir().join(&alias_name);
ensure!(
alias_path.exists(),
CantFindVersion {
requested_version: requested_version.clone()
}
);
info!("Using Node for alias {}", alias_name.cyan());
Some(ApplicableVersion {
path: alias_path,
version: Version::Alias(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!(
alias_path.exists(),
CantFindVersion {
requested_version: requested_version.clone()
}
);
info!("Using Node for alias {}", alias_name.cyan());
Some(ApplicableVersion {
path: alias_path,
version: Version::Alias(alias_name),
})
}
} else {
let current_version = requested_version.to_version(&all_versions, config);
current_version.map(|version| {

3
src/commands/env.rs

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

24
src/commands/unalias.rs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
use super::command::Command;
use crate::config::FnmConfig;
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;
#[derive(StructOpt, Debug)]
@ -13,15 +15,17 @@ impl Command for Unalias { @@ -13,15 +15,17 @@ impl Command for Unalias {
type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let alias_path = config.aliases_dir().join(&self.requested_alias);
ensure!(
alias_path.exists(),
AliasNotFound {
requested_alias: self.requested_alias
}
);
let requested_version = choose_version_for_user_input::choose_version_for_user_input(
&UserVersion::Full(Version::Alias(self.requested_alias.clone())),
config,
)
.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(())
}

11
src/commands/use.rs

@ -38,11 +38,16 @@ impl Command for Use { @@ -38,11 +38,16 @@ impl Command for Use {
.context(CantInferVersion)?;
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()
} else if let Some(alias_name) = requested_version.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());
alias_path
} else {
@ -164,6 +169,8 @@ fn warn_if_multishell_path_not_in_path_env_var( @@ -164,6 +169,8 @@ fn warn_if_multishell_path_not_in_path_env_var(
pub enum Error {
#[snafu(display("Can't create the symlink: {}", source))]
SymlinkingCreationIssue { source: std::io::Error },
#[snafu(display("Can't read the symlink: {}", source))]
SymlinkReadFailed { source: std::io::Error },
#[snafu(display("{}", source))]
InstallError { source: <Install as Command>::Error },
#[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<()> { @@ -23,3 +23,7 @@ pub fn remove_symlink_dir<P: AsRef<Path>>(path: P) -> std::io::Result<()> {
std::fs::remove_file(path)?;
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 { @@ -5,6 +5,9 @@ pub trait Shell: Debug {
fn path(&self, path: &Path) -> String;
fn set_env_var(&self, name: &str, value: &str) -> 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;
}

4
src/shell/zsh.rs

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

4
src/system_version.rs

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

33
src/version.rs

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

14
tests/feature_tests/mod.rs

@ -238,3 +238,17 @@ mod unalias_error { @@ -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"))
});
}
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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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