mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
233 feature request subcommands (#234)
This commit is contained in:
committed by
GitHub
parent
2a51ab984c
commit
679a829744
10
CHANGELOG.md
10
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# termscp
|
||||
|
||||
<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 align="center">~ A feature rich terminal file transfer ~</p>
|
||||
@@ -73,7 +73,7 @@
|
||||
/></a>
|
||||
<a href="https://github.com/veeso/termscp/stargazers"
|
||||
><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"
|
||||
/></a>
|
||||
<a href="https://crates.io/crates/termscp"
|
||||
|
||||
@@ -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-)
|
||||
@@ -45,10 +48,7 @@ OR
|
||||
|
||||
- `-P, --password <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 <path>` 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 <theme-file>`
|
||||
|
||||
#### Install latest version
|
||||
|
||||
Run termscp as `termscp update`
|
||||
|
||||
---
|
||||
|
||||
## S3 connection parameters
|
||||
|
||||
@@ -45,10 +45,7 @@ OR
|
||||
|
||||
- `-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
|
||||
- `-c, --config` Abrir termscp comenzando desde la página de configuración
|
||||
- `-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
|
||||
- `-h, --help` Imprimir página de ayuda
|
||||
|
||||
|
||||
@@ -43,10 +43,7 @@ ou
|
||||
|
||||
- `-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
|
||||
- `-c, --config` Ouvrir termscp à partir de la page de configuration
|
||||
- `-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
|
||||
- `-h, --help` Imprimer la page d'aide
|
||||
|
||||
|
||||
@@ -43,10 +43,7 @@ O
|
||||
|
||||
- `-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
|
||||
- `-c, --config` Apri la configurazione di termscp
|
||||
- `-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
|
||||
- `-h, --help` Mostra la pagina di aiuto.
|
||||
|
||||
|
||||
16
docs/man.md
16
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 <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 <path>` 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 <theme-file>`
|
||||
|
||||
#### Install latest version
|
||||
|
||||
Run termscp as `termscp update`
|
||||
|
||||
---
|
||||
|
||||
## S3 connection parameters
|
||||
|
||||
@@ -43,9 +43,7 @@ termscp启动时可以使用以下选项:
|
||||
|
||||
- `-P, --password <password>` 登陆密码
|
||||
- `-b, --address-as-bookmark` 将地址参数解析为书签名称
|
||||
- `-c, --config` 打开termscp时打开配置页面
|
||||
- `-q, --quiet` 禁用日志
|
||||
- `-t, --theme <path>` 导入自定义主题
|
||||
- `-v, --version` 打印版本信息
|
||||
- `-h, --help` 打开帮助
|
||||
|
||||
|
||||
@@ -34,36 +34,25 @@ Address syntax can be:
|
||||
Please, report issues to <https://github.com/veeso/termscp>
|
||||
Please, consider supporting the author <https://ko-fi.com/veeso>")]
|
||||
pub struct Args {
|
||||
#[argh(
|
||||
switch,
|
||||
short = 'b',
|
||||
description = "resolve address argument as a bookmark name"
|
||||
)]
|
||||
#[argh(subcommand)]
|
||||
pub nested: Option<ArgsSubcommands>,
|
||||
/// 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<String>,
|
||||
#[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<String>,
|
||||
#[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<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 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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]
|
||||
|
||||
93
src/main.rs
93
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<RunOpts, String> {
|
||||
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<Remote, String> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user