Add downloading to install command

feature/lookup-installed
trivernis 1 year ago
parent 4abc55d7c1
commit 5238fb57e0
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -1,6 +1,7 @@
use std::str::FromStr;
use clap::{Parser, Subcommand};
use semver::VersionReq;
#[derive(Clone, Debug, Parser)]
#[clap(infer_subcommands = true)]
@ -44,8 +45,14 @@ impl FromStr for Version {
let version = match &*input {
"latest" => Self::Latest,
"lts" => Self::Lts,
_ => Self::SemVer(SemVersion::from_str(s)?),
"lts" => Self::LatestLts,
_ => {
if let Ok(req) = VersionReq::parse(s) {
Self::Req(req)
} else {
Self::Lts(s.to_lowercase())
}
}
};
Ok(version)
@ -55,40 +62,7 @@ impl FromStr for Version {
#[derive(Clone, Debug)]
pub enum Version {
Latest,
Lts,
SemVer(SemVersion),
}
#[derive(Clone, Debug)]
pub struct SemVersion {
pub major: u8,
pub minor: Option<u8>,
pub patch: Option<u16>,
}
impl FromStr for SemVersion {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut major = s;
let mut minor = None;
let mut patch = None;
if let Some((maj, rest)) = s.split_once('.') {
major = maj;
if let Some((min, pat)) = rest.split_once('.') {
minor = Some(min.parse().map_err(|_| "minor is not a number")?);
patch = Some(pat.parse().map_err(|_| "patch is not a number")?);
} else {
minor = Some(rest.parse().map_err(|_| "minor is not a number")?);
}
}
Ok(Self {
major: major.parse().map_err(|_| "major is not a number")?,
minor,
patch,
})
}
LatestLts,
Lts(String),
Req(VersionReq),
}

@ -10,8 +10,11 @@ lazy_static! {
pub static ref DATA_DIR: PathBuf = dirs::data_dir()
.unwrap_or_else(|| PathBuf::from(".data"))
.join(PathBuf::from("nenv"));
pub static ref NODE_PATH: PathBuf = DATA_DIR.join(PathBuf::from("current"));
pub static ref NODE_VERSIONS_PATH: PathBuf = DATA_DIR.join(PathBuf::from("versions"));
pub static ref CACHE_DIR: PathBuf = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from(".cache"))
.join(PathBuf::from("nenv"));
pub static ref BIN_DIR: PathBuf = DATA_DIR.join(PathBuf::from("bin"));
pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join(PathBuf::from("versions"));
pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}");
}

@ -1,5 +0,0 @@
use crate::{error::LibResult, Version};
pub async fn download_version(version: Version) -> LibResult<()> {
todo!("Download node version to data dir")
}

