mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Merge branch '0.10.0' into main
This commit is contained in:
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: sudo apt update && sudo apt install -y libdbus-1-dev libssh2-1-dev libssl-dev
|
||||
run: sudo apt update && sudo apt install -y libdbus-1-dev
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
- [Changelog](#changelog)
|
||||
- [0.10.0](#0100)
|
||||
- [0.9.0](#090)
|
||||
- [0.8.2](#082)
|
||||
- [0.8.1](#081)
|
||||
@@ -25,6 +26,40 @@
|
||||
|
||||
---
|
||||
|
||||
## 0.10.0
|
||||
|
||||
Released on 15/10/2022
|
||||
|
||||
> ⭐ 500 stars update ⭐
|
||||
> Thank you for supporting termscp and make it reaching 500 stars on Github
|
||||
|
||||
- **Changed keybindings for BACKTAB**: backtab will now change the explorer tab
|
||||
- To active the LOG PANEL, use `P`
|
||||
- **Yes/No dialogs** are now answerable by pressing `Y` or `N` on your keyboard ([Issue 121](https://github.com/veeso/termscp/issues/121))
|
||||
- **Use ssh2 config IdentityFile** as fallback for key based authentication
|
||||
- **Bugfix**
|
||||
- Fixed [Issue 122](https://github.com/veeso/termscp/issues/122)
|
||||
- Fixed version comparison when going above 0.9
|
||||
- Dependencies:
|
||||
- Bump `argh` to `0.1.9`
|
||||
- Bump `chrono` to `0.4.22`
|
||||
- Bump `keyring` to `1.2.0`
|
||||
- Bump `notify-rust` to `4.5.10`
|
||||
- Bump `open` to `3.0.3`
|
||||
- Bump `rpassword` to `7.0.0`
|
||||
- Changed `regex` to `lazy-regex 2.3.0`
|
||||
- Bump `remotefs-ftp` to `0.1.2`
|
||||
- Bump `remotefs-ssh` to `0.1.2`
|
||||
- Bump `self_update` to `0.32`
|
||||
- Bump `ssh2-config` to `0.1.3`
|
||||
- Bump `tuirealm` to `1.8.0`
|
||||
- Bump `tui-realm-stdlib` to `1.1.7`
|
||||
- Bump `unicode-width` to `0.1.10`
|
||||
- Added `version-compare 0.1.0`
|
||||
- Bump `whoami` to `1.2.3`
|
||||
- Bump `wildmatch` to `2.1.1`
|
||||
- Removed libssl dependency
|
||||
|
||||
## 0.9.0
|
||||
|
||||
Released on 18/06/2022
|
||||
|
||||
1067
Cargo.lock
generated
1067
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
52
Cargo.toml
52
Cargo.toml
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
authors = ["Christian Visintin"]
|
||||
authors = ["Christian Visintin <christian.visintin1997@gmail.com>"]
|
||||
categories = ["command-line-utilities"]
|
||||
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
|
||||
edition = "2021"
|
||||
@@ -10,7 +10,7 @@ license = "MIT"
|
||||
name = "termscp"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/veeso/termscp"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
|
||||
[package.metadata.rpm]
|
||||
package = "termscp"
|
||||
@@ -31,51 +31,59 @@ name = "termscp"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
argh = "0.1.7"
|
||||
argh = "0.1.9"
|
||||
bitflags = "1.3.2"
|
||||
bytesize = "1.1.0"
|
||||
chrono = "0.4.19"
|
||||
chrono = "0.4.22"
|
||||
content_inspector = "0.2.4"
|
||||
dirs = "4.0.0"
|
||||
edit = "0.1.4"
|
||||
hostname = "0.3.1"
|
||||
keyring = { version = "1.1.2", optional = true }
|
||||
keyring = { version = "1.2.0", optional = true }
|
||||
lazy-regex = "^2.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.17"
|
||||
magic-crypt = "3.1.10"
|
||||
notify = "4.0.17"
|
||||
notify-rust = { version = "4.5.6", default-features = false, features = [ "d" ] }
|
||||
open = "2.1.3"
|
||||
notify-rust = { version = "4.5.10", default-features = false, features = [ "d" ] }
|
||||
open = "3.0.3"
|
||||
rand = "0.8.5"
|
||||
regex = "1.5.6"
|
||||
remotefs = "^0.2.0"
|
||||
remotefs-aws-s3 = "^0.2.0"
|
||||
remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] }
|
||||
remotefs-ssh = { version = "^0.1.1", features = [ "ssh2-vendored" ] }
|
||||
rpassword = "6.0.1"
|
||||
self_update = { version = "0.30.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
|
||||
serde = { version = "^1.0.0", features = [ "derive" ] }
|
||||
remotefs-aws-s3 = { version = "^0.2.1", default-features = false, features = [ "find", "rustls" ] }
|
||||
|
||||
rpassword = "7.0.0"
|
||||
self_update = { version = "0.32.0", default-features = false, features = [ "rustls", "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
|
||||
serde = { version = "^1", features = [ "derive" ] }
|
||||
simplelog = "0.12.0"
|
||||
ssh2-config = "^0.1.3"
|
||||
tempfile = "3.2.0"
|
||||
thiserror = "^1.0.0"
|
||||
thiserror = "^1"
|
||||
toml = "0.5.0"
|
||||
tui-realm-stdlib = "1.1.6"
|
||||
tuirealm = "1.6.0"
|
||||
unicode-width = "0.1.8"
|
||||
whoami = "1.2.1"
|
||||
wildmatch = "2.1.0"
|
||||
tui-realm-stdlib = "1.1.7"
|
||||
tuirealm = "1.8.0"
|
||||
unicode-width = "0.1.10"
|
||||
version-compare = "0.1.0"
|
||||
whoami = "1.2.3"
|
||||
wildmatch = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.2.1"
|
||||
serial_test = "^0.7.0"
|
||||
pretty_assertions = "^1.3"
|
||||
serial_test = "^0.9"
|
||||
|
||||
[features]
|
||||
default = [ "with-keyring" ]
|
||||
github-actions = [ ]
|
||||
with-keyring = [ "keyring" ]
|
||||
|
||||
[target."cfg(target_family = \"windows\")"]
|
||||
[target."cfg(target_family = \"windows\")".dependencies]
|
||||
remotefs-ftp = { version = "^0.1.2", features = [ "native-tls" ] }
|
||||
remotefs-ssh = "^0.1.2"
|
||||
|
||||
[target."cfg(target_family = \"unix\")"]
|
||||
[target."cfg(target_family = \"unix\")".dependencies]
|
||||
remotefs-ftp = { version = "^0.1.2", features = [ "vendored", "native-tls" ] }
|
||||
remotefs-ssh = { version = "^0.1.2", features = [ "ssh2-vendored" ] }
|
||||
users = "0.11.0"
|
||||
|
||||
[profile.dev]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Christian Visintin
|
||||
Copyright (c) 2020-2022 Christian Visintin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||
<p align="center">Current version: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">Current version: 0.10.0 (15/10/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
@@ -224,7 +224,7 @@ The user manual can be found on the [termscp's website](https://veeso.github.io/
|
||||
|
||||
## Upcoming Features 🧪
|
||||
|
||||
For **2022** there will be two major updates during the year.
|
||||
For **2023** there will be two major updates during the year.
|
||||
|
||||
Planned for *future updates ⏲️*:
|
||||
|
||||
@@ -233,9 +233,8 @@ Planned for *future updates ⏲️*:
|
||||
- 🇫🇷 French
|
||||
- 🇩🇪 German
|
||||
- 🇮🇹 Italian
|
||||
- 🇳🇱 Dutch
|
||||
- 🇪🇸 Spanish
|
||||
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. (Maybe will be postponed to spring 2022).
|
||||
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration.
|
||||
|
||||
Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue.
|
||||
|
||||
|
||||
4
dist/build/windows.ps1
vendored
4
dist/build/windows.ps1
vendored
@@ -14,7 +14,7 @@ cargo build --release
|
||||
# Make zip
|
||||
$zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip"
|
||||
Set-Location .\target\release\
|
||||
Compress-Archive termscp.exe $zipName
|
||||
Compress-Archive -Force termscp.exe $zipName
|
||||
# Get checksum
|
||||
checksum.exe -t sha256 $zipName
|
||||
Get-FileHash $zipName
|
||||
Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName
|
||||
|
||||
2
dist/build/x86_64/Dockerfile
vendored
2
dist/build/x86_64/Dockerfile
vendored
@@ -8,8 +8,6 @@ RUN apt update && apt install -y \
|
||||
git \
|
||||
gcc \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libssh2-1-dev \
|
||||
libdbus-1-dev \
|
||||
curl
|
||||
# Clone repository
|
||||
|
||||
13
dist/build/x86_64_centos7/Dockerfile
vendored
13
dist/build/x86_64_centos7/Dockerfile
vendored
@@ -7,11 +7,12 @@ WORKDIR /usr/src/
|
||||
RUN yum -y install \
|
||||
git \
|
||||
gcc \
|
||||
openssl \
|
||||
pkgconfig \
|
||||
gcc \
|
||||
make \
|
||||
dbus-devel \
|
||||
openssl-devel \
|
||||
bash
|
||||
bash \
|
||||
rpm-build
|
||||
# Install rust
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \
|
||||
chmod +x /tmp/rust.sh && \
|
||||
@@ -20,10 +21,10 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh &&
|
||||
RUN git clone --branch $branch https://github.com/veeso/termscp.git
|
||||
# Set workdir to termscp
|
||||
WORKDIR /usr/src/termscp/
|
||||
# Install cargo arxch
|
||||
RUN source $HOME/.cargo/env && cargo install cargo-rpm
|
||||
# Build for x86_64
|
||||
RUN source $HOME/.cargo/env && cargo build --release
|
||||
# Install cargo rpm
|
||||
RUN source $HOME/.cargo/env && cargo install cargo-rpm
|
||||
# Build pkgs
|
||||
RUN source $HOME/.cargo/env && yum -y install rpm-build && cargo rpm init && cargo rpm build
|
||||
RUN source $HOME/.cargo/env && cargo rpm init && cargo rpm build
|
||||
CMD ["sh"]
|
||||
|
||||
2
dist/build/x86_64_debian8/Dockerfile
vendored
2
dist/build/x86_64_debian8/Dockerfile
vendored
@@ -6,8 +6,6 @@ RUN apt update && apt install -y \
|
||||
git \
|
||||
gcc \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libssh2-1-dev \
|
||||
libdbus-1-dev \
|
||||
curl
|
||||
|
||||
|
||||
3
dist/build/x86_64_debian9/Dockerfile
vendored
3
dist/build/x86_64_debian9/Dockerfile
vendored
@@ -8,9 +8,8 @@ RUN apt update && apt install -y \
|
||||
git \
|
||||
gcc \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libssh2-1-dev \
|
||||
libdbus-1-dev \
|
||||
build-essential \
|
||||
bash \
|
||||
curl
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||
<p align="center">Aktuelle Version: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">Aktuelle Version: 0.10.0 (09/06/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -204,6 +204,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
||||
| `<M>` | Select a file | Mark |
|
||||
| `<N>` | Create new file with provided name | New |
|
||||
| `<O|F4>` | Edit file; see Text editor | Open |
|
||||
| `<P>` | Open log panel | Panel |
|
||||
| `<Q|F10>` | Quit termscp | Quit |
|
||||
| `<R|F6>` | Rename file | Rename |
|
||||
| `<S|F2>` | Save file as... | Save |
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||
<p align="center">Versión actual: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">Versión actual: 0.10.0 (09/06/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -204,6 +204,7 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
|
||||
| `<M>` | Seleccione un archivo | Mark |
|
||||
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
|
||||
| `<O|F4>` | Editar archivo | Open |
|
||||
| `<P>` | Open log panel | Panel |
|
||||
| `<Q|F10>` | Salir de termscp | Quit |
|
||||
| `<R|F6>` | Renombrar archivo | Rename |
|
||||
| `<S|F2>` | Guardar archivo como... | Save |
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||
<p align="center">Version actuelle: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">Version actuelle: 0.10.0 (09/06/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -202,6 +202,7 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
|
||||
| `<M>` | Sélectionner un fichier | Mark |
|
||||
| `<N>` | Créer un nouveau fichier avec le nom fourni | New |
|
||||
| `<O|F4>` | Modifier le fichier | Open |
|
||||
| `<P>` | Ouvre le panel de journals | Panel |
|
||||
| `<Q|F10>` | Quitter termscp | Quit |
|
||||
| `<R|F6>` | Renommer le fichier | Rename |
|
||||
| `<S|F2>` | Enregistrer le fichier sous... | Save |
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||
<p align="center">Versione corrente: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">Versione corrente: 0.10.0 (09/06/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -198,6 +198,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
|
||||
| `<M>` | Seleziona file | Mark |
|
||||
| `<N>` | Crea nuovo file con il nome fornito | New |
|
||||
| `<O|F4>` | Modifica file; Vedi text editor | Open |
|
||||
| `<P>` | Apri pannello log | Panel |
|
||||
| `<Q|F10>` | Termina termscp | Quit |
|
||||
| `<R|F6>` | Rinomina file | Rename |
|
||||
| `<S|F2>` | Salva file con nome | Save |
|
||||
|
||||
@@ -202,6 +202,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
||||
| `<M>` | Select a file | Mark |
|
||||
| `<N>` | Create new file with provided name | New |
|
||||
| `<O|F4>` | Edit file; see Text editor | Open |
|
||||
| `<P>` | Open log panel | Panel |
|
||||
| `<Q|F10>` | Quit termscp | Quit |
|
||||
| `<R|F6>` | Rename file | Rename |
|
||||
| `<S|F2>` | Save file as... | Save |
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">由 <a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</p>
|
||||
<p align="center">当前版本: 0.9.0 (18/06/2022)</p>
|
||||
<p align="center">当前版本: 0.10.0 (09/06/2022)</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -199,6 +199,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
|
||||
| `<M>` | 选中文件 | Mark |
|
||||
| `<N>` | 使用键入的名称新建文件 | New |
|
||||
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
|
||||
| `<P>` | 打开日志面板 | Panel |
|
||||
| `<Q|F10>` | 退出termscp | Quit |
|
||||
| `<R|F7>` | 重命名文件 | Rename |
|
||||
| `<S|F2>` | 另存为... | Save |
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# -f, -y, --force, --yes
|
||||
# Skip the confirmation prompt during installation
|
||||
|
||||
TERMSCP_VERSION="0.9.0"
|
||||
TERMSCP_VERSION="0.10.0"
|
||||
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
||||
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
||||
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"
|
||||
@@ -296,7 +296,7 @@ install_bsd_cargo_deps() {
|
||||
}
|
||||
|
||||
install_linux_cargo_deps() {
|
||||
local debian_deps="gcc pkg-config libssl-dev libssh2-1-dev libdbus-1-dev"
|
||||
local debian_deps="gcc pkg-config libdbus-1-dev"
|
||||
local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel"
|
||||
local arch_deps="gcc openssl pkg-config dbus"
|
||||
local deps_cmd=""
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct UserHosts {
|
||||
}
|
||||
|
||||
/// Bookmark describes a single bookmark entry in the user hosts storage
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||
pub struct Bookmark {
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_protocol",
|
||||
@@ -41,7 +41,7 @@ pub struct Bookmark {
|
||||
}
|
||||
|
||||
/// Connection parameters for Aws s3 protocol
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)]
|
||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
|
||||
pub struct S3Params {
|
||||
pub bucket: String,
|
||||
pub region: Option<String>,
|
||||
|
||||
@@ -106,7 +106,7 @@ mod tests {
|
||||
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
||||
let cfg: UserConfig = UserConfig {
|
||||
user_interface: ui,
|
||||
remote: remote,
|
||||
remote,
|
||||
};
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
|
||||
@@ -12,7 +12,7 @@ use tuirealm::tui::style::Color;
|
||||
/// ### Theme
|
||||
///
|
||||
/// Theme contains all the colors lookup table for termscp
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct Theme {
|
||||
// -- auth
|
||||
#[serde(
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::utils::path::diff_paths;
|
||||
use crate::utils::string::secure_substring;
|
||||
// Ext
|
||||
use bytesize::ByteSize;
|
||||
use regex::Regex;
|
||||
use lazy_regex::{Lazy, Regex};
|
||||
use remotefs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::time::UNIX_EPOCH;
|
||||
@@ -32,17 +32,14 @@ const FMT_KEY_SYMLINK: &str = "SYMLINK";
|
||||
const FMT_KEY_USER: &str = "USER";
|
||||
// Default
|
||||
const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}";
|
||||
// Regex
|
||||
lazy_static! {
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 0: KEY NAME
|
||||
* - group 1?: LENGTH
|
||||
* - group 2?: EXTRA
|
||||
*/
|
||||
static ref FMT_KEY_REGEX: Regex = Regex::new(r"\{(.*?)\}").ok().unwrap();
|
||||
static ref FMT_ATTR_REGEX: Regex = Regex::new(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?").ok().unwrap();
|
||||
}
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 0: KEY NAME
|
||||
* - group 1?: LENGTH
|
||||
* - group 2?: EXTRA
|
||||
*/
|
||||
static FMT_KEY_REGEX: Lazy<Regex> = lazy_regex!(r"\{(.*?)\}");
|
||||
static FMT_ATTR_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?");
|
||||
|
||||
/// Call Chain block is a block in a chain of functions which are called in order to format the File.
|
||||
/// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found
|
||||
|
||||
@@ -25,7 +25,7 @@ bitflags! {
|
||||
}
|
||||
|
||||
/// FileSorting defines the criteria for sorting files
|
||||
#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, std::fmt::Debug)]
|
||||
pub enum FileSorting {
|
||||
Name,
|
||||
ModifyTime,
|
||||
@@ -34,7 +34,7 @@ pub enum FileSorting {
|
||||
}
|
||||
|
||||
/// GroupDirs defines how directories should be grouped in sorting files
|
||||
#[derive(PartialEq, std::fmt::Debug)]
|
||||
#[derive(PartialEq, Eq, std::fmt::Debug)]
|
||||
pub enum GroupDirs {
|
||||
First,
|
||||
Last,
|
||||
@@ -318,8 +318,8 @@ mod tests {
|
||||
explorer.stack_size = 2;
|
||||
explorer.dirstack = VecDeque::with_capacity(2);
|
||||
// Push dir
|
||||
explorer.pushd(&Path::new("/tmp"));
|
||||
explorer.pushd(&Path::new("/home/omar"));
|
||||
explorer.pushd(Path::new("/tmp"));
|
||||
explorer.pushd(Path::new("/home/omar"));
|
||||
// Pop
|
||||
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
|
||||
assert_eq!(explorer.dirstack.len(), 1);
|
||||
@@ -328,9 +328,9 @@ mod tests {
|
||||
// Dirstack is empty now
|
||||
assert!(explorer.popd().is_none());
|
||||
// Exceed limit
|
||||
explorer.pushd(&Path::new("/tmp"));
|
||||
explorer.pushd(&Path::new("/home/omar"));
|
||||
explorer.pushd(&Path::new("/dev"));
|
||||
explorer.pushd(Path::new("/tmp"));
|
||||
explorer.pushd(Path::new("/home/omar"));
|
||||
explorer.pushd(Path::new("/dev"));
|
||||
assert_eq!(explorer.dirstack.len(), 2);
|
||||
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
|
||||
assert_eq!(
|
||||
|
||||
@@ -11,7 +11,7 @@ pub use params::{FileTransferParams, ProtocolParams};
|
||||
|
||||
/// This enum defines the different transfer protocol available in termscp
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum FileTransferProtocol {
|
||||
Sftp,
|
||||
Scp,
|
||||
|
||||
@@ -654,7 +654,7 @@ mod tests {
|
||||
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
||||
let mut counter: usize = 0;
|
||||
for _ in entries {
|
||||
counter = counter + 1;
|
||||
counter += 1;
|
||||
}
|
||||
assert_eq!(host.files.len(), counter);
|
||||
}
|
||||
@@ -696,7 +696,7 @@ mod tests {
|
||||
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
||||
let mut counter: usize = 0;
|
||||
for _ in entries {
|
||||
counter = counter + 1;
|
||||
counter += 1;
|
||||
}
|
||||
assert_eq!(host.list_dir().len(), counter);
|
||||
}
|
||||
@@ -709,10 +709,10 @@ mod tests {
|
||||
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
|
||||
// Verify new files
|
||||
// Scan dir
|
||||
let entries = std::fs::read_dir(PathBuf::from(new_dir).as_path()).unwrap();
|
||||
let entries = std::fs::read_dir(new_dir.as_path()).unwrap();
|
||||
let mut counter: usize = 0;
|
||||
for _ in entries {
|
||||
counter = counter + 1;
|
||||
counter += 1;
|
||||
}
|
||||
assert_eq!(host.files.len(), counter);
|
||||
}
|
||||
@@ -793,7 +793,7 @@ mod tests {
|
||||
let files: Vec<File> = host.list_dir();
|
||||
// Verify files
|
||||
let file_0: &File = files.get(0).unwrap();
|
||||
if file_0.name() == String::from("foo.txt") {
|
||||
if file_0.name() == *"foo.txt" {
|
||||
assert!(file_0.metadata.symlink.is_none());
|
||||
} else {
|
||||
assert_eq!(
|
||||
@@ -803,7 +803,7 @@ mod tests {
|
||||
}
|
||||
// Verify simlink
|
||||
let file_1: &File = files.get(1).unwrap();
|
||||
if file_1.name() == String::from("bar.txt") {
|
||||
if file_1.name() == *"bar.txt" {
|
||||
assert_eq!(
|
||||
file_1.metadata.symlink.as_ref().unwrap(),
|
||||
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
||||
|
||||
@@ -5,6 +5,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate lazy_regex;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
@@ -96,7 +96,7 @@ impl Update {
|
||||
new_version,
|
||||
cargo_crate_version!()
|
||||
);
|
||||
if new_version.as_str() > cargo_crate_version!() {
|
||||
if Self::is_new_version_higher(new_version.as_str(), cargo_crate_version!()) {
|
||||
Some(r) // New version is available
|
||||
} else {
|
||||
None // No new version
|
||||
@@ -105,6 +105,12 @@ impl Update {
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check wether new version is higher than new version
|
||||
fn is_new_version_higher(new_version: &str, current_version: &str) -> bool {
|
||||
version_compare::compare(new_version, current_version).unwrap_or(version_compare::Cmp::Lt)
|
||||
== version_compare::Cmp::Gt
|
||||
}
|
||||
}
|
||||
impl From<Status> for UpdateStatus {
|
||||
fn from(s: Status) -> Self {
|
||||
@@ -193,4 +199,13 @@ mod test {
|
||||
assert_eq!(release.body.as_str(), "fixed everything");
|
||||
assert_eq!(release.version.as_str(), "0.7.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_tell_that_version_is_higher() {
|
||||
assert!(Update::is_new_version_higher("0.10.0", "0.9.0"));
|
||||
assert!(Update::is_new_version_higher("0.20.0", "0.19.0"));
|
||||
assert!(!Update::is_new_version_higher("0.9.0", "0.10.0"));
|
||||
assert!(!Update::is_new_version_higher("0.9.9", "0.10.1"));
|
||||
assert!(!Update::is_new_version_higher("0.10.9", "0.11.0"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ mod tests {
|
||||
// Verify client has updated parameters
|
||||
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
|
||||
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
|
||||
let mut expected_key_path: PathBuf = key_path.clone();
|
||||
let mut expected_key_path: PathBuf = key_path;
|
||||
expected_key_path.push("pi@192.168.1.31.key");
|
||||
assert_eq!(
|
||||
client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),
|
||||
|
||||
@@ -126,7 +126,7 @@ mod tests {
|
||||
#[serial]
|
||||
fn test_system_environment_get_bookmarks_paths() {
|
||||
assert_eq!(
|
||||
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
|
||||
get_bookmarks_paths(Path::new("/home/omar/.config/termscp/")),
|
||||
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
|
||||
);
|
||||
}
|
||||
@@ -135,7 +135,7 @@ mod tests {
|
||||
#[serial]
|
||||
fn test_system_environment_get_config_paths() {
|
||||
assert_eq!(
|
||||
get_config_paths(&Path::new("/home/omar/.config/termscp/")),
|
||||
get_config_paths(Path::new("/home/omar/.config/termscp/")),
|
||||
(
|
||||
PathBuf::from("/home/omar/.config/termscp/config.toml"),
|
||||
PathBuf::from("/home/omar/.config/termscp/.ssh/")
|
||||
@@ -147,7 +147,7 @@ mod tests {
|
||||
#[serial]
|
||||
fn test_system_environment_get_log_paths() {
|
||||
assert_eq!(
|
||||
get_log_paths(&Path::new("/home/omar/.config/termscp/")),
|
||||
get_log_paths(Path::new("/home/omar/.config/termscp/")),
|
||||
PathBuf::from("/home/omar/.config/termscp/termscp.log"),
|
||||
);
|
||||
}
|
||||
@@ -156,7 +156,7 @@ mod tests {
|
||||
#[serial]
|
||||
fn test_system_environment_get_theme_path() {
|
||||
assert_eq!(
|
||||
get_theme_path(&Path::new("/home/omar/.config/termscp/")),
|
||||
get_theme_path(Path::new("/home/omar/.config/termscp/")),
|
||||
PathBuf::from("/home/omar/.config/termscp/theme.toml"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_system_keys_filestorage_make_dir() {
|
||||
let storage: FileStorage = FileStorage::new(&Path::new("/tmp/"));
|
||||
let storage: FileStorage = FileStorage::new(Path::new("/tmp/"));
|
||||
assert_eq!(
|
||||
storage.make_file_path("bookmarks").as_path(),
|
||||
Path::new("/tmp/.bookmarks.key")
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod keyringstorage;
|
||||
use thiserror::Error;
|
||||
|
||||
/// defines the error type for the `KeyStorage`
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum KeyStorageError {
|
||||
#[cfg(feature = "with-keyring")]
|
||||
#[error("Key has a bad syntax")]
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
// Locals
|
||||
use super::config_client::ConfigClient;
|
||||
// Ext
|
||||
use remotefs_ssh::SshKeyStorage as SshKeyStorageT;
|
||||
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
|
||||
use ssh2_config::SshConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct SshKeyStorage {
|
||||
hosts: HashMap<String, PathBuf>, // Association between {user}@{host} and RSA key path
|
||||
/// Association between {user}@{host} and RSA key path
|
||||
hosts: HashMap<String, PathBuf>,
|
||||
/// Ssh2 configuration
|
||||
ssh_config: Option<SshConfig>,
|
||||
}
|
||||
|
||||
impl SshKeyStorage {
|
||||
@@ -19,6 +23,7 @@ impl SshKeyStorage {
|
||||
pub fn empty() -> Self {
|
||||
SshKeyStorage {
|
||||
hosts: HashMap::new(),
|
||||
ssh_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,21 +39,67 @@ impl SshKeyStorage {
|
||||
let key: String = Self::make_mapkey(host, username);
|
||||
self.hosts.insert(key, p);
|
||||
}
|
||||
}
|
||||
|
||||
impl SshKeyStorageT for SshKeyStorage {
|
||||
fn resolve(&self, host: &str, username: &str) -> Option<&Path> {
|
||||
/// Parse ssh2 config
|
||||
fn parse_ssh2_config(path: &str) -> Result<SshConfig, String> {
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
let mut reader = File::open(path)
|
||||
.map_err(|e| format!("failed to open {}: {}", path, e))
|
||||
.map(BufReader::new)?;
|
||||
SshConfig::default()
|
||||
.parse(&mut reader)
|
||||
.map_err(|e| format!("Failed to parse ssh2 config: {}", e))
|
||||
}
|
||||
|
||||
/// Resolve host via termscp ssh keys storage
|
||||
fn resolve_host_in_termscp_storage(&self, host: &str, username: &str) -> Option<&Path> {
|
||||
let key: String = Self::make_mapkey(host, username);
|
||||
self.hosts.get(&key).map(|x| x.as_path())
|
||||
}
|
||||
|
||||
/// Resolve host via ssh2 configuration
|
||||
fn resolve_host_in_ssh2_configuration(&self, host: &str) -> Option<PathBuf> {
|
||||
if let Some(config) = self.ssh_config.as_ref() {
|
||||
let params = config.query(host);
|
||||
params
|
||||
.identity_file
|
||||
.as_ref()
|
||||
.and_then(|x| x.get(0).cloned())
|
||||
} else {
|
||||
debug!("ssh2 config is not available; no key has been found");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SshKeyStorageTrait for SshKeyStorage {
|
||||
fn resolve(&self, host: &str, username: &str) -> Option<PathBuf> {
|
||||
// search in termscp keys
|
||||
if let Some(path) = self.resolve_host_in_termscp_storage(host, username) {
|
||||
return Some(path.to_path_buf());
|
||||
}
|
||||
debug!(
|
||||
"couldn't find any ssh key associated to {} at {}. Trying with ssh2 config",
|
||||
username, host
|
||||
);
|
||||
// otherwise search in configuration
|
||||
self.resolve_host_in_ssh2_configuration(host)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ConfigClient> for SshKeyStorage {
|
||||
fn from(cfg_client: &ConfigClient) -> Self {
|
||||
// read ssh2 config
|
||||
let ssh_config = cfg_client.get_ssh_config().and_then(|x| {
|
||||
debug!("reading ssh config at {}", x);
|
||||
Self::parse_ssh2_config(x).ok()
|
||||
});
|
||||
let mut hosts: HashMap<String, PathBuf> =
|
||||
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
|
||||
debug!("Setting up SSH key storage");
|
||||
// Iterate over keys
|
||||
// Iterate over keys in storage
|
||||
for key in cfg_client.iter_ssh_keys() {
|
||||
match cfg_client.get_ssh_key(key) {
|
||||
Ok(host) => match host {
|
||||
@@ -66,7 +117,7 @@ impl From<&ConfigClient> for SshKeyStorage {
|
||||
info!("Got SSH key for {}", key);
|
||||
}
|
||||
// Return storage
|
||||
SshKeyStorage { hosts }
|
||||
SshKeyStorage { hosts, ssh_config }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +126,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::system::config_client::ConfigClient;
|
||||
use crate::utils::test_helpers;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::Path;
|
||||
@@ -93,7 +145,7 @@ mod tests {
|
||||
// Create ssh key storage
|
||||
let storage: SshKeyStorage = SshKeyStorage::from(&client);
|
||||
// Verify key exists
|
||||
let mut exp_key_path: PathBuf = key_path.clone();
|
||||
let mut exp_key_path: PathBuf = key_path;
|
||||
exp_key_path.push("pi@192.168.1.31.key");
|
||||
assert_eq!(
|
||||
*storage.resolve("192.168.1.31", "pi").unwrap(),
|
||||
@@ -103,6 +155,35 @@ mod tests {
|
||||
assert!(storage.resolve("deskichup", "veeso").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sould_resolve_key_from_ssh2_config() {
|
||||
let rsa_key = test_helpers::create_sample_file_with_content("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a");
|
||||
let ssh_config_file = test_helpers::create_sample_file_with_content(format!(
|
||||
r#"
|
||||
Host test
|
||||
HostName 127.0.0.1
|
||||
Port 2222
|
||||
User test
|
||||
IdentityFile {}
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
"#,
|
||||
rsa_key.path().display()
|
||||
));
|
||||
// make storage
|
||||
let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
|
||||
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||
.ok()
|
||||
.unwrap();
|
||||
client.set_ssh_config(Some(ssh_config_file.path().to_string_lossy().to_string()));
|
||||
let storage: SshKeyStorage = SshKeyStorage::from(&client);
|
||||
assert_eq!(
|
||||
storage.resolve("test", "pi").unwrap().as_path(),
|
||||
rsa_key.path()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_sshkey_storage_empty() {
|
||||
let storage: SshKeyStorage = SshKeyStorage::empty();
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
|
||||
|
||||
use tui_realm_stdlib::{Input, List, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
@@ -223,6 +223,14 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Form(FormMsg::DeleteBookmark)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseDeleteBookmark)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -283,6 +291,14 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Form(FormMsg::DeleteRecent)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseDeleteRecent)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
|
||||
|
||||
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
@@ -186,6 +186,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Form(FormMsg::Quit)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -245,6 +253,14 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Form(FormMsg::InstallUpdate)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseInstallUpdatePopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
|
||||
@@ -60,14 +60,14 @@ pub enum Id {
|
||||
WindowSizeError,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum Msg {
|
||||
Form(FormMsg),
|
||||
Ui(UiMsg),
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum FormMsg {
|
||||
Connect,
|
||||
DeleteBookmark,
|
||||
@@ -81,7 +81,7 @@ pub enum FormMsg {
|
||||
SaveBookmark,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum UiMsg {
|
||||
AddressBlurDown,
|
||||
AddressBlurUp,
|
||||
|
||||
@@ -148,9 +148,7 @@ impl FileTransferActivity {
|
||||
}
|
||||
};
|
||||
// Edit file
|
||||
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
|
||||
return Err(err);
|
||||
}
|
||||
self.edit_local_file(tmpfile.as_path())?;
|
||||
// Get local fs entry
|
||||
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
|
||||
Ok(e) => e,
|
||||
|
||||
@@ -192,7 +192,8 @@ impl Component<Msg, NoUserEvent> for Log {
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab, ..
|
||||
code: Key::BackTab | Key::Tab | Key::Char('p'),
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -147,6 +147,14 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseDeletePopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Transfer(TransferMsg::DeleteFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseDeletePopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -203,6 +211,14 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseDisconnectPopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::Disconnect)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseDisconnectPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -1195,6 +1211,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseQuitPopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::Quit)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -1344,6 +1368,14 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::PendingAction(PendingActionMsg::TransferPendingFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -1790,6 +1822,16 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
|
||||
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
|
||||
)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::PendingAction(
|
||||
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
|
||||
)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -1967,6 +2009,14 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseWatcherPopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Transfer(TransferMsg::ToggleWatch)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::CloseWatcherPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
|
||||
@@ -82,14 +82,11 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
|
||||
Some(Msg::None)
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab, ..
|
||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseFindExplorer))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left | Key::Right | Key::Tab,
|
||||
code: Key::Left | Key::Right | Key::Tab | Key::BackTab,
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -206,14 +203,11 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
Some(Msg::None)
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab, ..
|
||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Right | Key::Tab,
|
||||
code: Key::Right | Key::Tab | Key::BackTab,
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -275,6 +269,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
code: Key::Char('o') | Key::Function(4),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
@@ -386,14 +384,11 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
Some(Msg::None)
|
||||
}
|
||||
// -- comp msg
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::BackTab, ..
|
||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left | Key::Tab,
|
||||
code: Key::Left | Key::Tab | Key::BackTab,
|
||||
..
|
||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -455,6 +450,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
code: Key::Char('o') | Key::Function(4),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('p'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('r') | Key::Function(6),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
|
||||
@@ -208,7 +208,7 @@ impl FileTransferActivity {
|
||||
Ok(h) => {
|
||||
let hostname: String = h.as_os_str().to_string_lossy().to_string();
|
||||
let tokens: Vec<&str> = hostname.split('.').collect();
|
||||
String::from(*tokens.get(0).unwrap_or(&"localhost"))
|
||||
String::from(*tokens.first().unwrap_or(&"localhost"))
|
||||
}
|
||||
Err(_) => String::from("localhost"),
|
||||
};
|
||||
|
||||
@@ -140,7 +140,6 @@ enum UiMsg {
|
||||
CloseWatchedPathsList,
|
||||
CloseWatcherPopup,
|
||||
Disconnect,
|
||||
ExplorerBackTabbed,
|
||||
LogBackTabbed,
|
||||
Quit,
|
||||
ReplacePopupTabbed,
|
||||
@@ -153,6 +152,7 @@ enum UiMsg {
|
||||
ShowFindPopup,
|
||||
ShowGotoPopup,
|
||||
ShowKeybindingsPopup,
|
||||
ShowLogPanel,
|
||||
ShowMkdirPopup,
|
||||
ShowNewFilePopup,
|
||||
ShowOpenWithPopup,
|
||||
|
||||
@@ -326,11 +326,7 @@ impl FileTransferActivity {
|
||||
break;
|
||||
}
|
||||
// Send entry; name is always None after first call
|
||||
if let Err(err) =
|
||||
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)
|
||||
{
|
||||
return Err(err);
|
||||
}
|
||||
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -717,13 +713,11 @@ impl FileTransferActivity {
|
||||
}
|
||||
// Receive entry; name is always None after first call
|
||||
// Local path becomes local_dir_path
|
||||
if let Err(err) = self.filetransfer_recv_recurse(
|
||||
self.filetransfer_recv_recurse(
|
||||
entry,
|
||||
local_dir_path.as_path(),
|
||||
None,
|
||||
) {
|
||||
return Err(err);
|
||||
}
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ impl FileTransferActivity {
|
||||
self.action_submit_local(entry);
|
||||
// Update file list if sync
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_remote_filelist();
|
||||
self.update_remote_filelist();
|
||||
}
|
||||
self.update_local_filelist();
|
||||
}
|
||||
@@ -112,7 +112,7 @@ impl FileTransferActivity {
|
||||
self.action_submit_remote(entry);
|
||||
// Update file list if sync
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_local_filelist();
|
||||
self.update_local_filelist();
|
||||
}
|
||||
self.update_remote_filelist();
|
||||
}
|
||||
@@ -161,7 +161,7 @@ impl FileTransferActivity {
|
||||
FileExplorerTab::Local => {
|
||||
self.action_go_to_local_upper_dir();
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_remote_filelist();
|
||||
self.update_remote_filelist();
|
||||
}
|
||||
// Reload file list component
|
||||
self.update_local_filelist()
|
||||
@@ -169,7 +169,7 @@ impl FileTransferActivity {
|
||||
FileExplorerTab::Remote => {
|
||||
self.action_go_to_remote_upper_dir();
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_local_filelist();
|
||||
self.update_local_filelist();
|
||||
}
|
||||
// Reload file list component
|
||||
self.update_remote_filelist()
|
||||
@@ -182,7 +182,7 @@ impl FileTransferActivity {
|
||||
FileExplorerTab::Local => {
|
||||
self.action_go_to_previous_local_dir();
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_remote_filelist();
|
||||
self.update_remote_filelist();
|
||||
}
|
||||
// Reload file list component
|
||||
self.update_local_filelist()
|
||||
@@ -190,7 +190,7 @@ impl FileTransferActivity {
|
||||
FileExplorerTab::Remote => {
|
||||
self.action_go_to_previous_remote_dir();
|
||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||
let _ = self.update_local_filelist();
|
||||
self.update_local_filelist();
|
||||
}
|
||||
// Reload file list component
|
||||
self.update_remote_filelist()
|
||||
@@ -406,7 +406,7 @@ impl FileTransferActivity {
|
||||
self.disconnect();
|
||||
self.umount_disconnect();
|
||||
}
|
||||
UiMsg::ExplorerBackTabbed => {
|
||||
UiMsg::ShowLogPanel => {
|
||||
assert!(self.app.active(&Id::Log).is_ok());
|
||||
}
|
||||
UiMsg::LogBackTabbed => {
|
||||
|
||||
@@ -80,8 +80,8 @@ impl FileTransferActivity {
|
||||
self.refresh_local_status_bar();
|
||||
self.refresh_remote_status_bar();
|
||||
// Update components
|
||||
let _ = self.update_local_filelist();
|
||||
let _ = self.update_remote_filelist();
|
||||
self.update_local_filelist();
|
||||
self.update_remote_filelist();
|
||||
// Global listener
|
||||
self.mount_global_listener();
|
||||
// Give focus to local explorer
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::{CommonMsg, Msg, ViewLayout};
|
||||
|
||||
use tui_realm_stdlib::{List, Paragraph, Radio, Span};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
|
||||
@@ -296,6 +296,14 @@ impl Component<Msg, NoUserEvent> for SavePopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Common(CommonMsg::SaveConfig)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Common(CommonMsg::CloseSavePopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
|
||||
@@ -278,7 +278,7 @@ impl NotificationsThreshold {
|
||||
parse_bytesize(bytes).is_some()
|
||||
}
|
||||
fn char_valid(_input: &str, incoming: char) -> bool {
|
||||
incoming.is_digit(10) || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
|
||||
incoming.is_ascii_digit() || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
|
||||
}
|
||||
Self {
|
||||
component: Input::default()
|
||||
|
||||
@@ -58,6 +58,14 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
|
||||
self.perform(Cmd::Move(Direction::Right));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('y'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ssh(SshMsg::DeleteSshKey)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => Some(Msg::Ssh(SshMsg::CloseDelSshKeyPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
@@ -143,7 +151,8 @@ impl Component<Msg, NoUserEvent> for SshKeys {
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Delete, ..
|
||||
code: Key::Delete | Key::Char('e'),
|
||||
..
|
||||
}) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)),
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char('n'),
|
||||
|
||||
@@ -99,7 +99,7 @@ pub enum IdTheme {
|
||||
TransferTitle2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Msg {
|
||||
Common(CommonMsg),
|
||||
Config(ConfigMsg),
|
||||
@@ -108,7 +108,7 @@ pub enum Msg {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CommonMsg {
|
||||
ChangeLayout,
|
||||
CloseErrorPopup,
|
||||
@@ -125,7 +125,7 @@ pub enum CommonMsg {
|
||||
WindowResized,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ConfigMsg {
|
||||
CheckUpdatesBlurDown,
|
||||
CheckUpdatesBlurUp,
|
||||
@@ -152,7 +152,7 @@ pub enum ConfigMsg {
|
||||
TextEditorBlurUp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SshMsg {
|
||||
CloseDelSshKeyPopup,
|
||||
CloseNewSshKeyPopup,
|
||||
@@ -165,7 +165,7 @@ pub enum SshMsg {
|
||||
SshUsernameBlur,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ThemeMsg {
|
||||
AuthAddressBlurDown,
|
||||
AuthAddressBlurUp,
|
||||
@@ -230,7 +230,7 @@ const STORE_CONFIG_CHANGED: &str = "SETUP_CONFIG_CHANGED";
|
||||
/// ### ViewLayout
|
||||
///
|
||||
/// Current view layout
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum ViewLayout {
|
||||
SetupForm,
|
||||
SshKeys,
|
||||
|
||||
@@ -20,8 +20,6 @@ impl SetupActivity {
|
||||
self.new_app(ViewLayout::SshKeys);
|
||||
// Load keys
|
||||
self.reload_ssh_keys();
|
||||
// Give focus
|
||||
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
|
||||
}
|
||||
|
||||
pub(crate) fn view_ssh_keys(&mut self) {
|
||||
@@ -137,5 +135,6 @@ impl SetupActivity {
|
||||
vec![]
|
||||
)
|
||||
.is_ok());
|
||||
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ mod tests {
|
||||
fn test_utils_crypto_aes128() {
|
||||
let key: &str = "MYSUPERSECRETKEY";
|
||||
let input: &str = "Hello world!";
|
||||
let secret: String = aes128_b64_crypt(&key, input);
|
||||
let secret: String = aes128_b64_crypt(key, input);
|
||||
assert_eq!(secret.as_str(), "z4Z6LpcpYqBW4+bkIok+5A==");
|
||||
assert_eq!(
|
||||
aes128_b64_decrypt(key, secret.as_str())
|
||||
|
||||
@@ -300,7 +300,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_utils_fmt_time() {
|
||||
let system_time: SystemTime = SystemTime::from(SystemTime::UNIX_EPOCH);
|
||||
let system_time: SystemTime = SystemTime::UNIX_EPOCH;
|
||||
assert_eq!(
|
||||
fmt_time(system_time, "%Y-%m-%d"),
|
||||
String::from("1970-01-01")
|
||||
@@ -326,12 +326,12 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn test_utils_fmt_path_elide() {
|
||||
let p: &Path = &Path::new("/develop/pippo");
|
||||
let p: &Path = Path::new("/develop/pippo");
|
||||
// Under max size
|
||||
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo"));
|
||||
// Above max size, only one ancestor
|
||||
assert_eq!(fmt_path_elide(p, 8), String::from("/develop/pippo"));
|
||||
let p: &Path = &Path::new("/develop/pippo/foo/bar");
|
||||
let p: &Path = Path::new("/develop/pippo/foo/bar");
|
||||
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/…/foo/bar"));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,56 +14,57 @@ use crate::system::environment;
|
||||
|
||||
// Ext
|
||||
use bytesize::ByteSize;
|
||||
use regex::Regex;
|
||||
use lazy_regex::{Lazy, Regex};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use tuirealm::tui::style::Color;
|
||||
use tuirealm::utils::parser as tuirealm_parser;
|
||||
|
||||
// Regex
|
||||
lazy_static! {
|
||||
|
||||
/**
|
||||
* This regex matches the protocol used as option
|
||||
* Regex matches:
|
||||
* - group 1: Some(protocol) | None
|
||||
* - group 2: Some(other args)
|
||||
*/
|
||||
static ref REMOTE_OPT_PROTOCOL_REGEX: Regex = Regex::new(r"(?:([a-z0-9]+)://)?(?:(.+))").unwrap();
|
||||
/**
|
||||
* This regex matches the protocol used as option
|
||||
* Regex matches:
|
||||
* - group 1: Some(protocol) | None
|
||||
* - group 2: Some(other args)
|
||||
*/
|
||||
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Some(user) | None
|
||||
* - group 2: Address
|
||||
* - group 3: Some(port) | None
|
||||
* - group 4: Some(path) | None
|
||||
*/
|
||||
static ref REMOTE_GENERIC_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?").ok().unwrap();
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Some(user) | None
|
||||
* - group 2: Address
|
||||
* - group 3: Some(port) | None
|
||||
* - group 4: Some(path) | None
|
||||
*/
|
||||
static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
||||
r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?"
|
||||
);
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Bucket
|
||||
* - group 2: Region
|
||||
* - group 3: Some(profile) | None
|
||||
* - group 4: Some(path) | None
|
||||
*/
|
||||
static ref REMOTE_S3_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?").unwrap();
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Bucket
|
||||
* - group 2: Region
|
||||
* - group 3: Some(profile) | None
|
||||
* - group 4: Some(path) | None
|
||||
*/
|
||||
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
|
||||
lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Version
|
||||
* E.g. termscp-0.3.2 => 0.3.2
|
||||
* v0.4.0 => 0.4.0
|
||||
*/
|
||||
static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap();
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: Version
|
||||
* E.g. termscp-0.3.2 => 0.3.2
|
||||
* v0.4.0 => 0.4.0
|
||||
*/
|
||||
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
|
||||
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: amount (number)
|
||||
* - group 4: unit (K, M, G, T, P)
|
||||
*/
|
||||
static ref BYTESIZE_REGEX: Regex = Regex::new(r"(:?([0-9])+)( )*(:?[KMGTP])?B$").unwrap();
|
||||
}
|
||||
/**
|
||||
* Regex matches:
|
||||
* - group 1: amount (number)
|
||||
* - group 4: unit (K, M, G, T, P)
|
||||
*/
|
||||
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
|
||||
|
||||
// -- remote opts
|
||||
|
||||
|
||||
@@ -94,11 +94,11 @@ mod test {
|
||||
#[test]
|
||||
fn absolutize_path() {
|
||||
assert_eq!(
|
||||
absolutize(&Path::new("/home/omar"), &Path::new("readme.txt")).as_path(),
|
||||
absolutize(Path::new("/home/omar"), Path::new("readme.txt")).as_path(),
|
||||
Path::new("/home/omar/readme.txt")
|
||||
);
|
||||
assert_eq!(
|
||||
absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(),
|
||||
absolutize(Path::new("/home/omar"), Path::new("/tmp/readme.txt")).as_path(),
|
||||
Path::new("/tmp/readme.txt")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Create sample file with default lorem ipsum content
|
||||
pub fn create_sample_file() -> NamedTempFile {
|
||||
// Write
|
||||
create_sample_file_with_content("Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus.")
|
||||
}
|
||||
|
||||
/// Create sample file with provided content
|
||||
pub fn create_sample_file_with_content(content: impl std::fmt::Display) -> NamedTempFile {
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
writeln!(
|
||||
tmpfile,
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(tmpfile, "{}", content).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user