From 1a4c95db0ab98d3be4252f5277da6d217094f4f7 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 4 Jul 2022 19:37:46 +0200 Subject: [PATCH 1/6] rename binaries from nvm-rust to nvm --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/release.yml | 8 ++++---- Cargo.toml | 4 ++++ tests/utils.rs | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62b3dc0..b833e77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,11 +15,11 @@ jobs: matrix: include: - os: macos-latest - file-name: nvm-rust + file-name: nvm - os: ubuntu-latest - file-name: nvm-rust + file-name: nvm - os: windows-latest - file-name: nvm-rust.exe + file-name: nvm.exe runs-on: ${{ matrix.os }} @@ -53,7 +53,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v3 with: - name: build-${{ matrix.os }} + name: nvm-${{ matrix.os }} path: target/release/${{ matrix.file-name }} test: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c975dfb..e87eb71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ on: - workflow_dispatch: + workflow_dispatch: push: tags: - v* @@ -54,13 +54,13 @@ jobs: matrix: include: - os: macos-latest - file-name: nvm-rust + file-name: nvm display-name: nvm-macos - os: ubuntu-latest - file-name: nvm-rust + file-name: nvm display-name: nvm-linux - os: windows-latest - file-name: nvm-rust.exe + file-name: nvm.exe display-name: nvm-win.exe runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index 1d353de..1cf232f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,10 @@ exclude = [ "test-data/", ] +[[bin]] +name = "nvm" +path = "src/main.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/tests/utils.rs b/tests/utils.rs index 37e0c27..cd0f488 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -29,7 +29,7 @@ fn integration_dir() -> TempDir { pub fn setup_integration_test() -> Result<(TempDir, Command)> { let temp_dir = integration_dir(); - let mut cmd = Command::cargo_bin("nvm-rust").expect("Could not create Command"); + let mut cmd = Command::cargo_bin("nvm").expect("Could not create Command"); cmd.args(&["--dir", &temp_dir.to_string_lossy()]); Result::Ok((temp_dir, cmd)) From 5cbe39530032abb8f20480e1a0f3de8b5f3a1f07 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 4 Jul 2022 19:59:33 +0200 Subject: [PATCH 2/6] restore `ls --local` functionality --- src/subcommand/list.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index fc5d7d4..086da92 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref}; use anyhow::Result; use clap::{AppSettings, Parser}; +use itertools::Itertools; use node_semver::Range; use crate::{ @@ -61,6 +62,18 @@ impl Action for ListCommand { installed_versions = node_version::filter_version_req(installed_versions, filter); } + if options.installed { + println!( + "{}", + installed_versions + .iter() + .map(|version| version.to_string()) + .join("\n") + ); + + return Ok(()); + } + let mut latest_per_major: HashMap = HashMap::new(); let online_versions = OnlineNodeVersion::fetch_all()?; if !online_versions.is_empty() { @@ -88,6 +101,6 @@ impl Action for ListCommand { .collect(); println!("{}", lines.join("\n")); - Result::Ok(()) + Ok(()) } } From 8dc14e11b0045096230d8d7b0c3a574f55232725 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 4 Jul 2022 19:59:41 +0200 Subject: [PATCH 3/6] remove `ls --online` --- src/subcommand/list.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index 086da92..e0b923f 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -43,9 +43,6 @@ pub struct ListCommand { /// Only display installed versions #[clap(short, long)] pub installed: bool, - /// Only display available versions - #[clap(short, long, takes_value(false))] - pub online: bool, /// Filter by semantic versions. /// /// `12`, `^10.9`, `>=8.10`, `>=8, <9` From 77ea1a28d9010b32ad89b85c62a5cbfc94123583 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 4 Jul 2022 20:01:57 +0200 Subject: [PATCH 4/6] rename `ls --installed` to `--local`, add alias for old flag --- src/subcommand/list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index e0b923f..d7b3ca1 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -41,8 +41,8 @@ setting = AppSettings::ColoredHelp )] pub struct ListCommand { /// Only display installed versions - #[clap(short, long)] - pub installed: bool, + #[clap(short, long, alias="installed")] + pub local: bool, /// Filter by semantic versions. /// /// `12`, `^10.9`, `>=8.10`, `>=8, <9` @@ -59,7 +59,7 @@ impl Action for ListCommand { installed_versions = node_version::filter_version_req(installed_versions, filter); } - if options.installed { + if options.local { println!( "{}", installed_versions From 77b33762423073cabae0f8aa32d3e2bbb40d941c Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 5 Jul 2022 16:03:06 +0200 Subject: [PATCH 5/6] rewrite ls --- src/node_version.rs | 40 ++++++++++++----- src/subcommand/list.rs | 99 +++++++++++++++++++++++++++++------------- 2 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/node_version.rs b/src/node_version.rs index b721ad2..336050b 100644 --- a/src/node_version.rs +++ b/src/node_version.rs @@ -1,5 +1,6 @@ use std::{ borrow::Borrow, + cmp::Ordering, collections::HashMap, fs::{read_link, remove_dir_all}, path::PathBuf, @@ -16,6 +17,26 @@ pub trait NodeVersion { fn version(&self) -> &Version; } +impl PartialEq for dyn NodeVersion { + fn eq(&self, other: &Self) -> bool { + todo!() + } +} + +impl Eq for dyn NodeVersion {} + +impl PartialOrd for dyn NodeVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.version().cmp(other.version())) + } +} + +impl Ord for dyn NodeVersion { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.version().cmp(other.version()) + } +} + pub fn is_version_range(value: &str) -> Result { Range::parse(value).context(value.to_string()) } @@ -27,8 +48,8 @@ pub fn filter_version_req(versions: Vec, version_range: &Rang .collect() } -pub fn get_latest_of_each_major<'p, V: NodeVersion>(versions: &'p [V]) -> HashMap { - let mut map: HashMap = HashMap::new(); +pub fn get_latest_of_each_major(versions: &Vec) -> Vec<&V> { + let mut map: HashMap = HashMap::new(); for version in versions.iter() { let entry = map.get_mut(&version.version().major); @@ -39,7 +60,7 @@ pub fn get_latest_of_each_major<'p, V: NodeVersion>(versions: &'p [V]) -> HashMa map.insert(version.version().major, version); } - map + map.values().cloned().collect() } /// Handles `vX.X.X` prefixes @@ -54,7 +75,7 @@ fn parse_version_str(version_str: &str) -> Result { Version::parse(clean_version).context(version_str.to_owned()) } -#[derive(Clone, Deserialize, Debug, Eq, PartialEq)] +#[derive(Clone, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd)] #[serde(rename_all(deserialize = "snake_case"))] pub struct OnlineNodeVersion { #[serde()] @@ -77,11 +98,7 @@ impl OnlineNodeVersion { pub fn get_download_url(&self) -> Result { let file_name = self.get_file(); - let url = format!( - "https://nodejs.org/dist/v{}/{}", - self.version, - file_name - ); + let url = format!("https://nodejs.org/dist/v{}/{}", self.version, file_name); Url::parse(&url).context(format!("Could not create a valid download url. [{}]", url)) } @@ -263,11 +280,12 @@ impl NodeVersion for InstalledNodeVersion { #[cfg(test)] mod tests { - mod online_version { - use crate::node_version::OnlineNodeVersion; + mod online_version { use anyhow::Result; use node_semver::Version; + use crate::node_version::OnlineNodeVersion; + #[test] fn can_parse_version_data() -> Result<()> { let expected = OnlineNodeVersion { diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index d7b3ca1..dbd7bc0 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -1,5 +1,3 @@ -use std::{collections::HashMap, ops::Deref}; - use anyhow::Result; use clap::{AppSettings, Parser}; use itertools::Itertools; @@ -12,24 +10,39 @@ use crate::{ Config, }; -enum VersionStatus { - Outdated(OnlineNodeVersion), +enum VersionStatus<'p> { Latest, - Unknown, + NotInstalled, + Outdated(&'p OnlineNodeVersion), } -fn emoji_from(status: &VersionStatus) -> char { - match status { - VersionStatus::Outdated(_) => '⏫', - _ => '✅', +impl<'p> VersionStatus<'p> { + fn from(versions: &[&T], latest: &'p OnlineNodeVersion) -> VersionStatus<'p> { + if versions.is_empty() { + VersionStatus::NotInstalled + } else if versions + .iter() + .all(|version| version.version() < latest.version()) + { + VersionStatus::Outdated(latest) + } else { + VersionStatus::Latest + } + } + + fn to_emoji(&self) -> char { + match self { + VersionStatus::Latest => '✅', + VersionStatus::NotInstalled => '〰', + VersionStatus::Outdated(_) => '⏫', + } } -} -fn latest_version_string_from(status: &VersionStatus) -> String { - match status { - VersionStatus::Outdated(version) => format!("-> {}", version.to_string()), - VersionStatus::Latest => "".to_string(), - _ => "-> unknown".to_string(), + fn to_version_string(&self) -> String { + match self { + VersionStatus::Outdated(version) => format!("-> {}", version.to_string()), + _ => "".to_string(), + } } } @@ -41,7 +54,7 @@ setting = AppSettings::ColoredHelp )] pub struct ListCommand { /// Only display installed versions - #[clap(short, long, alias="installed")] + #[clap(short, long, alias = "installed")] pub local: bool, /// Filter by semantic versions. /// @@ -71,33 +84,59 @@ impl Action for ListCommand { return Ok(()); } - let mut latest_per_major: HashMap = HashMap::new(); + // Get available versions, extract only the latest for each major version + let mut latest_per_major = Vec::<&OnlineNodeVersion>::new(); let online_versions = OnlineNodeVersion::fetch_all()?; if !online_versions.is_empty() { latest_per_major = node_version::get_latest_of_each_major(&online_versions); + latest_per_major.sort(); + latest_per_major.reverse(); + } + + let majors_and_installed_versions: Vec<(&OnlineNodeVersion, Vec<&InstalledNodeVersion>)> = + latest_per_major + .into_iter() + .map(|latest| { + ( + latest, + installed_versions + .iter() + .filter(|installed| installed.version().major == latest.version().major) + .collect(), + ) + }) + .collect(); + + // Show the latest X major versions by default + // and show any older, installed versions as well + let mut versions_to_show = Vec::<(&OnlineNodeVersion, &Vec<&InstalledNodeVersion>)>::new(); + for (i, (latest, installed)) in majors_and_installed_versions.iter().enumerate() { + if i < 5 || !installed.is_empty() { + versions_to_show.push((latest, installed)); + } } - let lines: Vec = installed_versions + let output = versions_to_show .iter() - .map(|version| { - let version_status = match latest_per_major.get(&version.version().major) { - Some(latest) if latest.version().gt(version.version()) => { - VersionStatus::Outdated(latest.deref().clone()) - }, - Some(_) => VersionStatus::Latest, - None => VersionStatus::Unknown, + .map(|(online_version, installed_versions)| { + let version_status = VersionStatus::from(installed_versions, online_version); + + let version_to_show = if installed_versions.is_empty() { + online_version.to_string() + } else { + installed_versions[0].to_string() }; format!( "{} {} {}", - emoji_from(&version_status), - version.to_string(), - latest_version_string_from(&version_status) + &version_status.to_emoji(), + version_to_show.to_string(), + &version_status.to_version_string(), ) }) - .collect(); + .join("\n"); - println!("{}", lines.join("\n")); + println!("{output}"); Ok(()) } } From fccaebc3801faf40a02a7e6293992a825a256673 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 5 Jul 2022 16:06:06 +0200 Subject: [PATCH 6/6] fix lint warnings --- src/node_version.rs | 16 ++++++++-------- src/subcommand/list.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/node_version.rs b/src/node_version.rs index 336050b..1146928 100644 --- a/src/node_version.rs +++ b/src/node_version.rs @@ -19,7 +19,7 @@ pub trait NodeVersion { impl PartialEq for dyn NodeVersion { fn eq(&self, other: &Self) -> bool { - todo!() + self.version().eq(other.version()) } } @@ -32,7 +32,7 @@ impl PartialOrd for dyn NodeVersion { } impl Ord for dyn NodeVersion { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> Ordering { self.version().cmp(other.version()) } } @@ -48,7 +48,7 @@ pub fn filter_version_req(versions: Vec, version_range: &Rang .collect() } -pub fn get_latest_of_each_major(versions: &Vec) -> Vec<&V> { +pub fn get_latest_of_each_major(versions: &[V]) -> Vec<&V> { let mut map: HashMap = HashMap::new(); for version in versions.iter() { @@ -189,7 +189,7 @@ impl InstalledNodeVersion { remove_dir_all(self.get_dir_path(config))?; println!("Uninstalled {}!", self.version()); - Result::Ok(()) + Ok(()) } /// Checks that all the required files are present in the installation dir @@ -210,7 +210,7 @@ impl InstalledNodeVersion { ); } - Result::Ok(()) + Ok(()) } // Static functions @@ -235,7 +235,7 @@ impl InstalledNodeVersion { let entry = entry.unwrap(); let result = parse_version_str(entry.file_name().to_string_lossy().as_ref()); - if let Result::Ok(version) = result { + if let Ok(version) = result { version_dirs.push(version); } } @@ -280,7 +280,7 @@ impl NodeVersion for InstalledNodeVersion { #[cfg(test)] mod tests { - mod online_version { + mod online_version { use anyhow::Result; use node_semver::Version; @@ -360,7 +360,7 @@ mod tests { assert_eq!(expected, result); - Result::Ok(()) + Ok(()) } } } diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index dbd7bc0..e01976a 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -130,7 +130,7 @@ impl Action for ListCommand { format!( "{} {} {}", &version_status.to_emoji(), - version_to_show.to_string(), + version_to_show, &version_status.to_version_string(), ) })