@ -1,9 +1,25 @@
use std::io;
use miette::Diagnostic;
use thiserror::Error;
use crate::web_api::error::ApiError;
pub(crate) type LibResult<T> = Result<T>;
pub(crate) type LibError = Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {}
#[derive(Debug, Error, Diagnostic)]
pub enum Error {
#[error("Failed to call nodejs.com api: {0}")]
Web(
#[from]
#[source]
#[diagnostic_source]
ApiError,
),
#[error("IO Error: {0}")]
Io(#[from] io::Error),
}

@ -1,13 +1,15 @@
use repository::{config::Config, NodeVersion, Repository};
mod consts;
mod download;
pub mod error;
mod utils;
pub mod repository;
mod web_api;
use error::Result;
pub enum Version {
Latest,
Lts,
Specific(u8, Option<u8>, Option<u16>),
pub async fn install_version(version: NodeVersion) -> Result<()> {
get_repository().await?.install_version(version).await
}
pub fn install(version: Version) {}
async fn get_repository() -> Result<Repository> {
Repository::init(Config::default()).await
}

@ -1,11 +1,29 @@
use args::Args;
use clap::Parser;
use nenv::repository::NodeVersion;
mod args;
#[tokio::main(flavor = "current_thread")]
async fn main() {
color_eyre::install().unwrap();
let args: Args = Args::parse();
dbg!(args);
match args.commmand {
args::Command::Install(v) => nenv::install_version(version_to_req(v.version))
.await
.unwrap(),
args::Command::Use(_) => todo!(),
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,13 @@
pub struct Config {
pub dist_base_url: String,
pub default_version: String,
}
impl Default for Config {
fn default() -> Self {
Self {
dist_base_url: String::from("https://nodejs.org/dist"),
default_version: String::from("latest"),
}
}
}

@ -0,0 +1,92 @@
use std::path::PathBuf;
use semver::{Version, VersionReq};
use tokio::{
fs::{self, File},
io::BufWriter,
};
use crate::{
consts::{BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR},
error::LibResult,
web_api::{VersionInfo, WebApi},
};
use self::{config::Config, versions::Versions};
pub mod config;
pub mod versions;
pub enum NodeVersion {
Latest,
LatestLts,
Lts(String),
Req(VersionReq),
}
pub struct Repository {
versions: Versions,
web_api: WebApi,
config: Config,
}
impl Repository {
/// Initializes a new repository with the given confi
pub async fn init(config: Config) -> LibResult<Self> {
Self::create_folders().await?;
let web_api = WebApi::new(&config.dist_base_url);
let all_versions = web_api.get_versions().await?;
Ok(Self {
config,
web_api,
versions: Versions::new(all_versions),
})
}
async fn create_folders() -> LibResult<()> {
let dirs = vec![
&*CFG_DIR,
&*DATA_DIR,
&*CACHE_DIR,
&*BIN_DIR,
&*NODE_VERSIONS_DIR,
];
for dir in dirs {
if !dir.exists() {
fs::create_dir_all(dir).await?;
}
}
Ok(())
}
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?;
todo!()
}
async fn download_version(&self, version: &Version) -> LibResult<PathBuf> {
let download_path = CACHE_DIR.join(format!("node-v{}{}", version, *NODE_ARCHIVE_SUFFIX));
let mut download_writer = BufWriter::new(File::create(&download_path).await?);
self.web_api
.download_version(version.to_string(), &mut download_writer)
.await?;
Ok(download_path)
}
fn parse_req(&self, version_req: NodeVersion) -> &VersionInfo {
match version_req {
NodeVersion::Latest => self.versions.latest(),
NodeVersion::LatestLts => self.versions.latest_lts(),
NodeVersion::Lts(lts) => self.versions.get_lts(&lts).expect("Version not found"),
NodeVersion::Req(req) => self
.versions
.get_fulfilling(&req)
.expect("Version not found"),
}
}
}

@ -0,0 +1,73 @@
use std::collections::HashMap;
use semver::{Version, VersionReq};
use crate::web_api::VersionInfo;
pub struct Versions {
lts_versions: HashMap<String, VersionReq>,
versions: HashMap<Version, VersionInfo>,
}
impl Versions {
/// creates a new instance to access version information
pub fn new(all_versions: Vec<VersionInfo>) -> Self {
let lts_versions = all_versions
.iter()
.filter_map(|v| {
Some((
v.lts.as_ref()?.to_lowercase(),
VersionReq::parse(&format!("{}", v.version.major)).ok()?,
))
})
.collect::<HashMap<_, _>>();
let versions = all_versions
.into_iter()
.map(|v| (v.version.to_owned(), v))
.collect::<HashMap<_, _>>();
Self {
lts_versions,
versions,
}
}
/// Returns the latest known node version
pub fn latest(&self) -> &VersionInfo {
let mut versions = self.versions.keys().collect::<Vec<_>>();
versions.sort();
self.versions
.get(versions.last().expect("No known node versions"))
.unwrap()
}
/// Returns the latest node lts version
pub fn latest_lts(&self) -> &VersionInfo {
let mut versions = self
.lts_versions
.values()
.filter_map(|req| self.get_fulfilling(req))
.collect::<Vec<_>>();
versions.sort_by_key(|v| &v.version);
versions.last().expect("No known lts node versions")
}
/// Returns a lts version by name
pub fn get_lts<S: AsRef<str>>(&self, lts_name: S) -> Option<&VersionInfo> {
let lts_version = self.lts_versions.get(lts_name.as_ref())?;
self.get_fulfilling(lts_version)
}
/// Returns any version that fulfills the given requirement
pub fn get_fulfilling(&self, req: &VersionReq) -> Option<&VersionInfo> {
let mut versions = self
.versions
.keys()
.filter(|v| req.matches(v))
.collect::<Vec<_>>();
versions.sort();
self.versions.get(versions.last()?)
}
}

@ -1,31 +0,0 @@
use std::{borrow::Borrow, collections::HashMap, hash::Hash};
use crate::web_api::VersionInfo;
/// Converts the list of versions to a tree for easy version lookup
pub fn convert_version_list_to_tree(
version_infos: Vec<VersionInfo>,
) -> HashMap<u64, HashMap<u64, HashMap<u64, VersionInfo>>> {
let mut version_map = HashMap::new();
for info in version_infos {
let major_map = version_map.get_mut_or_insert(info.version.major, HashMap::new());
let minor_map = major_map.get_mut_or_insert(info.version.minor, HashMap::new());
minor_map.insert(info.version.patch, info);
}
version_map
}
trait GetOrInsert<K: Copy, V> {
fn get_mut_or_insert(&mut self, key: K, default_value: V) -> &mut V;
}
impl<K: Eq + Hash + Copy, V> GetOrInsert<K, V> for HashMap<K, V> {
fn get_mut_or_insert(&mut self, key: K, default_value: V) -> &mut V {
if !self.contains_key(&key) {
self.insert(key, default_value);
}
self.get_mut(&key).unwrap()
}
}

@ -20,18 +20,18 @@ use tokio::io::{AsyncWrite, AsyncWriteExt};
mod test;
#[derive(Clone, Debug)]
pub struct NodejsAccess {
pub struct WebApi {
base_url: String,
client: Client,
}
impl Default for NodejsAccess {
impl Default for WebApi {
fn default() -> Self {
Self::new("https://nodejs.org/dist")
}
}
impl NodejsAccess {
impl WebApi {
/// Creates a new instance to access the nodejs website
pub fn new<S: ToString>(base_url: S) -> Self {
Self {

Loading…
Cancel
Save