Add use command and mapper

feature/lookup-installed
trivernis 1 year ago
parent a39df138c9
commit 1bd3e79cde
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -15,6 +15,8 @@ path = "src/main.rs"
[dependencies]
clap = { version = "4.1.1", features = ["derive"] }
color-eyre = "0.6.2"
crossterm = "0.25.0"
dialoguer = "0.10.3"
dirs = "4.0.0"
futures-util = "0.3.25"
indicatif = "0.17.3"
@ -24,6 +26,7 @@ miette = "5.5.0"
reqwest = { version = "0.11.14", features = ["json", "stream"] }
semver = { version = "1.0.16", features = ["std", "serde"] }
serde = { version = "1.0.152", features = ["derive"] }
set_env = "1.3.4"
tar = "0.4.38"
thiserror = "1.0.38"
tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time"] }

@ -1,6 +1,7 @@
use std::str::FromStr;
use clap::{Parser, Subcommand};
use nenv::repository::NodeVersion;
use semver::VersionReq;
#[derive(Clone, Debug, Parser)]
@ -28,41 +29,11 @@ pub enum Command {
#[derive(Clone, Debug, Parser)]
pub struct InstallArgs {
#[arg()]
pub version: Version,
pub version: NodeVersion,
}
#[derive(Clone, Debug, Parser)]
pub struct UseArgs {
#[arg()]
pub version: Version,
}
impl FromStr for Version {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let input = s.to_lowercase();
let version = match &*input {
"latest" => Self::Latest,
"lts" => Self::LatestLts,
_ => {
if let Ok(req) = VersionReq::parse(s) {
Self::Req(req)
} else {
Self::Lts(s.to_lowercase())
}
}
};
Ok(version)
}
}
#[derive(Clone, Debug)]
pub enum Version {
Latest,
LatestLts,
Lts(String),
Req(VersionReq),
pub version: NodeVersion,
}

