From 55c8ba6b752d42cf6501e541f4278d3fd3a62342 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 1 Sep 2021 22:12:05 +0200 Subject: [PATCH] feat(core): implement first draft of package install command --- Cargo.lock | 2 + espanso/src/cli/mod.rs | 1 + espanso/src/cli/package/install.rs | 86 ++++++++++++++++++++++++++++++ espanso/src/cli/package/mod.rs | 57 ++++++++++++++++++++ espanso/src/exit_code.rs | 13 +++-- espanso/src/main.rs | 85 +++++++++++++++++++---------- 6 files changed, 212 insertions(+), 32 deletions(-) create mode 100644 espanso/src/cli/package/install.rs create mode 100644 espanso/src/cli/package/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 2359d26..fc7edb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,12 +784,14 @@ name = "espanso-package" version = "0.1.0" dependencies = [ "anyhow", + "fs_extra", "glob", "lazy_static", "log", "natord", "regex", "reqwest", + "scopeguard", "serde", "serde_json", "serde_yaml", diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs index b792645..fba2bd8 100644 --- a/espanso/src/cli/mod.rs +++ b/espanso/src/cli/mod.rs @@ -29,6 +29,7 @@ pub mod launcher; pub mod log; pub mod migrate; pub mod modulo; +pub mod package; pub mod path; pub mod service; pub mod util; diff --git a/espanso/src/cli/package/install.rs b/espanso/src/cli/package/install.rs new file mode 100644 index 0000000..2e2fe5d --- /dev/null +++ b/espanso/src/cli/package/install.rs @@ -0,0 +1,86 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use anyhow::{anyhow, Context, Result}; +use clap::ArgMatches; +use espanso_package::{PackageSpecifier, SaveOptions}; +use espanso_path::Paths; + +use crate::info_println; + +pub fn install_package(paths: &Paths, matches: &ArgMatches) -> Result<()> { + let package_name = matches + .value_of("package_name") + .ok_or(anyhow!("missing package name"))?; + let version = matches.value_of("version"); + let force = matches.is_present("force"); + + info_println!( + "installing package: {} - version: {}", + package_name, + version.unwrap_or("latest") + ); + + let package_specifier = if let Some(git_repo) = matches.value_of("git") { + let git_branch = matches.value_of("git-branch"); + let use_native_git = matches.is_present("use-native-git"); + + PackageSpecifier { + name: package_name.to_string(), + version: version.map(String::from), + git_repo_url: Some(git_repo.to_string()), + git_branch: git_branch.map(String::from), + use_native_git, + } + } else { + // Install from the hub + + PackageSpecifier { + name: package_name.to_string(), + version: version.map(String::from), + ..Default::default() + } + }; + + // TODO: if git is specified, make sure external is as well (or warn otherwise) + + let package_provider = espanso_package::get_provider(&package_specifier) + .context("unable to obtain compatible package provider")?; + + info_println!("using package provider: {}", package_provider.name()); + + let package = package_provider.download(&package_specifier)?; + + info_println!( + "found package: {} - version: {}", + package.name(), + package.version() + ); + + let archiver = + espanso_package::get_archiver(&paths.packages).context("unable to get package archiver")?; + + archiver.save(&*package, &package_specifier, &SaveOptions { + overwrite_existing: force, + }).context("unable to save package")?; + + info_println!("package installed!"); + + Ok(()) +} diff --git a/espanso/src/cli/package/mod.rs b/espanso/src/cli/package/mod.rs new file mode 100644 index 0000000..c21c802 --- /dev/null +++ b/espanso/src/cli/package/mod.rs @@ -0,0 +1,57 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use crate::{ + error_eprintln, + exit_code::{configure_custom_panic_hook, PACKAGE_INSTALL_FAILED, PACKAGE_SUCCESS}, +}; + +use super::{CliModule, CliModuleArgs}; + +mod install; + +pub fn new() -> CliModule { + CliModule { + enable_logs: true, + disable_logs_terminal_output: true, + requires_paths: true, + subcommand: "package".to_string(), + log_mode: super::LogMode::AppendOnly, + entry: package_main, + ..Default::default() + } +} + +fn package_main(args: CliModuleArgs) -> i32 { + configure_custom_panic_hook(); + + let paths = args.paths.expect("missing paths argument"); + let cli_args = args.cli_args.expect("missing cli_args"); + + if let Some(sub_matches) = cli_args.subcommand_matches("install") { + if let Err(err) = install::install_package(&paths, sub_matches) { + error_eprintln!("unable to install package: {:?}", err); + return PACKAGE_INSTALL_FAILED; + } + } + + // TODO: uninstall, list, update + + PACKAGE_SUCCESS +} diff --git a/espanso/src/exit_code.rs b/espanso/src/exit_code.rs index 9ea341c..ab1a091 100644 --- a/espanso/src/exit_code.rs +++ b/espanso/src/exit_code.rs @@ -55,8 +55,14 @@ pub const WORKAROUND_SUCCESS: i32 = 0; pub const WORKAROUND_FAILURE: i32 = 1; pub const WORKAROUND_NOT_AVAILABLE: i32 = 2; +pub const PACKAGE_SUCCESS: i32 = 0; +pub const PACKAGE_UNEXPECTED_FAILURE: i32 = 1; +pub const PACKAGE_INSTALL_FAILED: i32 = 2; + use std::sync::Mutex; +use crate::error_eprintln; + lazy_static! { static ref CURRENT_PANIC_EXIT_CODE: Mutex = Mutex::new(MIGRATE_UNEXPECTED_FAILURE); } @@ -80,7 +86,7 @@ pub fn configure_custom_panic_hook() { match info.location() { Some(location) => { - eprintln!( + error_eprintln!( "ERROR: '{}' panicked at '{}': {}:{}", thread, msg, @@ -88,7 +94,9 @@ pub fn configure_custom_panic_hook() { location.line(), ); } - None => eprintln!("ERROR: '{}' panicked at '{}'", thread, msg,), + None => { + error_eprintln!("ERROR: '{}' panicked at '{}'", thread, msg); + } } let exit_code = CURRENT_PANIC_EXIT_CODE.lock().unwrap(); @@ -102,4 +110,3 @@ pub fn update_panic_exit_code(exit_code: i32) { .expect("unable to update panic exit code"); *lock = exit_code; } - diff --git a/espanso/src/main.rs b/espanso/src/main.rs index 356d59a..9781745 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -69,6 +69,7 @@ lazy_static! { cli::env_path::new(), cli::service::new(), cli::workaround::new(), + cli::package::new(), ]; static ref ALIASES: Vec = vec![ CliAlias { @@ -79,6 +80,14 @@ lazy_static! { subcommand: "stop".to_owned(), forward_into: "service".to_owned(), }, + CliAlias { + subcommand: "install".to_owned(), + forward_into: "package".to_owned(), + }, + CliAlias { + subcommand: "uninstall".to_owned(), + forward_into: "package".to_owned(), + }, ]; } @@ -86,7 +95,7 @@ fn main() { util::attach_console(); let install_subcommand = SubCommand::with_name("install") - .about("Install a package. Equivalent to 'espanso package install'") + .about("Install a package") .arg( Arg::with_name("external") .short("e") @@ -97,21 +106,43 @@ fn main() { ) .arg(Arg::with_name("package_name").help("Package name")) .arg( - Arg::with_name("repository_url") - .help("(Optional) Link to GitHub repository") + Arg::with_name("version") + .long("version") .required(false) - .default_value("hub"), + .takes_value(true) + .help("Force a particular version to be installed instead of the latest available."), ) .arg( - Arg::with_name("proxy") - .help("Use a proxy, should be used as --proxy=https://proxy:1234") + Arg::with_name("git") + .long("git") .required(false) - .long("proxy") - .takes_value(true), + .takes_value(true) + .help("Git repository from which espanso should install the package."), + ) + .arg( + Arg::with_name("git-branch") + .long("git-branch") + .required(false) + .takes_value(true) + .help("Force espanso to search for the package on a specific git branch"), + ) + .arg( + Arg::with_name("force") + .long("force") + .required(false) + .takes_value(false) + .help("Overwrite the package if already installed"), + ) + .arg( + Arg::with_name("use-native-git") + .long("use-native-git") + .required(false) + .takes_value(false) + .help("If specified, espanso will use the 'git' command instead of trying direct methods."), ); let uninstall_subcommand = SubCommand::with_name("uninstall") - .about("Remove an installed package. Equivalent to 'espanso package uninstall'") + .about("Remove a package") .arg(Arg::with_name("package_name").help("Package name")); let start_subcommand = SubCommand::with_name("start") @@ -332,26 +363,22 @@ fn main() { // ) // ) // ) - // Package manager - // .subcommand(SubCommand::with_name("package") - // .about("Espanso package manager commands") - // .subcommand(install_subcommand.clone()) - // .subcommand(uninstall_subcommand.clone()) - // .subcommand(SubCommand::with_name("list") - // .about("List all installed packages") - // .arg(Arg::with_name("full") - // .help("Print all package info") - // .long("full"))) - // .subcommand(SubCommand::with_name("refresh") - // .about("Update espanso package index")) - // ) .subcommand( - SubCommand::with_name("workaround") + SubCommand::with_name("package") + .about("package-management commands") + .subcommand(install_subcommand.clone()) + .subcommand(uninstall_subcommand.clone()) .subcommand( - SubCommand::with_name("secure-input") - .about("Attempt to disable secure input by automating the common steps."), + SubCommand::with_name("list").about("List all installed packages"), // TODO: update and update all ) - .about("A collection of workarounds to solve some common problems."), + .subcommand( + SubCommand::with_name("workaround") + .subcommand( + SubCommand::with_name("secure-input") + .about("Attempt to disable secure input by automating the common steps."), + ) + .about("A collection of workarounds to solve some common problems."), + ), ) .subcommand( SubCommand::with_name("worker") @@ -368,9 +395,9 @@ fn main() { .required(false) .takes_value(false), ), - ); - // .subcommand(install_subcommand) - // .subcommand(uninstall_subcommand); + ) + .subcommand(install_subcommand) + .subcommand(uninstall_subcommand); // TODO: explain that the register and unregister commands are only meaningful // when using the system daemon manager on macOS and Linux