Merge pull request #27 from veeso/local-and-remote-file-fmt

Remote file syntax for formatter
This commit is contained in:
Christian Visintin
2021-05-01 19:29:37 +02:00
committed by GitHub
11 changed files with 167 additions and 51 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Run cargo-tarpaulin - name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1 uses: actions-rs/tarpaulin@v0.1
with: with:
args: "--ignore-tests -- --test-threads 1" args: "--ignore-tests --features githubActions -- --test-threads 1"
- name: Upload to codecov.io - name: Upload to codecov.io
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1

View File

@@ -21,6 +21,8 @@
Released on FIXME: ?? Released on FIXME: ??
- **Remote and Local hosts file formatter**:
- Added the possibility to set different formatters for local and remote hosts
- Bugfix: - Bugfix:
- Fixed wrong text wrap in log box - Fixed wrong text wrap in log box
- Dependencies: - Dependencies:

View File

@@ -196,7 +196,7 @@ You can access the SSH key storage, from configuration moving to the `SSH Keys`
### File Explorer Format ### File Explorer Format
It is possible through configuration to define a custom format for the file explorer. This field, with name `File formatter syntax` will define how the file entries will be displayed in the file explorer. It is possible through configuration to define a custom format for the file explorer. This is possible both for local and remote host, so you can have two different syntax in use. These fields, with name `File formatter syntax (local)` and `File formatter syntax (remote)` will define how the file entries will be displayed in the file explorer.
The syntax for the formatter is the following `{KEY1}... {KEY2:LENGTH}... {KEY3:LENGTH:EXTRA} {KEYn}...`. The syntax for the formatter is the following `{KEY1}... {KEY2:LENGTH}... {KEY3:LENGTH:EXTRA} {KEYn}...`.
Each key in bracket will be replaced with the related attribute, while everything outside brackets will be left unchanged. Each key in bracket will be replaced with the related attribute, while everything outside brackets will be left unchanged.

View File

@@ -60,7 +60,8 @@ pub struct UserInterfaceConfig {
pub show_hidden_files: bool, pub show_hidden_files: bool,
pub check_for_updates: Option<bool>, // @! Since 0.3.3 pub check_for_updates: Option<bool>, // @! Since 0.3.3
pub group_dirs: Option<String>, pub group_dirs: Option<String>,
pub file_fmt: Option<String>, pub file_fmt: Option<String>, // Refers to local host (for backward compatibility)
pub remote_file_fmt: Option<String>, // @! Since 0.5.0
} }
#[derive(Deserialize, Serialize, std::fmt::Debug)] #[derive(Deserialize, Serialize, std::fmt::Debug)]
@@ -92,6 +93,7 @@ impl Default for UserInterfaceConfig {
check_for_updates: Some(true), check_for_updates: Some(true),
group_dirs: None, group_dirs: None,
file_fmt: None, file_fmt: None,
remote_file_fmt: None,
} }
} }
} }
@@ -178,6 +180,7 @@ mod tests {
check_for_updates: Some(true), check_for_updates: Some(true),
group_dirs: Some(String::from("first")), group_dirs: Some(String::from("first")),
file_fmt: Some(String::from("{NAME}")), file_fmt: Some(String::from("{NAME}")),
remote_file_fmt: Some(String::from("{USER}")),
}; };
let cfg: UserConfig = UserConfig { let cfg: UserConfig = UserConfig {
user_interface: ui, user_interface: ui,
@@ -196,6 +199,10 @@ mod tests {
assert_eq!(cfg.user_interface.check_for_updates, Some(true)); assert_eq!(cfg.user_interface.check_for_updates, Some(true));
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first"))); assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first")));
assert_eq!(cfg.user_interface.file_fmt, Some(String::from("{NAME}"))); assert_eq!(cfg.user_interface.file_fmt, Some(String::from("{NAME}")));
assert_eq!(
cfg.user_interface.remote_file_fmt,
Some(String::from("{USER}"))
);
} }
#[test] #[test]
@@ -218,6 +225,8 @@ mod tests {
); );
assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true); assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true);
assert_eq!(cfg.remote.ssh_keys.len(), 0); assert_eq!(cfg.remote.ssh_keys.len(), 0);
assert!(cfg.user_interface.file_fmt.is_none());
assert!(cfg.user_interface.remote_file_fmt.is_none());
} }
#[test] #[test]

