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