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
-
+
~ A feature rich terminal file transfer ~
@@ -73,7 +73,7 @@
/>
` 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 {