View File

@@ -114,6 +114,10 @@ mod tests {
cfg.user_interface.file_fmt, cfg.user_interface.file_fmt,
Some(String::from("{NAME} {PEX}")) Some(String::from("{NAME} {PEX}"))
); );
assert_eq!(
cfg.user_interface.remote_file_fmt,
Some(String::from("{NAME} {USER}")),
);
// Verify keys // Verify keys
assert_eq!( assert_eq!(
*cfg.remote *cfg.remote
@@ -149,7 +153,8 @@ mod tests {
assert_eq!(cfg.user_interface.show_hidden_files, true); assert_eq!(cfg.user_interface.show_hidden_files, true);
assert_eq!(cfg.user_interface.group_dirs, None); assert_eq!(cfg.user_interface.group_dirs, None);
assert!(cfg.user_interface.check_for_updates.is_none()); assert!(cfg.user_interface.check_for_updates.is_none());
assert_eq!(cfg.user_interface.file_fmt, None); assert!(cfg.user_interface.file_fmt.is_none());
assert!(cfg.user_interface.remote_file_fmt.is_none());
// Verify keys // Verify keys
assert_eq!( assert_eq!(
*cfg.remote *cfg.remote
@@ -208,6 +213,7 @@ mod tests {
check_for_updates = true check_for_updates = true
group_dirs = "last" group_dirs = "last"
file_fmt = "{NAME} {PEX}" file_fmt = "{NAME} {PEX}"
remote_file_fmt = "{NAME} {USER}"
[remote.ssh_keys] [remote.ssh_keys]
"192.168.1.31" = "/home/omar/.ssh/raspberry.key" "192.168.1.31" = "/home/omar/.ssh/raspberry.key"

View File

@@ -176,23 +176,40 @@ impl ConfigClient {
self.config.user_interface.group_dirs = val.map(|val| val.to_string()); self.config.user_interface.group_dirs = val.map(|val| val.to_string());
} }
/// ### get_file_fmt /// ### get_local_file_fmt
/// ///
/// Get current file fmt /// Get current file fmt for local host
pub fn get_file_fmt(&self) -> Option<String> { pub fn get_local_file_fmt(&self) -> Option<String> {
self.config.user_interface.file_fmt.clone() self.config.user_interface.file_fmt.clone()
} }
/// ### set_file_fmt /// ### set_local_file_fmt
/// ///
/// Set file fmt parameter /// Set file fmt parameter for local host
pub fn set_file_fmt(&mut self, s: String) { pub fn set_local_file_fmt(&mut self, s: String) {
self.config.user_interface.file_fmt = match s.is_empty() { self.config.user_interface.file_fmt = match s.is_empty() {
true => None, true => None,
false => Some(s), false => Some(s),
}; };
} }
/// ### get_remote_file_fmt
///
/// Get current file fmt for remote host
pub fn get_remote_file_fmt(&self) -> Option<String> {
self.config.user_interface.remote_file_fmt.clone()
}
/// ### set_remote_file_fmt
///
/// Set file fmt parameter for remote host
pub fn set_remote_file_fmt(&mut self, s: String) {
self.config.user_interface.remote_file_fmt = match s.is_empty() {
true => None,
false => Some(s),
};
}
// SSH Keys // SSH Keys
/// ### save_ssh_key /// ### save_ssh_key
@@ -496,18 +513,36 @@ mod tests {
} }
#[test] #[test]
fn test_system_config_file_fmt() { fn test_system_config_local_file_fmt() {
let tmp_dir: tempfile::TempDir = create_tmp_dir(); let tmp_dir: tempfile::TempDir = create_tmp_dir();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path()); 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()) let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
.ok() .ok()
.unwrap(); .unwrap();
assert_eq!(client.get_file_fmt(), None); assert_eq!(client.get_local_file_fmt(), None);
client.set_file_fmt(String::from("{NAME}")); client.set_local_file_fmt(String::from("{NAME}"));
assert_eq!(client.get_file_fmt().unwrap(), String::from("{NAME}")); assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}"));
// Delete // Delete
client.set_file_fmt(String::from("")); client.set_local_file_fmt(String::from(""));
assert_eq!(client.get_file_fmt(), None); assert_eq!(client.get_local_file_fmt(), None);
}
#[test]
fn test_system_config_remote_file_fmt() {
let tmp_dir: tempfile::TempDir = create_tmp_dir();
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();
assert_eq!(client.get_remote_file_fmt(), None);
client.set_remote_file_fmt(String::from("{NAME}"));
assert_eq!(
client.get_remote_file_fmt().unwrap(),
String::from("{NAME}")
);
// Delete
client.set_remote_file_fmt(String::from(""));
assert_eq!(client.get_remote_file_fmt(), None);
} }
#[test] #[test]

View File

@@ -94,21 +94,46 @@ impl FileTransferActivity {
/// ### build_explorer /// ### build_explorer
/// ///
/// Build explorer reading configuration from `ConfigClient` /// Build explorer reading configuration from `ConfigClient`
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer { fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorerBuilder {
let mut builder: FileExplorerBuilder = FileExplorerBuilder::new();
// Set common keys
builder
.with_file_sorting(FileSorting::ByName)
.with_stack_size(16);
match &cli { match &cli {
Some(cli) => FileExplorerBuilder::new() // Build according to current configuration Some(cli) => {
.with_file_sorting(FileSorting::ByName) builder // Build according to current configuration
.with_group_dirs(cli.get_group_dirs()) .with_group_dirs(cli.get_group_dirs())
.with_hidden_files(cli.get_show_hidden_files()) .with_hidden_files(cli.get_show_hidden_files());
.with_stack_size(16)
.with_formatter(cli.get_file_fmt().as_deref())
.build(),
None => FileExplorerBuilder::new() // Build default
.with_file_sorting(FileSorting::ByName)
.with_group_dirs(Some(GroupDirs::First))
.with_stack_size(16)
.build(),
} }
None => {
builder // Build default
.with_group_dirs(Some(GroupDirs::First));
}
};
builder
}
/// ### build_local_explorer
///
/// Build a file explorer with local host setup
pub(super) fn build_local_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
let mut builder = Self::build_explorer(cli);
if let Some(cli) = cli {
builder.with_formatter(cli.get_local_file_fmt().as_deref());
}
builder.build()
}
/// ### build_remote_explorer
///
/// Build a file explorer with remote host setup
pub(super) fn build_remote_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
let mut builder = Self::build_explorer(cli);
if let Some(cli) = cli {
builder.with_formatter(cli.get_remote_file_fmt().as_deref());
}
builder.build()
} }
/// ### build_found_explorer /// ### build_found_explorer