@ -4,6 +4,7 @@ use miette::Diagnostic;
use thiserror::Error;
use crate::{
mapper::error::MapperError,
repository::{config::ConfigError, extract::ExtractError},
web_api::error::ApiError,
};
@ -37,6 +38,13 @@ pub enum Error {
#[diagnostic_source]
ConfigError,
),
#[error("Mapper failed: {0}")]
Mapper(
#[from]
#[source]
#[diagnostic_source]
MapperError,
),
#[error("IO Error: {0}")]
Io(#[from] io::Error),

@ -1,16 +1,60 @@
use crossterm::style::Stylize;
use mapper::Mapper;
use repository::{config::Config, NodeVersion, Repository};
mod consts;
pub mod error;
pub mod mapper;
pub mod repository;
mod utils;
mod web_api;
use dialoguer::Confirm;
use error::Result;
pub async fn install_version(version: NodeVersion) -> Result<()> {
get_repository().await?.install_version(version).await
let repo = get_repository().await?;
if repo.is_installed(&version).await? {
if !Confirm::new()
.with_prompt("The version {version} is already installed. Reinstall?")
.default(false)
.interact()
.unwrap()
{
return Ok(());
}
}
repo.install_version(&version).await?;
println!("Installed {}", version.to_string().bold());
Ok(())
}
pub async fn use_version(version: NodeVersion) -> Result<()> {
let mut mapper = get_mapper().await?;
if !mapper.repository().is_installed(&version).await?
&& Confirm::new()
.with_prompt(format!(
"The version {version} is not installed. Do you want to install it?"
))
.default(false)
.interact()
.unwrap()
{
mapper.repository().install_version(&version).await?;
}
mapper.use_version(&version).await?;
println!("Now using {}", version.to_string().bold());
Ok(())
}
async fn get_repository() -> Result<Repository> {
Repository::init(Config::load().await?).await
}
async fn get_mapper() -> Result<Mapper> {
Ok(Mapper::new(get_repository().await?))
}

@ -10,20 +10,9 @@ async fn main() {
color_eyre::install().unwrap();
let args: Args = Args::parse();
match args.commmand {
args::Command::Install(v) => nenv::install_version(version_to_req(v.version))
.await
.unwrap(),
args::Command::Use(_) => todo!(),
args::Command::Install(v) => nenv::install_version(v.version).await.unwrap(),
args::Command::Use(v) => nenv::use_version(v.version).await.unwrap(),
args::Command::Default => todo!(),
args::Command::Version => todo!(),
};
}
fn version_to_req(version: args::Version) -> NodeVersion {
match version {
args::Version::Latest => NodeVersion::Latest,
args::Version::LatestLts => NodeVersion::LatestLts,
args::Version::Req(req) => NodeVersion::Req(req),
args::Version::Lts(lts_name) => NodeVersion::Lts(lts_name),
}
}

@ -0,0 +1,17 @@
use miette::Diagnostic;
use thiserror::Error;
use crate::repository::config::ConfigError;
pub type MapperResult<T> = Result<T, MapperError>;
#[derive(Error, Diagnostic, Debug)]
pub enum MapperError {
#[error("Config error: {0}")]
Config(
#[from]
#[source]
#[diagnostic_source]
ConfigError,
),
}

@ -0,0 +1,49 @@
use std::{env, str::FromStr};
use crate::repository::{NodeVersion, Repository};
use self::error::MapperResult;
pub mod error;
/// Responsible for mapping to node executables
/// and managing node versions
pub struct Mapper {
repo: Repository,
active_version: NodeVersion,
}
impl Mapper {
pub fn new(repository: Repository) -> Self {
let version =
Self::get_version().unwrap_or_else(|| repository.config.default_version.to_owned());
Self {
repo: repository,
active_version: version,
}
}
pub fn repository(&self) -> &Repository {
&self.repo
}
/// Sets the given version as the default one
pub async fn use_version(&mut self, version: &NodeVersion) -> MapperResult<()> {
self.repo
.config
.set_default_version(version.clone())
.await?;
self.active_version = version.clone();
Ok(())
}
pub fn active_version(&self) -> &NodeVersion {
&self.active_version
}
fn get_version() -> Option<NodeVersion> {
env::var("NODE_VERSION")
.ok()
.and_then(|v| NodeVersion::from_str(&v).ok())
}
}

@ -7,10 +7,13 @@ use tokio::fs;
use crate::consts::{CFG_FILE_PATH, NODE_DIST_URL};
use super::NodeVersion;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config {
pub dist_base_url: String,
pub default_version: String,
#[serde(with = "NodeVersion")]
pub default_version: NodeVersion,
}
pub type ConfigResult<T> = Result<T, ConfigError>;
@ -41,7 +44,7 @@ impl Default for Config {
fn default() -> Self {
Self {
dist_base_url: String::from(NODE_DIST_URL),
default_version: String::from("latest"),
default_version: NodeVersion::LatestLts,
}
}
}
@ -51,7 +54,7 @@ impl Config {
pub async fn load() -> ConfigResult<Self> {
if !CFG_FILE_PATH.exists() {
let cfg = Config::default();
fs::write(&*CFG_FILE_PATH, toml::to_string_pretty(&cfg)?).await?;
cfg.save().await?;
Ok(cfg)
} else {
@ -61,4 +64,15 @@ impl Config {
Ok(cfg)
}
}
pub async fn save(&self) -> ConfigResult<()> {
fs::write(&*CFG_FILE_PATH, toml::to_string_pretty(&self)?).await?;
Ok(())
}
pub async fn set_default_version(&mut self, default_version: NodeVersion) -> ConfigResult<()> {
self.default_version = default_version;
self.save().await
}
}

@ -1,6 +1,11 @@
use std::path::{Path, PathBuf};
use core::fmt;
use std::{
path::{Path, PathBuf},
str::FromStr,
};
use semver::{Version, VersionReq};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use tokio::{
fs::{self, File},
io::BufWriter,
@ -18,6 +23,7 @@ pub mod config;
pub(crate) mod extract;
pub mod versions;
#[derive(Clone, Debug)]
pub enum NodeVersion {
Latest,
LatestLts,
@ -25,10 +31,55 @@ pub enum NodeVersion {
Req(VersionReq),
}
impl FromStr for NodeVersion {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let input = s.to_lowercase();
let version = match &*input {
"latest" => Self::Latest,
"lts" => Self::LatestLts,
_ => {
if let Ok(req) = VersionReq::parse(s) {
Self::Req(req)
} else {
Self::Lts(s.to_lowercase())
}
}
};
Ok(version)
}
}
impl NodeVersion {
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = String::deserialize(deserializer)?;
Self::from_str(&string).map_err(serde::de::Error::custom)
}
pub fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}
impl fmt::Display for NodeVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NodeVersion::Latest => String::from("latest"),
NodeVersion::LatestLts => String::from("lts"),
NodeVersion::Lts(name) => name.to_owned(),
NodeVersion::Req(req) => req.to_string(),
}
.fmt(f)
}
}
pub struct Repository {
versions: Versions,
web_api: WebApi,
config: Config,
pub config: Config,
}
impl Repository {
@ -62,9 +113,30 @@ impl Repository {
Ok(())
}
/// Returns a list of installed versions
pub async fn installed_versions(&self) -> LibResult<Vec<Version>> {
let mut versions = Vec::new();
let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await?;
while let Some(entry) = iter.next_entry().await? {
if let Ok(version) = Version::parse(entry.file_name().to_string_lossy().as_ref()) {
versions.push(version);
};
}
Ok(versions)
}
/// Returns if the given version is installed
pub async fn is_installed(&self, version: &NodeVersion) -> LibResult<bool> {
let info = self.parse_req(version);
Ok(self.installed_versions().await?.contains(&info.version))
}
/// Installs a specified node version
pub async fn install_version(&self, version_req: NodeVersion) -> LibResult<()> {
let info = self.parse_req(version_req);
pub async fn install_version(&self, version_req: &NodeVersion) -> LibResult<()> {
let info = self.parse_req(&version_req);
let archive_path = self.download_version(&info.version).await?;
self.extract_archive(info, &archive_path)?;
@ -92,7 +164,7 @@ impl Repository {
Ok(())
}
fn parse_req(&self, version_req: NodeVersion) -> &VersionInfo {
fn parse_req(&self, version_req: &NodeVersion) -> &VersionInfo {
match version_req {
NodeVersion::Latest => self.versions.latest(),
NodeVersion::LatestLts => self.versions.latest_lts(),

Loading…
Cancel
Save