Skip to content

Commit

Permalink
Refactor commands into separate modules (#94)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
chshersh authored Sep 16, 2022
1 parent 2bb99cf commit 65b53bd
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 151 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
66 changes: 30 additions & 36 deletions src/config/template.rs
Original file line number Diff line number Diff line change
@@ -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::<Vec<String>>() {
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"),
)
}
22 changes: 19 additions & 3 deletions src/config/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -23,7 +24,22 @@ impl TomlError {
}
}

pub fn parse_file(config_path: &PathBuf) -> Result<Config, TomlError> {
pub fn with_parsed_file<F: FnOnce(Config)>(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<Config, TomlError> {
let contents = fs::read_to_string(config_path).map_err(|e| TomlError::IO(format!("{}", e)))?;

parse_string(&contents)
Expand Down Expand Up @@ -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);
};
}

Expand Down
1 change: 1 addition & 0 deletions src/infra.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod client;
pub mod err;
6 changes: 6 additions & 0 deletions src/err.rs → src/infra/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
32 changes: 32 additions & 0 deletions src/install.rs
Original file line number Diff line number Diff line change
@@ -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<String, ConfigAsset> = 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);
}
}
74 changes: 11 additions & 63 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<String, ConfigAsset> =
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::<Vec<String>>() {
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<F: FnOnce(Config)>(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>) -> PathBuf {
match config_path {
Expand All @@ -90,7 +42,3 @@ fn resolve_config_path(config_path: Option<PathBuf>) -> PathBuf {
},
}
}

fn generate_config() {
println!("{}", template::config_template());
}
33 changes: 22 additions & 11 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -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:
Expand All @@ -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);

Expand All @@ -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
}
}
Expand Down
Loading

0 comments on commit 65b53bd

Please sign in to comment.