diff --git a/CHANGELOG.md b/CHANGELOG.md index 2704c89..0924def 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) + - [0.13.0](#0130) - [0.12.3](#0123) - [0.12.2](#0122) - [0.12.1](#0121) @@ -34,6 +35,15 @@ --- +## 0.13.0 + +Released on + +- Added CLI subcommands + - Changed `-t` to `theme` + - Changed `-u` to `update` + - Changed `-c` to `config` + ## 0.12.3 Released on 06/10/2023 diff --git a/README.md b/README.md index 01c1544..48628cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # termscp

- + termscp logo

~ A feature rich terminal file transfer ~

@@ -73,7 +73,7 @@ /> Repo stars ` if address is provided, password will be this argument - `-b, --address-as-bookmark` resolve address argument as a bookmark name -- `-c, --config` Open termscp starting from the configuration page - `-q, --quiet` Disable logging -- `-t, --theme ` Import specified theme -- `-u, --update` Update termscp to latest version - `-v, --version` Print version info - `-h, --help` Print help page @@ -131,6 +131,17 @@ Password can be basically provided through 3 ways when address argument is provi - Via `sshpass`: you can provide password via `sshpass`, e.g. `sshpass -f ~/.ssh/topsecret.key termscp cvisintin@192.168.1.31` - You will be prompted for it: if you don't use any of the previous methods, you will be prompted for the password, as happens with the more classics tools such as `scp`, `ssh`, etc. + +### Subcommands + +#### Import a theme + +Run termscp as `termscp theme ` + +#### Install latest version + +Run termscp as `termscp update` + --- ## S3 connection parameters diff --git a/docs/es/man.md b/docs/es/man.md index 243eb01..569ce68 100644 --- a/docs/es/man.md +++ b/docs/es/man.md @@ -45,10 +45,7 @@ OR - `-P, --password ` si se proporciona la dirección, la contraseña será este argumento - `-b, --address-as-bookmark` resuelve el argumento de la dirección como un nombre de marcador -- `-c, --config` Abrir termscp comenzando desde la página de configuración - `-q, --quiet` Deshabilitar el registro -- `-t, --theme ` Importar tema especificado -- `-u, --update` Actualizar termscp a la última versión - `-v, --version` Imprimir información de la versión - `-h, --help` Imprimir página de ayuda diff --git a/docs/fr/man.md b/docs/fr/man.md index bb1d575..185b1fe 100644 --- a/docs/fr/man.md +++ b/docs/fr/man.md @@ -43,10 +43,7 @@ ou - `-P, --password ` si l'adresse est fournie, le mot de passe sera cet argument - `-b, --address-as-bookmark` résoudre l'argument d'adresse en tant que nom de signet -- `-c, --config` Ouvrir termscp à partir de la page de configuration - `-q, --quiet` Désactiver la journalisation -- `-t, --theme ` Importer le thème spécifié -- `-u, --update` Mettre à jour termscp vers la dernière version - `-v, --version` Imprimer les informations sur la version - `-h, --help` Imprimer la page d'aide diff --git a/docs/it/man.md b/docs/it/man.md index f707c79..f11705c 100644 --- a/docs/it/man.md +++ b/docs/it/man.md @@ -43,10 +43,7 @@ O - `-P, --password ` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi - `-b, --address-as-bookmark` risolve l'argomento indirizzo come nome di un segnalibro -- `-c, --config` Apri la configurazione di termscp - `-q, --quiet` Disabilita i log -- `-t, --theme ` Importa il tema al percorso fornito -- `-u, --update` Aggiorna termscp all'ultima versione - `-v, --version` Mostra a video le informazioni sulla versione attualmente installata - `-h, --help` Mostra la pagina di aiuto. diff --git a/docs/man.md b/docs/man.md index 4059f3f..850ed1a 100644 --- a/docs/man.md +++ b/docs/man.md @@ -6,6 +6,9 @@ - [AWS S3 address argument](#aws-s3-address-argument) - [SMB address argument](#smb-address-argument) - [How Password can be provided 🔐](#how-password-can-be-provided-) + - [Subcommands](#subcommands) + - [Import a theme](#import-a-theme) + - [Install latest version](#install-latest-version) - [S3 connection parameters](#s3-connection-parameters) - [S3 credentials 🦊](#s3-credentials-) - [File explorer 📂](#file-explorer-) @@ -43,10 +46,7 @@ OR - `-P, --password ` if address is provided, password will be this argument - `-b, --address-as-bookmark` resolve address argument as a bookmark name -- `-c, --config` Open termscp starting from the configuration page - `-q, --quiet` Disable logging -- `-t, --theme ` Import specified theme -- `-u, --update` Update termscp to latest version - `-v, --version` Print version info - `-h, --help` Print help page @@ -129,6 +129,16 @@ Password can be basically provided through 3 ways when address argument is provi - Via `sshpass`: you can provide password via `sshpass`, e.g. `sshpass -f ~/.ssh/topsecret.key termscp cvisintin@192.168.1.31` - You will be prompted for it: if you don't use any of the previous methods, you will be prompted for the password, as happens with the more classics tools such as `scp`, `ssh`, etc. +### Subcommands + +#### Import a theme + +Run termscp as `termscp theme ` + +#### Install latest version + +Run termscp as `termscp update` + --- ## S3 connection parameters diff --git a/docs/zh-CN/man.md b/docs/zh-CN/man.md index 6138d11..fce5b7e 100644 --- a/docs/zh-CN/man.md +++ b/docs/zh-CN/man.md @@ -43,9 +43,7 @@ termscp启动时可以使用以下选项: - `-P, --password ` 登陆密码 - `-b, --address-as-bookmark` 将地址参数解析为书签名称 -- `-c, --config` 打开termscp时打开配置页面 - `-q, --quiet` 禁用日志 -- `-t, --theme ` 导入自定义主题 - `-v, --version` 打印版本信息 - `-h, --help` 打开帮助 diff --git a/src/cli_opts.rs b/src/cli_opts.rs index 44c7f0e..fddba55 100644 --- a/src/cli_opts.rs +++ b/src/cli_opts.rs @@ -34,36 +34,25 @@ Address syntax can be: Please, report issues to Please, consider supporting the author ")] pub struct Args { - #[argh( - switch, - short = 'b', - description = "resolve address argument as a bookmark name" - )] + #[argh(subcommand)] + pub nested: Option, + /// resolve address argument as a bookmark name + #[argh(switch, short = 'b')] pub address_as_bookmark: bool, - #[argh(switch, short = 'c', description = "open termscp configuration")] - pub config: bool, - #[argh(switch, short = 'D', description = "enable TRACE log level")] + /// enable TRACE log level + #[argh(switch, short = 'D')] pub debug: bool, - #[argh(option, short = 'P', description = "provide password from CLI")] + /// provide password from CLI + #[argh(option, short = 'P')] pub password: Option, - #[argh(switch, short = 'q', description = "disable logging")] + /// disable logging + #[argh(switch, short = 'q')] pub quiet: bool, - #[argh(option, short = 't', description = "import specified theme")] - pub theme: Option, - #[argh( - switch, - short = 'u', - description = "update termscp to the latest version" - )] - pub update: bool, - #[argh( - option, - short = 'T', - default = "10", - description = "set UI ticks; default 10ms" - )] + /// set UI ticks; default 10ms + #[argh(option, short = 'T', default = "10")] pub ticks: u64, - #[argh(switch, short = 'v', description = "print version")] + /// print version + #[argh(switch, short = 'v')] pub version: bool, // -- positional #[argh( @@ -73,6 +62,33 @@ pub struct Args { pub positional: Vec, } +#[derive(FromArgs)] +#[argh(subcommand)] +pub enum ArgsSubcommands { + Config(ConfigArgs), + LoadTheme(LoadThemeArgs), + Update(UpdateArgs), +} + +#[derive(FromArgs)] +/// open termscp configuration +#[argh(subcommand, name = "config")] +pub struct ConfigArgs {} + +#[derive(FromArgs)] +/// import the specified theme +#[argh(subcommand, name = "update")] +pub struct UpdateArgs {} + +#[derive(FromArgs)] +/// import the specified theme +#[argh(subcommand, name = "theme")] +pub struct LoadThemeArgs { + #[argh(positional)] + /// theme file + pub theme: PathBuf, +} + pub struct RunOpts { pub remote: Remote, pub ticks: Duration, @@ -80,6 +96,29 @@ pub struct RunOpts { pub task: Task, } +impl RunOpts { + pub fn config() -> Self { + Self { + task: Task::Activity(NextActivity::SetupActivity), + ..Default::default() + } + } + + pub fn update() -> Self { + Self { + task: Task::InstallUpdate, + ..Default::default() + } + } + + pub fn import_theme(theme: PathBuf) -> Self { + Self { + task: Task::ImportTheme(theme), + ..Default::default() + } + } +} + impl Default for RunOpts { fn default() -> Self { Self { diff --git a/src/config/mod.rs b/src/config/mod.rs index f279682..bbd7a8b 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,7 +3,6 @@ //! `config` is the module which provides access to all the termscp configurations // export -pub use params::*; pub mod bookmarks; pub mod params; diff --git a/src/host/mod.rs b/src/host/mod.rs index b0d45f1..4679cd6 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -1081,10 +1081,7 @@ mod tests { let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); // Execute - #[cfg(unix)] - assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n"); - #[cfg(windows)] - assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n"); + assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5")); } #[test] diff --git a/src/main.rs b/src/main.rs index 9b39251..9e1e839 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ mod utils; // namespaces use activity_manager::{ActivityManager, NextActivity}; -use cli_opts::{Args, BookmarkParams, HostParams, Remote, RunOpts, Task}; +use cli_opts::{Args, ArgsSubcommands, BookmarkParams, HostParams, Remote, RunOpts, Task}; use filetransfer::FileTransferParams; use system::logging::{self, LogLevel}; @@ -63,59 +63,57 @@ fn main() { /// In case of success returns `RunOpts` /// in case something is wrong returns the error message fn parse_args(args: Args) -> Result { - let mut run_opts: RunOpts = RunOpts::default(); - // Version - if args.version { - return Err(format!( - "termscp - {TERMSCP_VERSION} - Developed by {TERMSCP_AUTHORS}", - )); - } - // Setup activity? - if args.config { - run_opts.task = Task::Activity(NextActivity::SetupActivity); - } - // Logging - if args.debug { - run_opts.log_level = LogLevel::Trace; - } else if args.quiet { - run_opts.log_level = LogLevel::Off; - } - // Match ticks - run_opts.ticks = Duration::from_millis(args.ticks); - // @! extra modes - if let Some(theme) = args.theme.as_deref() { - run_opts.task = Task::ImportTheme(PathBuf::from(theme)); - } - if args.update { - run_opts.task = Task::InstallUpdate; - } - // @! Ordinary mode - // Remote argument - match parse_address_arg(&args) { - Err(err) => return Err(err), - Ok(Remote::None) => {} - Ok(remote) => { - // Set params - run_opts.remote = remote; - // In this case the first activity will be FileTransfer - run_opts.task = Task::Activity(NextActivity::FileTransfer); - } - } + let run_opts = match args.nested { + Some(ArgsSubcommands::Update(_)) => RunOpts::update(), + Some(ArgsSubcommands::LoadTheme(args)) => RunOpts::import_theme(args.theme), + Some(ArgsSubcommands::Config(_)) => RunOpts::config(), + None => { + let mut run_opts: RunOpts = RunOpts::default(); + // Version + if args.version { + return Err(format!( + "termscp - {TERMSCP_VERSION} - Developed by {TERMSCP_AUTHORS}", + )); + } + // Logging + if args.debug { + run_opts.log_level = LogLevel::Trace; + } else if args.quiet { + run_opts.log_level = LogLevel::Off; + } + // Match ticks + run_opts.ticks = Duration::from_millis(args.ticks); + // Remote argument + match parse_address_arg(&args) { + Err(err) => return Err(err), + Ok(Remote::None) => {} + Ok(remote) => { + // Set params + run_opts.remote = remote; + // In this case the first activity will be FileTransfer + run_opts.task = Task::Activity(NextActivity::FileTransfer); + } + } - // Local directory - if let Some(localdir) = args.positional.get(1) { - // Change working directory if local dir is set - let localdir: PathBuf = PathBuf::from(localdir); - if let Err(err) = env::set_current_dir(localdir.as_path()) { - return Err(format!("Bad working directory argument: {err}")); + // Local directory + if let Some(localdir) = args.positional.get(1) { + // Change working directory if local dir is set + let localdir: PathBuf = PathBuf::from(localdir); + if let Err(err) = env::set_current_dir(localdir.as_path()) { + return Err(format!("Bad working directory argument: {err}")); + } + } + + run_opts } - } + }; + Ok(run_opts) } /// Parse address argument from cli args fn parse_address_arg(args: &Args) -> Result { - if let Some(remote) = args.positional.get(0) { + if let Some(remote) = args.positional.first() { if args.address_as_bookmark { Ok(Remote::Bookmark(BookmarkParams::new( remote, @@ -197,5 +195,6 @@ fn run_activity(activity: NextActivity, ticks: Duration, remote: Remote) -> i32 Remote::None => {} } manager.run(activity); + 0 } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 716f91f..7d79999 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -419,7 +419,7 @@ mod tests { use tempfile::TempDir; use super::*; - use crate::config::UserConfig; + use crate::config::params::UserConfig; use crate::utils::random::random_alphanumeric_with_len; #[test] diff --git a/src/system/sshkey_storage.rs b/src/system/sshkey_storage.rs index 60cf63d..477b378 100644 --- a/src/system/sshkey_storage.rs +++ b/src/system/sshkey_storage.rs @@ -48,7 +48,7 @@ impl SshKeyStorage { .query(host) .identity_file .as_ref() - .and_then(|x| x.get(0).cloned()); + .and_then(|x| x.first().cloned()); key }) diff --git a/src/ui/activities/setup/view/mod.rs b/src/ui/activities/setup/view/mod.rs index 1e9b174..2cd56bb 100644 --- a/src/ui/activities/setup/view/mod.rs +++ b/src/ui/activities/setup/view/mod.rs @@ -7,9 +7,6 @@ pub mod setup; pub mod ssh_keys; pub mod theme; -pub use setup::*; -pub use ssh_keys::*; -pub use theme::*; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::tui::widgets::Clear; use tuirealm::{Frame, Sub, SubClause, SubEventClause}; diff --git a/src/utils/path.rs b/src/utils/path.rs index f121833..ea90e73 100644 --- a/src/utils/path.rs +++ b/src/utils/path.rs @@ -60,8 +60,8 @@ where } (None, _) => comps.push(Component::ParentDir), (Some(a), Some(b)) if comps.is_empty() && a == b => (), - (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), - (Some(_), Some(b)) if b == Component::ParentDir => return None, + (Some(a), Some(Component::CurDir)) => comps.push(a), + (Some(_), Some(Component::ParentDir)) => return None, (Some(a), Some(_)) => { comps.push(Component::ParentDir); for _ in itb {