233 feature request subcommands (#234)

This commit is contained in:
Christian Visintin
2024-03-01 10:01:25 +01:00
committed by GitHub
parent 2a51ab984c
commit 679a829744
16 changed files with 154 additions and 103 deletions
+10
View File
@@ -1,6 +1,7 @@
# Changelog # Changelog
- [Changelog](#changelog) - [Changelog](#changelog)
- [0.13.0](#0130)
- [0.12.3](#0123) - [0.12.3](#0123)
- [0.12.2](#0122) - [0.12.2](#0122)
- [0.12.1](#0121) - [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 ## 0.12.3
Released on 06/10/2023 Released on 06/10/2023
+2 -2
View File
@@ -1,7 +1,7 @@
# termscp # termscp
<p align="center"> <p align="center">
<img src="/assets/images/termscp.svg" width="256" height="256" /> <img src="/assets/images/termscp.svg" alt="termscp logo" width="256" height="256" />
</p> </p>
<p align="center">~ A feature rich terminal file transfer ~</p> <p align="center">~ A feature rich terminal file transfer ~</p>
@@ -73,7 +73,7 @@
/></a> /></a>
<a href="https://github.com/veeso/termscp/stargazers" <a href="https://github.com/veeso/termscp/stargazers"
><img ><img
src="https://img.shields.io/github/stars/veeso/termscp.svg" src="https://img.shields.io/github/stars/veeso/termscp?style=flat"
alt="Repo stars" alt="Repo stars"
/></a> /></a>
<a href="https://crates.io/crates/termscp" <a href="https://crates.io/crates/termscp"
+14 -3
View File
@@ -6,6 +6,9 @@
- [AWS S3 address argument](#aws-s3-address-argument) - [AWS S3 address argument](#aws-s3-address-argument)
- [SMB address argument](#smb-address-argument) - [SMB address argument](#smb-address-argument)
- [How Password can be provided 🔐](#how-password-can-be-provided-) - [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 connection parameters](#s3-connection-parameters)
- [S3 credentials 🦊](#s3-credentials-) - [S3 credentials 🦊](#s3-credentials-)
- [File explorer 📂](#file-explorer-) - [File explorer 📂](#file-explorer-)
@@ -45,10 +48,7 @@ OR
- `-P, --password <password>` if address is provided, password will be this argument - `-P, --password <password>` if address is provided, password will be this argument
- `-b, --address-as-bookmark` resolve address argument as a bookmark name - `-b, --address-as-bookmark` resolve address argument as a bookmark name
- `-c, --config` Open termscp starting from the configuration page
- `-q, --quiet` Disable logging - `-q, --quiet` Disable logging
- `-t, --theme <path>` Import specified theme
- `-u, --update` Update termscp to latest version
- `-v, --version` Print version info - `-v, --version` Print version info
- `-h, --help` Print help page - `-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` - 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. - 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 <theme-file>`
#### Install latest version
Run termscp as `termscp update`
--- ---
## S3 connection parameters ## S3 connection parameters
-3
View File
@@ -45,10 +45,7 @@ OR
- `-P, --password <password>` si se proporciona la dirección, la contraseña será este argumento - `-P, --password <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 - `-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 - `-q, --quiet` Deshabilitar el registro
- `-t, --theme <path>` Importar tema especificado
- `-u, --update` Actualizar termscp a la última versión
- `-v, --version` Imprimir información de la versión - `-v, --version` Imprimir información de la versión
- `-h, --help` Imprimir página de ayuda - `-h, --help` Imprimir página de ayuda
-3
View File
@@ -43,10 +43,7 @@ ou
- `-P, --password <password>` si l'adresse est fournie, le mot de passe sera cet argument - `-P, --password <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 - `-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 - `-q, --quiet` Désactiver la journalisation
- `-t, --theme <path>` Importer le thème spécifié
- `-u, --update` Mettre à jour termscp vers la dernière version
- `-v, --version` Imprimer les informations sur la version - `-v, --version` Imprimer les informations sur la version
- `-h, --help` Imprimer la page d'aide - `-h, --help` Imprimer la page d'aide
-3
View File
@@ -43,10 +43,7 @@ O
- `-P, --password <password>` Se viene fornito l'argomento indirizzo, questa sarà la password utilizzata per autenticarsi - `-P, --password <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 - `-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 - `-q, --quiet` Disabilita i log
- `-t, --theme <path>` Importa il tema al percorso fornito
- `-u, --update` Aggiorna termscp all'ultima versione
- `-v, --version` Mostra a video le informazioni sulla versione attualmente installata - `-v, --version` Mostra a video le informazioni sulla versione attualmente installata
- `-h, --help` Mostra la pagina di aiuto. - `-h, --help` Mostra la pagina di aiuto.
+13 -3
View File
@@ -6,6 +6,9 @@
- [AWS S3 address argument](#aws-s3-address-argument) - [AWS S3 address argument](#aws-s3-address-argument)
- [SMB address argument](#smb-address-argument) - [SMB address argument](#smb-address-argument)
- [How Password can be provided 🔐](#how-password-can-be-provided-) - [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 connection parameters](#s3-connection-parameters)
- [S3 credentials 🦊](#s3-credentials-) - [S3 credentials 🦊](#s3-credentials-)
- [File explorer 📂](#file-explorer-) - [File explorer 📂](#file-explorer-)
@@ -43,10 +46,7 @@ OR
- `-P, --password <password>` if address is provided, password will be this argument - `-P, --password <password>` if address is provided, password will be this argument
- `-b, --address-as-bookmark` resolve address argument as a bookmark name - `-b, --address-as-bookmark` resolve address argument as a bookmark name
- `-c, --config` Open termscp starting from the configuration page
- `-q, --quiet` Disable logging - `-q, --quiet` Disable logging
- `-t, --theme <path>` Import specified theme
- `-u, --update` Update termscp to latest version
- `-v, --version` Print version info - `-v, --version` Print version info
- `-h, --help` Print help page - `-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` - 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. - 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 <theme-file>`
#### Install latest version
Run termscp as `termscp update`
--- ---
## S3 connection parameters ## S3 connection parameters
-2
View File
@@ -43,9 +43,7 @@ termscp启动时可以使用以下选项:
- `-P, --password <password>` 登陆密码 - `-P, --password <password>` 登陆密码
- `-b, --address-as-bookmark` 将地址参数解析为书签名称 - `-b, --address-as-bookmark` 将地址参数解析为书签名称
- `-c, --config` 打开termscp时打开配置页面
- `-q, --quiet` 禁用日志 - `-q, --quiet` 禁用日志
- `-t, --theme <path>` 导入自定义主题
- `-v, --version` 打印版本信息 - `-v, --version` 打印版本信息
- `-h, --help` 打开帮助 - `-h, --help` 打开帮助
+64 -25
View File
@@ -34,36 +34,25 @@ Address syntax can be:
Please, report issues to <https://github.com/veeso/termscp> Please, report issues to <https://github.com/veeso/termscp>
Please, consider supporting the author <https://ko-fi.com/veeso>")] Please, consider supporting the author <https://ko-fi.com/veeso>")]
pub struct Args { pub struct Args {
#[argh( #[argh(subcommand)]
switch, pub nested: Option<ArgsSubcommands>,
short = 'b', /// resolve address argument as a bookmark name
description = "resolve address argument as a bookmark name" #[argh(switch, short = 'b')]
)]
pub address_as_bookmark: bool, pub address_as_bookmark: bool,
#[argh(switch, short = 'c', description = "open termscp configuration")] /// enable TRACE log level
pub config: bool, #[argh(switch, short = 'D')]
#[argh(switch, short = 'D', description = "enable TRACE log level")]
pub debug: bool, pub debug: bool,
#[argh(option, short = 'P', description = "provide password from CLI")] /// provide password from CLI
#[argh(option, short = 'P')]
pub password: Option<String>, pub password: Option<String>,
#[argh(switch, short = 'q', description = "disable logging")] /// disable logging
#[argh(switch, short = 'q')]
pub quiet: bool, pub quiet: bool,
#[argh(option, short = 't', description = "import specified theme")] /// set UI ticks; default 10ms
pub theme: Option<String>, #[argh(option, short = 'T', default = "10")]
#[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"
)]
pub ticks: u64, pub ticks: u64,
#[argh(switch, short = 'v', description = "print version")] /// print version
#[argh(switch, short = 'v')]
pub version: bool, pub version: bool,
// -- positional // -- positional
#[argh( #[argh(
@@ -73,6 +62,33 @@ pub struct Args {
pub positional: Vec<String>, pub positional: Vec<String>,
} }
#[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 struct RunOpts {
pub remote: Remote, pub remote: Remote,
pub ticks: Duration, pub ticks: Duration,
@@ -80,6 +96,29 @@ pub struct RunOpts {
pub task: Task, 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 { impl Default for RunOpts {
fn default() -> Self { fn default() -> Self {
Self { Self {
-1
View File
@@ -3,7 +3,6 @@
//! `config` is the module which provides access to all the termscp configurations //! `config` is the module which provides access to all the termscp configurations
// export // export
pub use params::*;
pub mod bookmarks; pub mod bookmarks;
pub mod params; pub mod params;
+1 -4
View File
@@ -1081,10 +1081,7 @@ mod tests {
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap(); let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
// Execute // Execute
#[cfg(unix)] assert!(host.exec("echo 5").ok().unwrap().as_str().contains("5"));
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");
} }
#[test] #[test]
+46 -47
View File
@@ -32,7 +32,7 @@ mod utils;
// namespaces // namespaces
use activity_manager::{ActivityManager, NextActivity}; 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 filetransfer::FileTransferParams;
use system::logging::{self, LogLevel}; use system::logging::{self, LogLevel};
@@ -63,59 +63,57 @@ fn main() {
/// In case of success returns `RunOpts` /// In case of success returns `RunOpts`
/// in case something is wrong returns the error message /// in case something is wrong returns the error message
fn parse_args(args: Args) -> Result<RunOpts, String> { fn parse_args(args: Args) -> Result<RunOpts, String> {
let mut run_opts: RunOpts = RunOpts::default(); let run_opts = match args.nested {
// Version Some(ArgsSubcommands::Update(_)) => RunOpts::update(),
if args.version { Some(ArgsSubcommands::LoadTheme(args)) => RunOpts::import_theme(args.theme),
return Err(format!( Some(ArgsSubcommands::Config(_)) => RunOpts::config(),
"termscp - {TERMSCP_VERSION} - Developed by {TERMSCP_AUTHORS}", None => {
)); let mut run_opts: RunOpts = RunOpts::default();
} // Version
// Setup activity? if args.version {
if args.config { return Err(format!(
run_opts.task = Task::Activity(NextActivity::SetupActivity); "termscp - {TERMSCP_VERSION} - Developed by {TERMSCP_AUTHORS}",
} ));
// Logging }
if args.debug { // Logging
run_opts.log_level = LogLevel::Trace; if args.debug {
} else if args.quiet { run_opts.log_level = LogLevel::Trace;
run_opts.log_level = LogLevel::Off; } else if args.quiet {
} run_opts.log_level = LogLevel::Off;
// Match ticks }
run_opts.ticks = Duration::from_millis(args.ticks); // Match ticks
// @! extra modes run_opts.ticks = Duration::from_millis(args.ticks);
if let Some(theme) = args.theme.as_deref() { // Remote argument
run_opts.task = Task::ImportTheme(PathBuf::from(theme)); match parse_address_arg(&args) {
} Err(err) => return Err(err),
if args.update { Ok(Remote::None) => {}
run_opts.task = Task::InstallUpdate; Ok(remote) => {
} // Set params
// @! Ordinary mode run_opts.remote = remote;
// Remote argument // In this case the first activity will be FileTransfer
match parse_address_arg(&args) { run_opts.task = Task::Activity(NextActivity::FileTransfer);
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 // Local directory
if let Some(localdir) = args.positional.get(1) { if let Some(localdir) = args.positional.get(1) {
// Change working directory if local dir is set // Change working directory if local dir is set
let localdir: PathBuf = PathBuf::from(localdir); let localdir: PathBuf = PathBuf::from(localdir);
if let Err(err) = env::set_current_dir(localdir.as_path()) { if let Err(err) = env::set_current_dir(localdir.as_path()) {
return Err(format!("Bad working directory argument: {err}")); return Err(format!("Bad working directory argument: {err}"));
}
}
run_opts
} }
} };
Ok(run_opts) Ok(run_opts)
} }
/// Parse address argument from cli args /// Parse address argument from cli args
fn parse_address_arg(args: &Args) -> Result<Remote, String> { fn parse_address_arg(args: &Args) -> Result<Remote, String> {
if let Some(remote) = args.positional.get(0) { if let Some(remote) = args.positional.first() {
if args.address_as_bookmark { if args.address_as_bookmark {
Ok(Remote::Bookmark(BookmarkParams::new( Ok(Remote::Bookmark(BookmarkParams::new(
remote, remote,
@@ -197,5 +195,6 @@ fn run_activity(activity: NextActivity, ticks: Duration, remote: Remote) -> i32
Remote::None => {} Remote::None => {}
} }
manager.run(activity); manager.run(activity);
0 0
} }
+1 -1
View File
@@ -419,7 +419,7 @@ mod tests {
use tempfile::TempDir; use tempfile::TempDir;
use super::*; use super::*;
use crate::config::UserConfig; use crate::config::params::UserConfig;
use crate::utils::random::random_alphanumeric_with_len; use crate::utils::random::random_alphanumeric_with_len;
#[test] #[test]
+1 -1
View File
@@ -48,7 +48,7 @@ impl SshKeyStorage {
.query(host) .query(host)
.identity_file .identity_file
.as_ref() .as_ref()
.and_then(|x| x.get(0).cloned()); .and_then(|x| x.first().cloned());
key key
}) })
-3
View File
@@ -7,9 +7,6 @@ pub mod setup;
pub mod ssh_keys; pub mod ssh_keys;
pub mod theme; pub mod theme;
pub use setup::*;
pub use ssh_keys::*;
pub use theme::*;
use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::event::{Key, KeyEvent, KeyModifiers};
use tuirealm::tui::widgets::Clear; use tuirealm::tui::widgets::Clear;
use tuirealm::{Frame, Sub, SubClause, SubEventClause}; use tuirealm::{Frame, Sub, SubClause, SubEventClause};
+2 -2
View File
@@ -60,8 +60,8 @@ where
} }
(None, _) => comps.push(Component::ParentDir), (None, _) => comps.push(Component::ParentDir),
(Some(a), Some(b)) if comps.is_empty() && a == b => (), (Some(a), Some(b)) if comps.is_empty() && a == b => (),
(Some(a), Some(b)) if b == Component::CurDir => comps.push(a), (Some(a), Some(Component::CurDir)) => comps.push(a),
(Some(_), Some(b)) if b == Component::ParentDir => return None, (Some(_), Some(Component::ParentDir)) => return None,
(Some(a), Some(_)) => { (Some(a), Some(_)) => {
comps.push(Component::ParentDir); comps.push(Component::ParentDir);
for _ in itb { for _ in itb {