From 65b53bdbb51fcbe59d5c57a2b5bd1d9181ea6fcf Mon Sep 17 00:00:00 2001 From: Dmitrii Kovanikov Date: Fri, 16 Sep 2022 14:23:04 +0100 Subject: [PATCH] Refactor commands into separate modules (#94) Some refactoring to change a few things: * Move all commands into separate modules to make the top-level `match` in `lib.rs` concise * Change the text of the `default-config` * Refactor formatting of tool names into a separate function * Rename integration test files to specify whether they are used for the the `sync` or `install` command --- .github/workflows/ci.yml | 14 ++-- src/config/template.rs | 66 ++++++++--------- src/config/toml.rs | 22 +++++- src/infra.rs | 1 + src/{ => infra}/err.rs | 6 ++ src/install.rs | 32 +++++++++ src/lib.rs | 74 +++----------------- src/sync.rs | 33 ++++++--- src/sync/db.rs | 15 ++++ tests/default-config.toml | 52 +++++++------- tests/only-ripgrep.toml | 3 - tests/{full-database.toml => sync-full.toml} | 2 +- tests/sync-ripgrep.toml | 3 + 13 files changed, 172 insertions(+), 151 deletions(-) rename src/{ => infra}/err.rs (84%) create mode 100644 src/install.rs delete mode 100644 tests/only-ripgrep.toml rename tests/{full-database.toml => sync-full.toml} (87%) create mode 100644 tests/sync-ripgrep.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60ffdb8..b338f93 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,9 +54,9 @@ jobs: if [[ ! -x $SYNC_DIR/rg ]]; then echo "error on: rg"; false; fi - if: matrix.os != 'windows-latest' - name: "Integration test: [unix] [only-ripgrep]" + name: "Integration test: [unix] [sync-ripgrep]" env: - SYNC_DIR: "only-ripgrep" + SYNC_DIR: "sync-ripgrep" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir $SYNC_DIR @@ -67,9 +67,9 @@ jobs: if [[ ! -x $SYNC_DIR/rg ]]; then echo "error on: rg"; false; fi - if: matrix.os == 'windows-latest' - name: "Integration test: [windows] [only-ripgrep]" + name: "Integration test: [windows] [sync-ripgrep]" env: - SYNC_DIR: "only-ripgrep" + SYNC_DIR: "sync-ripgrep" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir $env:SYNC_DIR @@ -82,9 +82,9 @@ jobs: } - if: matrix.os != 'windows-latest' - name: "Integration test: [unix] [full-database]" + name: "Integration test: [unix] [sync-full]" env: - SYNC_DIR: "full-database" + SYNC_DIR: "sync-full" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir $SYNC_DIR @@ -116,7 +116,7 @@ jobs: - if: matrix.os == 'windows-latest' name: "Integration test: [windows] [full-database]" env: - SYNC_DIR: "full-database" + SYNC_DIR: "sync-full" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | mkdir $env:SYNC_DIR diff --git a/src/config/template.rs b/src/config/template.rs index c9b23fe..6f3f7f2 100644 --- a/src/config/template.rs +++ b/src/config/template.rs @@ -1,52 +1,46 @@ /// This file only holds the template that is used to generate a default .tools.toml. -use std::fmt::Write; +use crate::sync::db; -use crate::sync::db::build_db; +pub fn generate_default_config() { + println!("{}", config_template()); +} -pub fn config_template() -> String { - let mut tools: String = String::new(); - for tool in build_db().keys().cloned().collect::>() { - if let Err(e) = writeln!(tools, "# [{}]", tool) { - crate::err::abort_suggest_issue(&format!("{}", e)); - }; - } - // adding another hash to fil a new line before the next block - tools.push('#'); +fn config_template() -> String { + let tools = db::fmt_tool_names(|name| format!("# [{name}]")); format!( - r###"# This config file was generated for version {version} -# -# # tool-sync default configuration file + r###"# This configuration is automatically generated by tool-sync {version} # /~https://github.com/chshersh/tool-sync -# This file was automatically generated by tool-sync -##################################################### -# +####################################### # +# Installation directory for all the tools: # store_directory = "$HOME/.local/bin" # -# tool-sync provides native support for some of the tools without the need to configure them -# Uncomment the tools you want to have them +# tool-sync provides native support for some of the tools without the need to +# configure them. Uncomment all the tools you want to install with a single +# 'tool sync' command: # {tools} -# To add configuration for other tools these are the config options: -# [ripgrep] -# owner = "BurntSushi" -# repo = "ripgrep" -# exe_name = "rg" -# -# # Uncomment to download a specific version or tag. -# # Without this tag latest will be used -# # tag = "13.0.0" -# # -# Asset name to download on linux OSes -# asset_name.linux = "x86_64-unknown-linux-musl" +# You can configure the installation of any tool by specifying corresponding options: # -# uncomment if you want to install on macOS as well -# asset_name.macos = "apple-darwin" -# -# uncomment if you want to install on Windows as well -# asset_name.windows = "x86_64-pc-windows-msvc""###, +# [ripgrep] # Name of the tool (new or one of the hardcoded to override default settings) +# owner = "BurntSushi" # GitHub repository owner +# repo = "ripgrep" # GitHub repository name +# exe_name = "rg" # Executable name inside the asset + +# Uncomment to download a specific version or tag. +# Without this tag latest will be used +# tag = "13.0.0" + +# Asset name to download on linux OSes +# asset_name.linux = "x86_64-unknown-linux-musl" + +# Uncomment if you want to install on macOS as well +# asset_name.macos = "apple-darwin" + +# Uncomment if you want to install on Windows as well +# asset_name.windows = "x86_64-pc-windows-msvc""###, version = env!("CARGO_PKG_VERSION"), ) } diff --git a/src/config/toml.rs b/src/config/toml.rs index cbbd35f..93bd90a 100644 --- a/src/config/toml.rs +++ b/src/config/toml.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use toml::{map::Map, Value}; use crate::config::schema::{Config, ConfigAsset}; +use crate::infra::err; use crate::model::asset_name::AssetName; #[derive(Debug, PartialEq, Eq)] @@ -23,7 +24,22 @@ impl TomlError { } } -pub fn parse_file(config_path: &PathBuf) -> Result { +pub fn with_parsed_file(config_path: PathBuf, on_success: F) { + match parse_file(&config_path) { + Ok(config) => { + on_success(config); + } + Err(e) => { + err::abort_with(&format!( + "Error parsing configuration at path {}: {}", + config_path.display(), + e.display() + )); + } + } +} + +fn parse_file(config_path: &PathBuf) -> Result { let contents = fs::read_to_string(config_path).map_err(|e| TomlError::IO(format!("{}", e)))?; parse_string(&contents) @@ -138,12 +154,12 @@ mod tests { #[test] fn test_parse_file_correct_output() { let result = std::panic::catch_unwind(|| { - let test_config_path = PathBuf::from("tests/full-database.toml"); + let test_config_path = PathBuf::from("tests/sync-full.toml"); parse_file(&test_config_path).expect("This should not fail") }); if let Ok(config) = result { - assert_eq!(String::from("full-database"), config.store_directory); + assert_eq!(String::from("sync-full"), config.store_directory); }; } diff --git a/src/infra.rs b/src/infra.rs index b9babe5..cd42182 100644 --- a/src/infra.rs +++ b/src/infra.rs @@ -1 +1,2 @@ pub mod client; +pub mod err; diff --git a/src/err.rs b/src/infra/err.rs similarity index 84% rename from src/err.rs rename to src/infra/err.rs index ada0b75..a4ccd01 100644 --- a/src/err.rs +++ b/src/infra/err.rs @@ -29,3 +29,9 @@ this issue: process::exit(1); } + +/// Print just the message and exit +pub fn abort(err_msg: &str) -> ! { + eprintln!("{}", err_msg); + process::exit(1); +} diff --git a/src/install.rs b/src/install.rs new file mode 100644 index 0000000..98c89e7 --- /dev/null +++ b/src/install.rs @@ -0,0 +1,32 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; + +use crate::config::schema::{Config, ConfigAsset}; +use crate::config::toml; +use crate::infra::err; +use crate::sync; +use crate::sync::db::{fmt_tool_names, lookup_tool}; + +/// Install a single tool +pub fn install(config_path: PathBuf, name: String) { + toml::with_parsed_file(config_path, |config| install_tool(config, name)) +} + +/// Find if the tool is already mentioned in the config +fn install_tool(mut config: Config, name: String) { + if let Some(tool_info) = lookup_tool(&name) { + let tool_btree: BTreeMap = BTreeMap::from([(name, tool_info.into())]); + config.tools = tool_btree; + sync::sync_from_config_no_check(config); + } else { + let tools = fmt_tool_names(|name| format!(" * {name}")); + + let exit_message = format!( + r#"Unknown tool: '{name}' +Supported tools: +{tools}"# + ); + + err::abort(&exit_message); + } +} diff --git a/src/lib.rs b/src/lib.rs index 11f33e9..4a1ee4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,78 +1,30 @@ mod config; -mod err; mod infra; +mod install; mod model; mod sync; -use std::collections::BTreeMap; -use std::fmt::Write; -use std::path::PathBuf; - use clap::Parser; +use std::path::PathBuf; use crate::config::cli::{Cli, Command}; -use crate::config::schema::{Config, ConfigAsset}; -use crate::config::template; -use crate::config::toml; -use crate::sync::db::{build_db, lookup_tool}; -use crate::sync::sync; - -const DEFAULT_CONFIG_PATH: &str = ".tool.toml"; +use crate::infra::err; pub fn run() { let cli = Cli::parse(); - let config_path = resolve_config_path(cli.config.clone()); + + // TODO: this is redundant for the `default-config` command + // See: /~https://github.com/chshersh/tool-sync/issues/75 + let config_path = resolve_config_path(cli.config); match cli.command { - Command::DefaultConfig => generate_config(), - Command::Sync => match toml::parse_file(&config_path) { - Err(e) => { - err::abort_with(&format!( - "Error parsing configuration at path {}: {}", - config_path.display(), - e.display() - )); - } - Ok(config) => { - sync(config); - } - }, - Command::Install { name } => { - with_parsed_file(&config_path, |mut config| { - if let Some(tool_info) = lookup_tool(&name) { - let tool_btree: BTreeMap = - BTreeMap::from([(name, tool_info.into())]); - config.tools = tool_btree; - sync(config); - } else { - let mut exit_message: String = - format!("Unknown tool: {}\nSupported tools:\n", name); - for tool in build_db().keys().cloned().collect::>() { - if let Err(e) = writeln!(exit_message, "\t* {}", tool) { - err::abort_suggest_issue(&format!("{}", e)); - }; - } - err::abort_with(&exit_message); - }; - }); - } + Command::DefaultConfig => config::template::generate_default_config(), + Command::Sync => sync::sync_from_path(config_path), + Command::Install { name } => install::install(config_path, name), } } -fn with_parsed_file(config_path: &PathBuf, on_success: F) { - match toml::parse_file(config_path) { - Ok(config) => { - on_success(config); - } - Err(e) => { - err::abort_with(&format!( - "Error parsing configuration at path {}: {}", - config_path.display(), - e.display() - )); - } - } -} +const DEFAULT_CONFIG_PATH: &str = ".tool.toml"; fn resolve_config_path(config_path: Option) -> PathBuf { match config_path { @@ -90,7 +42,3 @@ fn resolve_config_path(config_path: Option) -> PathBuf { }, } } - -fn generate_config() { - println!("{}", template::config_template()); -} diff --git a/src/sync.rs b/src/sync.rs index b3b5aa0..31029fa 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -7,19 +7,25 @@ mod prefetch; mod progress; use console::Emoji; +use std::path::PathBuf; use crate::config::schema::Config; +use crate::config::toml; use self::install::Installer; use self::prefetch::prefetch; use self::progress::SyncProgress; use self::progress::ToolPair; -pub fn sync(config: Config) { +pub fn sync_from_path(config_path: PathBuf) { + toml::with_parsed_file(config_path, sync_from_config) +} + +pub fn sync_from_config(config: Config) { if config.tools.is_empty() { no_tools_message() } else { - sync_tools(config) + sync_from_config_no_check(config) } } @@ -29,13 +35,17 @@ fn no_tools_message() { Put the following into the $HOME/.tool.toml file for the simplest configuration: - # ensure this directory is listed in $PATH - store_directory = "/path/to/install/directory" +### START ### + +# ensure this directory is listed in $PATH +store_directory = "/path/to/install/directory" + +[bat] +[exa] +[fd] +[ripgrep] - [bat] - [exa] - [fd] - [ripgrep] +### END ### For more details, refer to the official documentation: @@ -46,7 +56,8 @@ For more details, refer to the official documentation: const DONE: Emoji<'_, '_> = Emoji("✨ ", "* "); const DIRECTORY: Emoji<'_, '_> = Emoji("📁 ", "* "); -fn sync_tools(config: Config) { +/// Like `sync_from_config` but expects non-empty list of tools +pub fn sync_from_config_no_check(config: Config) { let store_directory = config.ensure_store_directory(); let tool_assets = prefetch(config.tools); @@ -64,8 +75,8 @@ fn sync_tools(config: Config) { let mut installed_tools: u64 = 0; for tool_asset in tool_assets { - let is_successs = installer.install(tool_asset); - if is_successs { + let is_success = installer.install(tool_asset); + if is_success { installed_tools += 1 } } diff --git a/src/sync/db.rs b/src/sync/db.rs index 231202c..7ceb0bb 100644 --- a/src/sync/db.rs +++ b/src/sync/db.rs @@ -110,3 +110,18 @@ pub fn build_db() -> BTreeMap { // tools } + +/// Format tool names of the database using a mutating formatting function +/// The result is something like this (depending on a function) +/// +/// ```toml +/// # [bat] +/// # [exa] +/// ``` +pub fn fmt_tool_names String>(fmt_tool: F) -> String { + build_db() + .keys() + .map(fmt_tool) + .collect::>() + .join("\n") +} diff --git a/tests/default-config.toml b/tests/default-config.toml index 0b3fbe5..12e0c51 100644 --- a/tests/default-config.toml +++ b/tests/default-config.toml @@ -1,15 +1,13 @@ -# This config file was generated for version 0.1.0 -# -# # tool-sync default configuration file +# This configuration is automatically generated by tool-sync 0.1.0 # /~https://github.com/chshersh/tool-sync -# This file was automatically generated by tool-sync -##################################################### -# +####################################### # +# Installation directory for all the tools: # store_directory = "$HOME/.local/bin" # -# tool-sync provides native support for some of the tools without the need to configure them -# Uncomment the tools you want to have them +# tool-sync provides native support for some of the tools without the need to +# configure them. Uncomment all the tools you want to install with a single +# 'tool sync' command: # # [bat] # [difftastic] @@ -18,22 +16,22 @@ # [ripgrep] # [tool-sync] # -# To add configuration for other tools these are the config options: -# [ripgrep] -# owner = "BurntSushi" -# repo = "ripgrep" -# exe_name = "rg" -# -# # Uncomment to download a specific version or tag. -# # Without this tag latest will be used -# # tag = "13.0.0" -# -# -# Asset name to download on linux OSes -# asset_name.linux = "x86_64-unknown-linux-musl" -# -# uncomment if you want to install on macOS as well -# asset_name.macos = "apple-darwin" -# -# uncomment if you want to install on Windows as well -# asset_name.windows = "x86_64-pc-windows-msvc" +# You can configure the installation of any tool by specifying corresponding options: +# +# [ripgrep] # Name of the tool (new or one of the hardcoded to override default settings) +# owner = "BurntSushi" # GitHub repository owner +# repo = "ripgrep" # GitHub repository name +# exe_name = "rg" # Executable name inside the asset + +# Uncomment to download a specific version or tag. +# Without this tag latest will be used +# tag = "13.0.0" + +# Asset name to download on linux OSes +# asset_name.linux = "x86_64-unknown-linux-musl" + +# Uncomment if you want to install on macOS as well +# asset_name.macos = "apple-darwin" + +# Uncomment if you want to install on Windows as well +# asset_name.windows = "x86_64-pc-windows-msvc" diff --git a/tests/only-ripgrep.toml b/tests/only-ripgrep.toml deleted file mode 100644 index 5eccbd0..0000000 --- a/tests/only-ripgrep.toml +++ /dev/null @@ -1,3 +0,0 @@ -store_directory = "only-ripgrep" - -[ripgrep] diff --git a/tests/full-database.toml b/tests/sync-full.toml similarity index 87% rename from tests/full-database.toml rename to tests/sync-full.toml index cbee660..8aa2e7c 100644 --- a/tests/full-database.toml +++ b/tests/sync-full.toml @@ -1,5 +1,5 @@ # a directory to store all tools -store_directory = "full-database" +store_directory = "sync-full" # the following tools will be installed in 'store_directory' [bat] diff --git a/tests/sync-ripgrep.toml b/tests/sync-ripgrep.toml new file mode 100644 index 0000000..c38fe9d --- /dev/null +++ b/tests/sync-ripgrep.toml @@ -0,0 +1,3 @@ +store_directory = "sync-ripgrep" + +[ripgrep]