View File

@@ -239,8 +239,8 @@ impl FileTransferActivity {
Self::make_ssh_storage(config_client.as_ref()), Self::make_ssh_storage(config_client.as_ref()),
)), )),
}, },
local: Self::build_explorer(config_client.as_ref()), local: Self::build_local_explorer(config_client.as_ref()),
remote: Self::build_explorer(config_client.as_ref()), remote: Self::build_remote_explorer(config_client.as_ref()),
found: None, found: None,
tab: FileExplorerTab::Local, tab: FileExplorerTab::Local,
log_records: VecDeque::with_capacity(256), // 256 events is enough I guess log_records: VecDeque::with_capacity(256), // 256 events is enough I guess

View File

@@ -53,7 +53,8 @@ const COMPONENT_RADIO_DEFAULT_PROTOCOL: &str = "RADIO_DEFAULT_PROTOCOL";
const COMPONENT_RADIO_HIDDEN_FILES: &str = "RADIO_HIDDEN_FILES"; const COMPONENT_RADIO_HIDDEN_FILES: &str = "RADIO_HIDDEN_FILES";
const COMPONENT_RADIO_UPDATES: &str = "RADIO_CHECK_UPDATES"; const COMPONENT_RADIO_UPDATES: &str = "RADIO_CHECK_UPDATES";
const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS"; const COMPONENT_RADIO_GROUP_DIRS: &str = "RADIO_GROUP_DIRS";
const COMPONENT_INPUT_FILE_FMT: &str = "INPUT_FILE_FMT"; const COMPONENT_INPUT_LOCAL_FILE_FMT: &str = "INPUT_LOCAL_FILE_FMT";
const COMPONENT_INPUT_REMOTE_FILE_FMT: &str = "INPUT_REMOTE_FILE_FMT";
const COMPONENT_RADIO_TAB: &str = "RADIO_TAB"; const COMPONENT_RADIO_TAB: &str = "RADIO_TAB";
const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS"; const COMPONENT_LIST_SSH_KEYS: &str = "LIST_SSH_KEYS";
const COMPONENT_INPUT_SSH_HOST: &str = "INPUT_SSH_HOST"; const COMPONENT_INPUT_SSH_HOST: &str = "INPUT_SSH_HOST";

