feat(core): implement first draft of package install command

This commit is contained in:
Federico Terzi 2021-09-01 22:12:05 +02:00
parent 5714ebe131
commit 55c8ba6b75
6 changed files with 212 additions and 32 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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;

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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(())
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}

View File

@ -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<i32> = 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;
}

View File

@ -69,6 +69,7 @@ lazy_static! {
cli::env_path::new(),
cli::service::new(),
cli::workaround::new(),
cli::package::new(),
];
static ref ALIASES: Vec<CliAlias> = 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,19 +363,14 @@ 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("package")
.about("package-management commands")
.subcommand(install_subcommand.clone())
.subcommand(uninstall_subcommand.clone())
.subcommand(
SubCommand::with_name("list").about("List all installed packages"), // TODO: update <Package> and update all
)
.subcommand(
SubCommand::with_name("workaround")
.subcommand(
@ -352,6 +378,7 @@ fn main() {
.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