From 2cf6cafdb6f03bf94a0b9a5247337e9de4bf1795 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Wed, 2 Jun 2021 11:54:00 +0200 Subject: [PATCH] feat(core): introduce migrate cli command --- Cargo.lock | 65 +++++++++++++++++ espanso/Cargo.toml | 5 ++ espanso/src/cli/migrate.rs | 144 +++++++++++++++++++++++++++++++++++++ espanso/src/cli/mod.rs | 1 + espanso/src/main.rs | 12 +++- 5 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 espanso/src/cli/migrate.rs diff --git a/Cargo.lock b/Cargo.lock index a27239a..f148f52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,32 @@ dependencies = [ "vec_map", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi", +] + [[package]] name = "const_fn" version = "0.4.5" @@ -281,6 +307,18 @@ dependencies = [ "libdbus-sys", ] +[[package]] +name = "dialoguer" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c" +dependencies = [ + "console", + "lazy_static", + "tempfile", + "zeroize", +] + [[package]] name = "diff" version = "0.1.12" @@ -348,6 +386,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -366,7 +410,9 @@ version = "1.0.0" dependencies = [ "anyhow", "clap", + "colored", "crossbeam", + "dialoguer", "dirs 3.0.1", "enum-as-inner", "espanso-clipboard", @@ -376,11 +422,13 @@ dependencies = [ "espanso-inject", "espanso-ipc", "espanso-match", + "espanso-migrate", "espanso-modulo", "espanso-path", "espanso-render", "espanso-ui", "fs2", + "fs_extra", "html2text", "lazy_static", "log", @@ -392,6 +440,7 @@ dependencies = [ "serde_json", "serde_yaml", "simplelog", + "tempdir", "thiserror", "winapi", ] @@ -1554,6 +1603,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "test-case" version = "1.1.0" @@ -1811,6 +1870,12 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" + [[package]] name = "zip" version = "0.5.12" diff --git a/espanso/Cargo.toml b/espanso/Cargo.toml index d9d203e..c9403d6 100644 --- a/espanso/Cargo.toml +++ b/espanso/Cargo.toml @@ -31,6 +31,7 @@ espanso-render = { path = "../espanso-render" } espanso-path = { path = "../espanso-path" } espanso-ipc = { path = "../espanso-ipc" } espanso-modulo = { path = "../espanso-modulo", optional = true } +espanso-migrate = { path = "../espanso-migrate" } maplit = "1.0.2" simplelog = "0.9.0" log = "0.4.14" @@ -48,6 +49,10 @@ html2text = "0.2.1" log-panics = "2.0.0" fs2 = "0.4.3" serde_yaml = "0.8.17" +fs_extra = "1.2.0" +dialoguer = "0.8.0" +colored = "2.0.0" +tempdir = "0.3.7" [target.'cfg(windows)'.dependencies] named_pipe = "0.4.1" diff --git a/espanso/src/cli/migrate.rs b/espanso/src/cli/migrate.rs new file mode 100644 index 0000000..5fbc4fe --- /dev/null +++ b/espanso/src/cli/migrate.rs @@ -0,0 +1,144 @@ +/* + * 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 std::path::PathBuf; + +use super::{CliModule, CliModuleArgs}; +use colored::*; +use dialoguer::Confirm; +use fs_extra::dir::CopyOptions; +use tempdir::TempDir; + +pub fn new() -> CliModule { + CliModule { + requires_paths: true, + requires_config: true, + subcommand: "migrate".to_string(), + entry: migrate_main, + ..Default::default() + } +} + +fn migrate_main(args: CliModuleArgs) -> i32 { + let paths = args.paths.expect("missing paths argument"); + + if !args.is_legacy_config { + eprintln!("Can't migrate configurations, as the default directory [1] is already encoded with the new format"); + eprintln!("[1]: {:?}", paths.config); + eprintln!("The migration tool is only meant to convert the espanso's legacy configuration format (prior to"); + eprintln!("version 0.7.3) to the new one (since version 2.0)"); + return 1; + } + + // TODO: check if legacy version is still running + + let target_backup_dir = find_available_backup_dir(); + + println!("\n{}\n", "Welcome to espanso v2!".bold()); + println!("This migration tool will help you to smoothly transition to the new espanso v2 configuration format."); + println!(""); + println!( + "1. Firstly, espanso will {} your current configuration, located in:\n", + "backup".green().bold() + ); + println!(" {}\n", paths.config.to_string_lossy().italic()); + println!(" into this folder:\n"); + println!(" {}\n", target_backup_dir.to_string_lossy().italic()); + println!( + "2. Then, it will {} your configuration to the new format, replacing", + "convert".bold().green() + ); + println!(" the current content of the config directory."); + println!(""); + + if !Confirm::new() + .with_prompt("Do you want to proceed?") + .default(true) + .interact() + .expect("unable to read choice") + { + return 2; + } + + println!("Backing up your configuration..."); + + fs_extra::dir::copy( + &paths.config, + &target_backup_dir, + &CopyOptions { + copy_inside: true, + ..Default::default() + }, + ) + .expect("unable to backup the current config"); + println!("{}", "Backup completed!".green()); + + println!("Converting the configuration..."); + let temp_dir = TempDir::new("espanso-migrate-out").expect("unable to create temporary directory"); + let temp_out_dir = temp_dir.path().join("out"); + espanso_migrate::migrate(&paths.config, &paths.packages, &temp_out_dir) + .expect("an error occurred while converting the configuration"); + println!("{}", "Conversion completed!".green()); + + println!("Replacing old configuration with the new one..."); + + let mut to_be_removed = Vec::new(); + let legacy_dir_content = fs_extra::dir::get_dir_content(&paths.config).expect("unable to list legacy dir files"); + to_be_removed.extend(legacy_dir_content.files); + to_be_removed.extend(legacy_dir_content.directories); + fs_extra::remove_items(&to_be_removed).expect("unable to remove previous configuration"); + fs_extra::dir::copy( + &temp_out_dir, + &paths.config, + &CopyOptions { + copy_inside: true, + ..Default::default() + }, + ) + .expect("unable to copy new configuration into target location"); + + let target_packages_dir = &paths.config.join("match").join("packages"); + if !target_packages_dir.is_dir() { + std::fs::create_dir_all(target_packages_dir).expect("unable to create new packages directory"); + } + + println!("{}", "Configuration successfully migrated!".green()); + + 0 +} + +fn find_available_backup_dir() -> PathBuf { + for i in 1..20 { + let num = if i > 1 { + format!("-{}", i) + } else { + "".to_string() + }; + + let target_backup_dir = dirs::document_dir() + .expect("unable to generate backup directory") + .join(format!("espanso-migrate-backup{}", num)); + + if !target_backup_dir.is_dir() { + return target_backup_dir; + } + } + + panic!("could not generate valid backup directory"); +} diff --git a/espanso/src/cli/mod.rs b/espanso/src/cli/mod.rs index 42dd910..e27e3aa 100644 --- a/espanso/src/cli/mod.rs +++ b/espanso/src/cli/mod.rs @@ -23,6 +23,7 @@ use espanso_path::Paths; pub mod daemon; pub mod log; +pub mod migrate; pub mod modulo; pub mod path; pub mod worker; diff --git a/espanso/src/main.rs b/espanso/src/main.rs index b03ccb0..41e9495 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -27,7 +27,7 @@ use std::path::PathBuf; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use cli::{CliModule, CliModuleArgs}; -use log::{error, info}; +use log::{error, info, warn}; use logging::FileProxy; use simplelog::{ CombinedLogger, ConfigBuilder, LevelFilter, TermLogger, TerminalMode, WriteLogger, @@ -55,6 +55,7 @@ lazy_static! { cli::worker::new(), cli::daemon::new(), cli::modulo::new(), + cli::migrate::new(), ]; } @@ -218,6 +219,10 @@ fn main() { ) .subcommand(SubCommand::with_name("base").about("Print the default match file path.")), ) + .subcommand( + SubCommand::with_name("migrate") + .about("Automatically migrate legacy config files to the new v2 format.") + ) // .subcommand(SubCommand::with_name("match") // .about("List and execute matches from the CLI") // .subcommand(SubCommand::with_name("list") @@ -353,6 +358,11 @@ fn main() { cli_args.is_legacy_config = is_legacy_config; cli_args.config_store = Some(config_store); cli_args.match_store = Some(match_store); + + if is_legacy_config { + warn!("espanso is reading the configuration using compatibility mode, thus some features might not be available"); + warn!("you can migrate to the new configuration format by running 'espanso migrate' in a terminal"); + } } if handler.enable_logs {