View File

@@ -28,11 +28,11 @@
*/ */
// locals // locals
use super::{ use super::{
SetupActivity, COMPONENT_INPUT_FILE_FMT, COMPONENT_INPUT_SSH_HOST, SetupActivity, COMPONENT_INPUT_LOCAL_FILE_FMT, COMPONENT_INPUT_REMOTE_FILE_FMT,
COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR, COMPONENT_LIST_SSH_KEYS, COMPONENT_INPUT_SSH_HOST, COMPONENT_INPUT_SSH_USERNAME, COMPONENT_INPUT_TEXT_EDITOR,
COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY, COMPONENT_RADIO_GROUP_DIRS, COMPONENT_LIST_SSH_KEYS, COMPONENT_RADIO_DEFAULT_PROTOCOL, COMPONENT_RADIO_DEL_SSH_KEY,
COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT, COMPONENT_RADIO_SAVE, COMPONENT_RADIO_GROUP_DIRS, COMPONENT_RADIO_HIDDEN_FILES, COMPONENT_RADIO_QUIT,
COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, COMPONENT_RADIO_SAVE, COMPONENT_RADIO_UPDATES, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP,
}; };
use crate::ui::activities::keymap::*; use crate::ui::activities::keymap::*;
@@ -68,15 +68,23 @@ impl SetupActivity {
None None
} }
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => { (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => {
self.view.active(COMPONENT_INPUT_FILE_FMT); self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
None None
} }
(COMPONENT_INPUT_FILE_FMT, &MSG_KEY_DOWN) => { (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => {
self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT);
None
}
(COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => {
self.view.active(COMPONENT_INPUT_TEXT_EDITOR); self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
None None
} }
// Input field <UP> // Input field <UP>
(COMPONENT_INPUT_FILE_FMT, &MSG_KEY_UP) => { (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => {
self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
None
}
(COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_UP) => {
self.view.active(COMPONENT_RADIO_GROUP_DIRS); self.view.active(COMPONENT_RADIO_GROUP_DIRS);
None None
} }
@@ -97,7 +105,7 @@ impl SetupActivity {
None None
} }
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => { (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => {
self.view.active(COMPONENT_INPUT_FILE_FMT); self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT);
None None
} }
// Error <ENTER> or <ESC> // Error <ENTER> or <ESC>

View File

@@ -173,12 +173,22 @@ impl SetupActivity {
)), )),
); );
self.view.mount( self.view.mount(
super::COMPONENT_INPUT_FILE_FMT, super::COMPONENT_INPUT_LOCAL_FILE_FMT,
Box::new(Input::new( Box::new(Input::new(
InputPropsBuilder::default() InputPropsBuilder::default()
.with_foreground(Color::LightBlue) .with_foreground(Color::LightBlue)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue)
.with_label(String::from("File formatter syntax")) .with_label(String::from("File formatter syntax (local)"))
.build(),
)),
);
self.view.mount(
super::COMPONENT_INPUT_REMOTE_FILE_FMT,
Box::new(Input::new(
InputPropsBuilder::default()
.with_foreground(Color::LightGreen)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen)
.with_label(String::from("File formatter syntax (remote)"))
.build(), .build(),
)), )),
); );
@@ -280,7 +290,8 @@ impl SetupActivity {
Constraint::Length(3), // Hidden files Constraint::Length(3), // Hidden files
Constraint::Length(3), // Updates tab Constraint::Length(3), // Updates tab
Constraint::Length(3), // Group dirs Constraint::Length(3), // Group dirs
Constraint::Length(3), // Format input Constraint::Length(3), // Local Format input
Constraint::Length(3), // Remote Format input
Constraint::Length(1), // Empty ? Constraint::Length(1), // Empty ?
] ]
.as_ref(), .as_ref(),
@@ -297,7 +308,9 @@ impl SetupActivity {
self.view self.view
.render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[4]); .render(super::COMPONENT_RADIO_GROUP_DIRS, f, ui_cfg_chunks[4]);
self.view self.view
.render(super::COMPONENT_INPUT_FILE_FMT, f, ui_cfg_chunks[5]); .render(super::COMPONENT_INPUT_LOCAL_FILE_FMT, f, ui_cfg_chunks[5]);
self.view
.render(super::COMPONENT_INPUT_REMOTE_FILE_FMT, f, ui_cfg_chunks[6]);
} }
ViewLayout::SshKeys => { ViewLayout::SshKeys => {
let sshcfg_chunks = Layout::default() let sshcfg_chunks = Layout::default()
@@ -692,11 +705,21 @@ impl SetupActivity {
let props = RadioPropsBuilder::from(props).with_value(dirs).build(); let props = RadioPropsBuilder::from(props).with_value(dirs).build();
let _ = self.view.update(super::COMPONENT_RADIO_GROUP_DIRS, props); let _ = self.view.update(super::COMPONENT_RADIO_GROUP_DIRS, props);
} }
// File Fmt // Local File Fmt
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_FILE_FMT) { if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_LOCAL_FILE_FMT) {
let file_fmt: String = cli.get_file_fmt().unwrap_or_default(); let file_fmt: String = cli.get_local_file_fmt().unwrap_or_default();
let props = InputPropsBuilder::from(props).with_value(file_fmt).build(); let props = InputPropsBuilder::from(props).with_value(file_fmt).build();
let _ = self.view.update(super::COMPONENT_INPUT_FILE_FMT, props); let _ = self
.view
.update(super::COMPONENT_INPUT_LOCAL_FILE_FMT, props);
}
// Remote File Fmt
if let Some(props) = self.view.get_props(super::COMPONENT_INPUT_REMOTE_FILE_FMT) {
let file_fmt: String = cli.get_remote_file_fmt().unwrap_or_default();
let props = InputPropsBuilder::from(props).with_value(file_fmt).build();
let _ = self
.view
.update(super::COMPONENT_INPUT_REMOTE_FILE_FMT, props);
} }
} }
} }
@@ -734,8 +757,15 @@ impl SetupActivity {
let check: bool = matches!(opt, 0); let check: bool = matches!(opt, 0);
cli.set_check_for_updates(check); cli.set_check_for_updates(check);
} }
if let Some(Payload::Text(fmt)) = self.view.get_state(super::COMPONENT_INPUT_FILE_FMT) { if let Some(Payload::Text(fmt)) =
cli.set_file_fmt(fmt); self.view.get_state(super::COMPONENT_INPUT_LOCAL_FILE_FMT)
{
cli.set_local_file_fmt(fmt);
}
if let Some(Payload::Text(fmt)) =
self.view.get_state(super::COMPONENT_INPUT_REMOTE_FILE_FMT)
{
cli.set_remote_file_fmt(fmt);
} }
if let Some(Payload::Unsigned(opt)) = if let Some(Payload::Unsigned(opt)) =
self.view.get_state(super::COMPONENT_RADIO_GROUP_DIRS) self.view.get_state(super::COMPONENT_RADIO_GROUP_DIRS)