From f517cd5469d009764918267c9e6e52d47d00292b Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 21 Jan 2023 15:31:20 +0100 Subject: [PATCH] ADd exec command with remapping --- Cargo.toml | 2 +- src/args.rs | 18 ++++++++---- src/error.rs | 1 - src/lib.rs | 14 +++++++++ src/main.rs | 15 +++++++--- src/mapper/error.rs | 5 ++++ src/mapper/mapped_command.rs | 56 ++++++++++++++++++++++++++++++++++++ src/mapper/mod.rs | 29 +++++++++++++++++-- src/repository/extract.rs | 19 +++++++----- src/repository/mod.rs | 28 ++++++++++++++++-- src/repository/node_paths.rs | 15 ++++++++++ src/web_api/error.rs | 1 - src/web_api/mod.rs | 3 +- 13 files changed, 179 insertions(+), 27 deletions(-) create mode 100644 src/mapper/mapped_command.rs create mode 100644 src/repository/node_paths.rs diff --git a/Cargo.toml b/Cargo.toml index c89733a..1928f02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ 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"] } +tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time", "process"] } toml = "0.5.11" tracing = "0.1.37" tracing-subscriber = "0.3.16" diff --git a/src/args.rs b/src/args.rs index 547474e..0951ecb 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,8 +1,7 @@ -use std::str::FromStr; +use std::ffi::OsString; use clap::{Parser, Subcommand}; use nenv::repository::NodeVersion; -use semver::VersionReq; #[derive(Clone, Debug, Parser)] #[clap(infer_subcommands = true)] @@ -19,16 +18,23 @@ pub enum Command { #[command()] Use(UseArgs), - #[command()] - Default, - #[command(short_flag = 'v', aliases = &["--version"])] Version, + + #[command()] + Exec(ExecArgs), } #[derive(Clone, Debug, Parser)] -pub struct InstallArgs { +pub struct ExecArgs { #[arg()] + pub command: String, + #[arg(trailing_var_arg = true, allow_hyphen_values = true)] + pub args: Vec, +} + +#[derive(Clone, Debug, Parser)] +pub struct InstallArgs { pub version: NodeVersion, } diff --git a/src/error.rs b/src/error.rs index e718b72..6131bfc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,6 @@ use crate::{ }; pub(crate) type LibResult = Result; -pub(crate) type LibError = Error; pub type Result = std::result::Result; diff --git a/src/lib.rs b/src/lib.rs index 15315fd..ef3cb57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +use std::ffi::OsString; + use crossterm::style::Stylize; use mapper::Mapper; use repository::{config::Config, NodeVersion, Repository}; @@ -51,6 +53,18 @@ pub async fn use_version(version: NodeVersion) -> Result<()> { Ok(()) } +pub async fn exec(command: String, args: Vec) -> Result { + let mapper = get_mapper().await?; + let active_version = mapper.active_version(); + + if !mapper.repository().is_installed(active_version).await? { + mapper.repository().install_version(&active_version).await?; + } + let exit_status = mapper.exec(command, args).await?; + + Ok(exit_status.code().unwrap_or(0)) +} + async fn get_repository() -> Result { Repository::init(Config::load().await?).await } diff --git a/src/main.rs b/src/main.rs index 1ba86a8..f1b1e0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ +use std::process; + use args::Args; use clap::Parser; -use nenv::repository::NodeVersion; - mod args; #[tokio::main(flavor = "current_thread")] @@ -12,7 +12,14 @@ async fn main() { match args.commmand { 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!(), + args::Command::Version => print_version(), + args::Command::Exec(args) => { + let exit_code = nenv::exec(args.command, args.args).await.unwrap(); + process::exit(exit_code); + } }; } + +fn print_version() { + println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); +} diff --git a/src/mapper/error.rs b/src/mapper/error.rs index 90a4eb5..e0035b7 100644 --- a/src/mapper/error.rs +++ b/src/mapper/error.rs @@ -3,6 +3,8 @@ use thiserror::Error; use crate::repository::config::ConfigError; +use super::mapped_command::CommandError; + pub type MapperResult = Result; #[derive(Error, Diagnostic, Debug)] @@ -14,4 +16,7 @@ pub enum MapperError { #[diagnostic_source] ConfigError, ), + + #[error("Failed to execute mapped command: {0}")] + Command(#[from] CommandError), } diff --git a/src/mapper/mapped_command.rs b/src/mapper/mapped_command.rs new file mode 100644 index 0000000..12e0281 --- /dev/null +++ b/src/mapper/mapped_command.rs @@ -0,0 +1,56 @@ +use std::{ + ffi::OsString, + io::{stderr, stdin, stdout}, + os::fd::{AsRawFd, FromRawFd}, + path::PathBuf, + process::{ExitStatus, Stdio}, +}; + +use thiserror::Error; +use tokio::{io, process::Command}; + +pub struct MappedCommand { + path: PathBuf, + args: Vec, +} + +pub type CommandResult = Result; + +#[derive(Error, Debug)] +pub enum CommandError { + #[error(transparent)] + Io(#[from] io::Error), + + #[error("The command {0:?} could not be found")] + NotFound(PathBuf), +} + +impl MappedCommand { + pub fn new(path: PathBuf, args: Vec) -> Self { + Self { path, args } + } + + #[tracing::instrument(skip_all, level = "debug")] + pub async fn run(self) -> CommandResult { + if !self.path.exists() { + return Err(CommandError::NotFound(self.path)); + } + let (stdin, stdout, stderr) = unsafe { + ( + Stdio::from_raw_fd(stdin().as_raw_fd()), + Stdio::from_raw_fd(stdout().as_raw_fd()), + Stdio::from_raw_fd(stderr().as_raw_fd()), + ) + }; + let exit_status = Command::new(self.path) + .args(self.args) + .stdin(stdin) + .stdout(stdout) + .stderr(stderr) + .spawn()? + .wait() + .await?; + + Ok(exit_status) + } +} diff --git a/src/mapper/mod.rs b/src/mapper/mod.rs index b8050df..f8915da 100644 --- a/src/mapper/mod.rs +++ b/src/mapper/mod.rs @@ -1,10 +1,17 @@ -use std::{env, str::FromStr}; +use std::{env, ffi::OsString, process::ExitStatus, str::FromStr}; -use crate::repository::{NodeVersion, Repository}; +use crate::{ + error::LibResult, + repository::{NodeVersion, Repository}, +}; -use self::error::MapperResult; +use self::{ + error::{MapperError, MapperResult}, + mapped_command::MappedCommand, +}; pub mod error; +mod mapped_command; /// Responsible for mapping to node executables /// and managing node versions pub struct Mapper { @@ -41,6 +48,22 @@ impl Mapper { &self.active_version } + /// Executes a mapped command with the given node environment + pub async fn exec(&self, command: String, args: Vec) -> LibResult { + let node_path = self + .repo + .get_version_path(&self.active_version) + .await? + .expect("version not installed"); + let executable = node_path.bin().join(command); + let exit_status = MappedCommand::new(executable, args) + .run() + .await + .map_err(MapperError::from)?; + + Ok(exit_status) + } + fn get_version() -> Option { env::var("NODE_VERSION") .ok() diff --git a/src/repository/extract.rs b/src/repository/extract.rs index e6c988e..f06ec5d 100644 --- a/src/repository/extract.rs +++ b/src/repository/extract.rs @@ -1,16 +1,13 @@ use std::{ - fs::{self, File}, + fs::File, io::{self, BufReader}, path::Path, }; -use libflate::gzip::Decoder; use miette::Diagnostic; -use tar::Archive; use thiserror::Error; -use zip::ZipArchive; -use crate::utils::{progress_bar, progress_spinner}; +use crate::utils::progress_spinner; type ExtractResult = Result; #[derive(Error, Debug, Diagnostic)] @@ -38,9 +35,13 @@ pub fn extract_file(src: &Path, dst: &Path) -> ExtractResult<()> { Ok(()) } +#[cfg(not(target_os = "windows"))] fn extract_tar_gz(src: &Path, dst: &Path) -> ExtractResult<()> { - let mut reader = BufReader::new(File::open(src)?); - let mut decoder = Decoder::new(reader)?; + use libflate::gzip::Decoder; + use tar::Archive; + + let reader = BufReader::new(File::open(src)?); + let decoder = Decoder::new(reader)?; let mut archive = Archive::new(decoder); let pb = progress_spinner(); pb.set_message("Extracting tar.gz archive"); @@ -51,7 +52,11 @@ fn extract_tar_gz(src: &Path, dst: &Path) -> ExtractResult<()> { Ok(()) } +#[cfg(target_os = "windows")] fn extract_zip(src: &Path, dst: &Path) -> ExtractResult<()> { + use crate::utils::progress_bar; + use std::fs; + use zip::ZipArchive; let mut archive = ZipArchive::new(File::open(src)?)?; let pb = progress_bar(archive.len() as u64); diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 1493652..1864357 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -12,15 +12,18 @@ use tokio::{ }; use crate::{ - consts::{BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR}, + consts::{ + ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS, + }, error::LibResult, web_api::{VersionInfo, WebApi}, }; -use self::{config::Config, versions::Versions}; +use self::{config::Config, node_paths::NodePath, versions::Versions}; pub mod config; pub(crate) mod extract; +mod node_paths; pub mod versions; #[derive(Clone, Debug)] @@ -113,6 +116,27 @@ impl Repository { Ok(()) } + /// Returns the path for the given node version + pub async fn get_version_path(&self, version: &NodeVersion) -> LibResult> { + let info = self.parse_req(&version); + let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await?; + let mut path = None; + + while let Some(entry) = iter.next_entry().await? { + if let Ok(version) = Version::parse(entry.file_name().to_string_lossy().as_ref()) { + if version == info.version { + path = Some(entry.path()); + } + }; + } + let Some(path) = path else { + return Ok(None); + }; + let path = path.join(format!("node-v{}-{}-{}", info.version, OS, ARCH)); + + Ok(Some(NodePath::new(path))) + } + /// Returns a list of installed versions pub async fn installed_versions(&self) -> LibResult> { let mut versions = Vec::new(); diff --git a/src/repository/node_paths.rs b/src/repository/node_paths.rs new file mode 100644 index 0000000..2e2307c --- /dev/null +++ b/src/repository/node_paths.rs @@ -0,0 +1,15 @@ +use std::path::PathBuf; + +pub struct NodePath { + base: PathBuf, +} + +impl NodePath { + pub fn new(base: PathBuf) -> Self { + Self { base } + } + + pub fn bin(&self) -> PathBuf { + self.base.join("bin") + } +} diff --git a/src/web_api/error.rs b/src/web_api/error.rs index 19f8570..8ee272c 100644 --- a/src/web_api/error.rs +++ b/src/web_api/error.rs @@ -1,6 +1,5 @@ use std::io; -use lazy_static::__Deref; use miette::Diagnostic; use thiserror::Error; diff --git a/src/web_api/mod.rs b/src/web_api/mod.rs index da0b2bd..5f69d66 100644 --- a/src/web_api/mod.rs +++ b/src/web_api/mod.rs @@ -1,13 +1,12 @@ use std::{ cmp::min, fmt::{Debug, Display}, - time::Duration, }; use crate::{consts::NODE_ARCHIVE_SUFFIX, utils::progress_bar}; use self::error::{ApiError, ApiResult}; -use indicatif::{ProgressBar, ProgressStyle}; + use reqwest::Client; pub mod error;