diff --git a/src/commands/env_add.rs b/src/commands/env_add.rs index 4d94f0e..9fdac7a 100644 --- a/src/commands/env_add.rs +++ b/src/commands/env_add.rs @@ -1,14 +1,12 @@ -use crate::{ - env_utils::{ - download_package, find_config, find_matching_package_version, read_config, write_config, - }, - utils::{abort, get_package_path}, +use crate::utils::{ + abort, download_package, find_matching_package_version, get_package_path, get_project_root, + read_config, write_config, }; use semver::VersionReq; use std::fs; pub fn env_add(name: &str, version: &VersionReq) { - let projet_path = find_config(); + let projet_path = get_project_root(); let mut config = read_config(&projet_path); let package = find_matching_package_version(name, version); diff --git a/src/commands/env_init.rs b/src/commands/env_init.rs index 6d93535..a6467c0 100644 --- a/src/commands/env_init.rs +++ b/src/commands/env_init.rs @@ -1,13 +1,12 @@ +use crate::utils::{abort, write_config, Config}; use semver::Version; - -use crate::{ - env_utils::{create_config, write_config}, - utils::abort, -}; use std::{env, fs}; pub fn env_init(version: Version) { - let config = create_config(version); + let config = Config { + python: version, + packages: toml::Table::new(), + }; let project_path = match env::current_dir() { Ok(dir) => dir, diff --git a/src/commands/env_pkgs.rs b/src/commands/env_pkgs.rs index c506c59..10a0dac 100644 --- a/src/commands/env_pkgs.rs +++ b/src/commands/env_pkgs.rs @@ -1,7 +1,7 @@ -use crate::env_utils::{find_config, read_config}; +use crate::utils::{get_project_root, read_config}; pub fn env_pkgs() { - let project_path = find_config(); + let project_path = get_project_root(); let config = read_config(&project_path); diff --git a/src/commands/env_sync.rs b/src/commands/env_sync.rs index 0f512fb..27c0948 100644 --- a/src/commands/env_sync.rs +++ b/src/commands/env_sync.rs @@ -1,11 +1,11 @@ use crate::constants::ENV_DIR_NAME; -use crate::env_utils::{create_virtual_env, find_config, read_config}; +use crate::utils::{create_or_update_virtual_env, get_project_root, read_config}; pub fn env_sync() { - let projet_path = find_config(); + let projet_path = get_project_root(); let config = read_config(&projet_path); - create_virtual_env(config, &projet_path.join(ENV_DIR_NAME)); + create_or_update_virtual_env(config, &projet_path.join(ENV_DIR_NAME)); println!("Installation complete!"); } diff --git a/src/commands/pen_activate.rs b/src/commands/pen_activate.rs index d358192..cd40e61 100644 --- a/src/commands/pen_activate.rs +++ b/src/commands/pen_activate.rs @@ -1,12 +1,9 @@ use crate::constants::ENV_DIR_NAME; -use crate::{ - env_utils::{find_config, read_config}, - utils::abort, -}; +use crate::utils::{abort, get_project_root, read_config}; use std::process; pub fn pen_activate() { - let project_path = find_config(); + let project_path = get_project_root(); let config = read_config(&project_path); let command = format!( @@ -34,10 +31,11 @@ pub fn pen_activate() { $SHELL "#, - project_path.join(ENV_DIR_NAME).to_string_lossy(), + project_path.join(ENV_DIR_NAME).to_string_lossy(), // todo .display() instead??? config.python ); + // todo make it work with plain sh match process::Command::new("bash").arg("-c").arg(command).spawn() { Ok(mut child) => child.wait().expect("Child process wasn't running."), Err(e) => abort("Failed to start shell.", Some(&e)), diff --git a/src/constants.rs b/src/constants.rs index c49b5fb..ae185b2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,10 +1,9 @@ -#![allow(dead_code)] - use crate::utils::abort; use home; use std::{path::PathBuf, sync::LazyLock}; pub static ENV_DIR_NAME: &str = ".venv"; +pub static CONFIG_FILE_NAME: &str = "pen.toml"; // pub static UPDATE_SCRIPT_URL: &str = "todo"; pub static HOME_DIR: LazyLock = LazyLock::new(|| match home::home_dir() { diff --git a/src/main.rs b/src/main.rs index e090356..8da05e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,6 @@ use utils::abort; mod commands; mod constants; -mod env_utils; -mod py_utils; mod utils; // todo a big question i have is should we not use abort and instead return errors and not exit whenever in the program? diff --git a/src/env_utils/config.rs b/src/utils/env_utils/config.rs similarity index 55% rename from src/env_utils/config.rs rename to src/utils/env_utils/config.rs index 0fc9829..abcf88b 100644 --- a/src/env_utils/config.rs +++ b/src/utils/env_utils/config.rs @@ -1,44 +1,17 @@ use semver::Version; use serde::{Deserialize, Serialize}; +use std::fs; use std::path::PathBuf; -use std::{env, fs}; use toml; +use crate::constants::CONFIG_FILE_NAME; use crate::utils::abort; -// todo docstring -pub fn create_config(py_version: Version) -> Config { - Config { - python: py_version, - packages: toml::Table::new(), - } -} - -// todo docstring -pub fn find_config() -> PathBuf { - let mut dir = match env::current_dir() { - Ok(dir) => dir, - Err(e) => abort("Failed to get current working directory.", Some(&e)), - }; - - loop { - match fs::exists(dir.join("pen.toml")) { - Ok(true) => return dir, - Ok(false) => { - if !dir.pop() { - abort("Couldn't find a pen.toml file.", None); - } - } - Err(e) => abort("Failed to find a pen.toml file.", Some(&e)), - }; - } -} - // todo docstring pub fn read_config(project_path: &PathBuf) -> Config { - let config_path = project_path.join("pen.toml"); + let config_path = project_path.join(&*CONFIG_FILE_NAME); match fs::read_to_string(&config_path) { - Ok(contents) => match toml::de::from_str::(&contents) { + Ok(contents) => match toml::from_str::(&contents) { Ok(toml) => toml, Err(e) => abort( &format!("Couldn't parse {}.", config_path.display()), @@ -54,7 +27,7 @@ pub fn read_config(project_path: &PathBuf) -> Config { // todo docstring pub fn write_config(project_path: PathBuf, config: Config) { - match toml::ser::to_string_pretty(&config) { + match toml::to_string_pretty(&config) { Ok(toml) => { if let Err(e) = fs::write(project_path.join("pen.toml"), toml) { abort( @@ -75,6 +48,6 @@ pub fn write_config(project_path: PathBuf, config: Config) { #[derive(Serialize, Deserialize)] pub struct Config { - pub python: Version, + pub python: Version, // todo do we want to have instead a VersionReq? pub packages: toml::Table, } diff --git a/src/env_utils/mod.rs b/src/utils/env_utils/mod.rs similarity index 100% rename from src/env_utils/mod.rs rename to src/utils/env_utils/mod.rs diff --git a/src/env_utils/package.rs b/src/utils/env_utils/package.rs similarity index 98% rename from src/env_utils/package.rs rename to src/utils/env_utils/package.rs index 45ed324..159530b 100644 --- a/src/env_utils/package.rs +++ b/src/utils/env_utils/package.rs @@ -132,7 +132,7 @@ pub fn find_matching_package_version(name: &str, version_requirements: &VersionR }; Package { - name: String::from(&json.info.name), + name: String::from(&json.info.name), // todo why is this not just name? version: best_version, } } diff --git a/src/env_utils/virtual_env.rs b/src/utils/env_utils/virtual_env.rs similarity index 94% rename from src/env_utils/virtual_env.rs rename to src/utils/env_utils/virtual_env.rs index db42c41..990ab7e 100644 --- a/src/env_utils/virtual_env.rs +++ b/src/utils/env_utils/virtual_env.rs @@ -1,13 +1,12 @@ use semver::{Version, VersionReq}; -use crate::{ - env_utils::{download_package, find_matching_package_version, Config, Package}, - py_utils::py_install_algo_v1, - utils::{self, abort}, +use crate::utils::{ + self, abort, download_package, find_matching_package_version, py_install_algo_v1, Config, + Package, }; use std::{fs, os::unix, path::PathBuf}; -pub fn create_virtual_env(config: Config, destination_path: &PathBuf) { +pub fn create_or_update_virtual_env(config: Config, destination_path: &PathBuf) { let py_dir = utils::get_python_path(&config.python); let py_version_short = format!("{}.{}", config.python.major, config.python.minor); @@ -16,7 +15,7 @@ pub fn create_virtual_env(config: Config, destination_path: &PathBuf) { } if let Err(e) = fs::write( destination_path.join("pyvenv.cfg"), - // todo this pyenv.cfg file, when writen, has some spacing before the paragraph, needs fixing + // todo this pyenv.cfg file, when writen, has some spacing beforeeach line of the paragraph, needs fixing format!( r#" # Created using pen home = {0}/bin diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 280f9fc..16d759d 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,9 @@ +mod env_utils; +mod path; +mod py_utils; mod utils; + +pub use env_utils::*; +pub use path::*; +pub use py_utils::*; pub use utils::*; diff --git a/src/utils/path.rs b/src/utils/path.rs new file mode 100644 index 0000000..fcd87a6 --- /dev/null +++ b/src/utils/path.rs @@ -0,0 +1,145 @@ +use crate::constants::{PYTHON_PACKAGES_DIR, PYTHON_VERSIONS_DIR, TMP_DIR}; +use crate::utils::{abort, Package}; +use semver::Version; +use std::{env, fs, io, path::PathBuf}; + +/// Constructs the path to the directory for a specified Python version without validating the format of the version string. +/// +/// # Arguments +/// - `py_version`: A string slice representing the Python version, which has been +/// validated to conform to the expected format (major.minor.patch). +/// +/// # Output +/// - A `PathBuf` pointing to the directory associated with the specified Python version. +/// +/// # Termination +/// - This function does not terminate. +/// +/// # Guarantees +/// - The returned path will be correctly formed if `py_version` is well formatted. +/// +/// # Limitations +/// - The function does not validate the contents of the constructed path or its existence. +pub fn get_python_path(version: &Version) -> PathBuf { + PYTHON_VERSIONS_DIR.join(format!( + "{}.{}.{}", + version.major, version.minor, version.patch + )) +} + +// todo docstring +pub fn get_package_path(package: &Package) -> PathBuf { + PYTHON_PACKAGES_DIR.join(format!( + "{}_{}.{}.{}", + package.name, package.version.major, package.version.minor, package.version.patch + )) +} + +// todo docstring +pub fn get_project_root() -> PathBuf { + // todo this should probably be in a more global utils file + let mut dir = match env::current_dir() { + Ok(dir) => dir, + Err(e) => abort("Failed to get current working directory.", Some(&e)), + }; + + loop { + match fs::exists(dir.join("pen.toml")) { + Ok(true) => return dir, + Ok(false) => { + if !dir.pop() { + abort("Couldn't find a pen.toml file.", None); + } + } + Err(e) => abort("Failed to find a pen.toml file.", Some(&e)), + }; + } +} + +/// Attempts to delete a specified directory. +/// +/// # Arguments +/// - `dir_path`: A `PathBuf` representing the directory to delete. +/// +/// # Output +/// - Returns `Ok(())` if the directory was successfully deleted or if it was already empty. +/// - Returns an `Err` if the directory still exists after attempting deletion. +/// +/// # Termination +/// - This function does not terminate. +/// +/// # Guarantees +/// - If this function returns `Ok(())`, it guarantees that the directory no longer exists. +pub fn try_deleting_dir(dir_path: &PathBuf) -> Result<(), std::io::Error> { + let delete_path = TMP_DIR.join("delete_path"); + return try_deleting_dir_to_temp(dir_path, &delete_path); +} + +pub fn try_deleting_dir_to_temp( + dir_path: &PathBuf, + temp_dir: &PathBuf, +) -> Result<(), std::io::Error> { + if let Ok(exists) = dir_path.try_exists() { + if !exists { + return Ok(()); + } + } + match temp_dir.try_exists() { + Ok(true) => fs::remove_dir_all(&temp_dir)?, + Ok(false) => (), + Err(e) => abort( + &format!("Unable to know if {} exists", temp_dir.display()), + Some(&e), + ), + } + fs::rename(&dir_path, &temp_dir)?; + if dir_path.try_exists()? { + Err(io::Error::new( + io::ErrorKind::Other, + "Directory still exists", + )) + } else { + Ok(()) + } +} + +/// Clears and recreates the temporary directory. +/// +/// # Input +/// - None. +/// +/// # Output +/// - None. +/// +/// # Termination +/// - If either removal or creation operations fail, the function prints an error message and terminates the process. +pub fn clear_temp() { + let temp_is_empty = match (&*TMP_DIR).read_dir() { + Ok(mut read_dir) => read_dir.next().is_none(), + Err(e) => abort( + &format!( + "Failed to check contents of directory {}", + (*TMP_DIR).display() + ), + Some(&e), + ), + }; + + if temp_is_empty { + return; + } + + if let Err(e) = fs::remove_dir_all(&*TMP_DIR) { + abort( + &format!("Failed to clear directory {}", (*TMP_DIR).display()), + Some(&e), + ) + } + + if let Err(e) = fs::create_dir(&*TMP_DIR) { + abort( + &format!("Failed to create directory {}", (*TMP_DIR).display()), + Some(&e), + ) + } +} diff --git a/src/py_utils/mod.rs b/src/utils/py_utils/mod.rs similarity index 100% rename from src/py_utils/mod.rs rename to src/utils/py_utils/mod.rs diff --git a/src/py_utils/py_install_algo_v1.rs b/src/utils/py_utils/py_install_algo_v1.rs similarity index 100% rename from src/py_utils/py_install_algo_v1.rs rename to src/utils/py_utils/py_install_algo_v1.rs diff --git a/src/utils/utils.rs b/src/utils/utils.rs index faa7288..8a0c19c 100644 --- a/src/utils/utils.rs +++ b/src/utils/utils.rs @@ -1,9 +1,7 @@ -use semver::Version; - use crate::constants::{ HOME_DIR, PEN_CONFIG_FILE, PEN_DIR, PYTHON_PACKAGES_DIR, PYTHON_VERSIONS_DIR, TMP_DIR, }; -use crate::env_utils::Package; +use semver::Version; use std::{ error::Error, fs, @@ -57,38 +55,6 @@ pub fn assert_major_minor_patch(py_version: &str) { } } -/// Constructs the path to the directory for a specified Python version without validating the format of the version string. -/// -/// # Arguments -/// - `py_version`: A string slice representing the Python version, which has been -/// validated to conform to the expected format (major.minor.patch). -/// -/// # Output -/// - A `PathBuf` pointing to the directory associated with the specified Python version. -/// -/// # Termination -/// - This function does not terminate. -/// -/// # Guarantees -/// - The returned path will be correctly formed if `py_version` is well formatted. -/// -/// # Limitations -/// - The function does not validate the contents of the constructed path or its existence. -pub fn get_python_path(version: &Version) -> PathBuf { - PYTHON_VERSIONS_DIR.join(format!( - "{}.{}.{}", - version.major, version.minor, version.patch - )) -} - -// todo docstring -pub fn get_package_path(package: &Package) -> PathBuf { - PYTHON_PACKAGES_DIR.join(format!( - "{}_{}.{}.{}", - package.name, package.version.major, package.version.minor, package.version.patch - )) -} - /// Prompts the user to confirm an action. /// /// # Arguments @@ -178,53 +144,6 @@ pub fn download_file(file_url: &str, file_path: &PathBuf) { } } -/// Attempts to delete a specified directory. -/// -/// # Arguments -/// - `dir_path`: A `PathBuf` representing the directory to delete. -/// -/// # Output -/// - Returns `Ok(())` if the directory was successfully deleted or if it was already empty. -/// - Returns an `Err` if the directory still exists after attempting deletion. -/// -/// # Termination -/// - This function does not terminate. -/// -/// # Guarantees -/// - If this function returns `Ok(())`, it guarantees that the directory no longer exists. -pub fn try_deleting_dir(dir_path: &PathBuf) -> Result<(), std::io::Error> { - let delete_path = TMP_DIR.join("delete_path"); - return try_deleting_dir_to_temp(dir_path, &delete_path); -} - -pub fn try_deleting_dir_to_temp( - dir_path: &PathBuf, - temp_dir: &PathBuf, -) -> Result<(), std::io::Error> { - if let Ok(exists) = dir_path.try_exists() { - if !exists { - return Ok(()); - } - } - match temp_dir.try_exists() { - Ok(true) => fs::remove_dir_all(&temp_dir)?, - Ok(false) => (), - Err(e) => abort( - &format!("Unable to know if {} exists", temp_dir.display()), - Some(&e), - ), - } - fs::rename(&dir_path, &temp_dir)?; - if dir_path.try_exists()? { - Err(io::Error::new( - io::ErrorKind::Other, - "Directory still exists", - )) - } else { - Ok(()) - } -} - /// Checks if the specified dependencies are installed by running their `--help` command. /// /// # Arguments @@ -306,47 +225,6 @@ pub fn catastrophic_failure(message: &str, e: Option<&dyn Error>) -> ! { process::exit(1); } // todo mabye possible to just print RED_BOLD then call abort idk -/// Clears and recreates the temporary directory. -/// -/// # Input -/// - None. -/// -/// # Output -/// - None. -/// -/// # Termination -/// - If either removal or creation operations fail, the function prints an error message and terminates the process. -pub fn clear_temp() { - let temp_is_empty = match (&*TMP_DIR).read_dir() { - Ok(mut read_dir) => read_dir.next().is_none(), - Err(e) => abort( - &format!( - "Failed to check contents of directory {}", - (*TMP_DIR).display() - ), - Some(&e), - ), - }; - - if temp_is_empty { - return; - } - - if let Err(e) = fs::remove_dir_all(&*TMP_DIR) { - abort( - &format!("Failed to clear directory {}", (*TMP_DIR).display()), - Some(&e), - ) - } - - if let Err(e) = fs::create_dir(&*TMP_DIR) { - abort( - &format!("Failed to create directory {}", (*TMP_DIR).display()), - Some(&e), - ) - } -} - /// Checks if the paths used in pen exists. /// /// # Arguments