diff --git a/src/mapper/error.rs b/src/mapper/error.rs index e0035b7..a712ee1 100644 --- a/src/mapper/error.rs +++ b/src/mapper/error.rs @@ -1,3 +1,5 @@ +use std::io; + use miette::Diagnostic; use thiserror::Error; @@ -19,4 +21,7 @@ pub enum MapperError { #[error("Failed to execute mapped command: {0}")] Command(#[from] CommandError), + + #[error("IO Error: {0}")] + Io(#[from] io::Error), } diff --git a/src/mapper/mapped_dir.rs b/src/mapper/mapped_dir.rs new file mode 100644 index 0000000..2035f43 --- /dev/null +++ b/src/mapper/mapped_dir.rs @@ -0,0 +1,95 @@ +use std::{ + collections::HashSet, + path::{Path, PathBuf}, +}; + +use tokio::fs::{self, DirEntry}; + +use crate::{consts::BIN_DIR, repository::node_path::NodePath}; + +use super::error::MapperResult; + +struct NodeApp { + info: DirEntry, +} + +impl NodeApp { + pub fn new(info: DirEntry) -> Self { + Self { info } + } + + /// returns the name of the application + pub fn name(&self) -> String { + let name = self.info.file_name(); + name.to_string_lossy().into_owned() + } + + pub async fn unmap(&self) -> MapperResult<()> { + fs::remove_file(self.info.path()).await?; + + Ok(()) + } + + /// creates wrappers to map this application + pub async fn map_executable(&self) -> MapperResult<()> { + let src_path = BIN_DIR.join(self.info.file_name()); + let name = self.info.file_name(); + let name = name.to_string_lossy(); + self.write_wrapper_script(&name, &src_path).await + } + + #[cfg(not(target_os = "windows"))] + async fn write_wrapper_script(&self, name: &str, path: &Path) -> MapperResult<()> { + fs::write( + path, + format!( + r#"#!/bin/sh + nenv exec {name} "$@""# + ), + ) + .await?; + let src_metadata = self.info.metadata().await?; + fs::set_permissions(&path, src_metadata.permissions()).await?; + + Ok(()) + } + + #[cfg(target_os = "windows")] + async fn write_wrapper_script(&self, name: &str, path: &Path) -> MapperResult<()> { + fs::write(path, format!("nenv exec {name} %*")).await?; + fs::set_permissions(&path, src_metadata.permissions()).await?; + + Ok(()) + } +} + +pub async fn map_node_bin(node_path: NodePath) -> MapperResult<()> { + let applications = get_applications(&node_path.bin()).await?; + let mapped_applications = get_applications(&*BIN_DIR).await?; + let mut new_mapped = HashSet::new(); + + for application in applications { + application.map_executable().await?; + new_mapped.insert(application.name()); + } + for app in mapped_applications { + if !new_mapped.contains(&app.name()) { + app.unmap().await?; + } + } + + Ok(()) +} + +async fn get_applications(path: &PathBuf) -> MapperResult> { + let mut files = Vec::new(); + let mut iter = fs::read_dir(path).await?; + + while let Some(entry) = iter.next_entry().await? { + if entry.path().is_file() { + files.push(NodeApp::new(entry)); + } + } + + Ok(files) +} diff --git a/src/mapper/mod.rs b/src/mapper/mod.rs index f8915da..788d1e0 100644 --- a/src/mapper/mod.rs +++ b/src/mapper/mod.rs @@ -5,13 +5,11 @@ use crate::{ repository::{NodeVersion, Repository}, }; -use self::{ - error::{MapperError, MapperResult}, - mapped_command::MappedCommand, -}; +use self::{error::MapperError, mapped_command::MappedCommand, mapped_dir::map_node_bin}; pub mod error; mod mapped_command; +mod mapped_dir; /// Responsible for mapping to node executables /// and managing node versions pub struct Mapper { @@ -34,12 +32,13 @@ impl Mapper { } /// Sets the given version as the default one - pub async fn use_version(&mut self, version: &NodeVersion) -> MapperResult<()> { + pub async fn use_version(&mut self, version: &NodeVersion) -> LibResult<()> { self.repo .config .set_default_version(version.clone()) .await?; self.active_version = version.clone(); + self.map_active_version().await?; Ok(()) } @@ -50,6 +49,7 @@ impl Mapper { /// Executes a mapped command with the given node environment pub async fn exec(&self, command: String, args: Vec) -> LibResult { + self.map_active_version().await?; let node_path = self .repo .get_version_path(&self.active_version) @@ -69,4 +69,16 @@ impl Mapper { .ok() .and_then(|v| NodeVersion::from_str(&v).ok()) } + + /// creates wrapper scripts for the current version + async fn map_active_version(&self) -> LibResult<()> { + let dir = self + .repo + .get_version_path(&self.active_version) + .await? + .expect("missing version"); + map_node_bin(dir).await?; + + Ok(()) + } } diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 1864357..31deee0 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -19,11 +19,11 @@ use crate::{ web_api::{VersionInfo, WebApi}, }; -use self::{config::Config, node_paths::NodePath, versions::Versions}; +use self::{config::Config, node_path::NodePath, versions::Versions}; pub mod config; pub(crate) mod extract; -mod node_paths; +pub(crate) mod node_path; pub mod versions; #[derive(Clone, Debug)] diff --git a/src/repository/node_paths.rs b/src/repository/node_path.rs similarity index 100% rename from src/repository/node_paths.rs rename to src/repository/node_path.rs