mirror of
https://github.com/veeso/termscp.git
synced 2026-04-05 17:51:22 -07:00
Theme provider and '-t' and '-c' CLI options
This commit is contained in:
124
src/config/bookmarks.rs
Normal file
124
src/config/bookmarks.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
//! ## Bookmarks
|
||||
//!
|
||||
//! `bookmarks` is the module which provides data types and de/serializer for bookmarks
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||
/// ## UserHosts
|
||||
///
|
||||
/// UserHosts contains all the hosts saved by the user in the data storage
|
||||
/// It contains both `Bookmark`
|
||||
pub struct UserHosts {
|
||||
pub bookmarks: HashMap<String, Bookmark>,
|
||||
pub recents: HashMap<String, Bookmark>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug, PartialEq)]
|
||||
/// ## Bookmark
|
||||
///
|
||||
/// Bookmark describes a single bookmark entry in the user hosts storage
|
||||
pub struct Bookmark {
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
pub protocol: String,
|
||||
pub username: String,
|
||||
pub password: Option<String>, // Password is optional; base64, aes-128 encrypted password
|
||||
}
|
||||
|
||||
impl Default for UserHosts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bookmarks: HashMap::new(),
|
||||
recents: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_bookmarks_default() {
|
||||
let bookmarks: UserHosts = UserHosts::default();
|
||||
assert_eq!(bookmarks.bookmarks.len(), 0);
|
||||
assert_eq!(bookmarks.recents.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bookmarks_bookmark_new() {
|
||||
let bookmark: Bookmark = Bookmark {
|
||||
address: String::from("192.168.1.1"),
|
||||
port: 22,
|
||||
protocol: String::from("SFTP"),
|
||||
username: String::from("root"),
|
||||
password: Some(String::from("password")),
|
||||
};
|
||||
let recent: Bookmark = Bookmark {
|
||||
address: String::from("192.168.1.2"),
|
||||
port: 22,
|
||||
protocol: String::from("SCP"),
|
||||
username: String::from("admin"),
|
||||
password: Some(String::from("password")),
|
||||
};
|
||||
let mut bookmarks: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
bookmarks.insert(String::from("test"), bookmark);
|
||||
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
recents.insert(String::from("ISO20201218T181432"), recent);
|
||||
let hosts: UserHosts = UserHosts {
|
||||
bookmarks: bookmarks,
|
||||
recents: recents,
|
||||
};
|
||||
// Verify
|
||||
let bookmark: &Bookmark = hosts.bookmarks.get(&String::from("test")).unwrap();
|
||||
assert_eq!(bookmark.address, String::from("192.168.1.1"));
|
||||
assert_eq!(bookmark.port, 22);
|
||||
assert_eq!(bookmark.protocol, String::from("SFTP"));
|
||||
assert_eq!(bookmark.username, String::from("root"));
|
||||
assert_eq!(
|
||||
*bookmark.password.as_ref().unwrap(),
|
||||
String::from("password")
|
||||
);
|
||||
let bookmark: &Bookmark = hosts
|
||||
.recents
|
||||
.get(&String::from("ISO20201218T181432"))
|
||||
.unwrap();
|
||||
assert_eq!(bookmark.address, String::from("192.168.1.2"));
|
||||
assert_eq!(bookmark.port, 22);
|
||||
assert_eq!(bookmark.protocol, String::from("SCP"));
|
||||
assert_eq!(bookmark.username, String::from("admin"));
|
||||
assert_eq!(
|
||||
*bookmark.password.as_ref().unwrap(),
|
||||
String::from("password")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//! ## Config
|
||||
//!
|
||||
//! `config` is the module which provides access to termscp configuration
|
||||
//! `config` is the module which provides access to all the termscp configurations
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
@@ -25,237 +25,10 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Modules
|
||||
pub mod serializer;
|
||||
// export
|
||||
pub use params::*;
|
||||
|
||||
// Locals
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
|
||||
// Ext
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||
/// ## UserConfig
|
||||
///
|
||||
/// UserConfig contains all the configurations for the user,
|
||||
/// supported by termscp
|
||||
pub struct UserConfig {
|
||||
pub user_interface: UserInterfaceConfig,
|
||||
pub remote: RemoteConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||
/// ## UserInterfaceConfig
|
||||
///
|
||||
/// UserInterfaceConfig provides all the keys to configure the user interface
|
||||
pub struct UserInterfaceConfig {
|
||||
pub text_editor: PathBuf,
|
||||
pub default_protocol: String,
|
||||
pub show_hidden_files: bool,
|
||||
pub check_for_updates: Option<bool>, // @! Since 0.3.3
|
||||
pub group_dirs: 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)]
|
||||
/// ## RemoteConfig
|
||||
///
|
||||
/// Contains configuratio related to remote hosts
|
||||
pub struct RemoteConfig {
|
||||
pub ssh_keys: HashMap<String, PathBuf>, // Association between host name and path to private key
|
||||
}
|
||||
|
||||
impl Default for UserConfig {
|
||||
fn default() -> Self {
|
||||
UserConfig {
|
||||
user_interface: UserInterfaceConfig::default(),
|
||||
remote: RemoteConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserInterfaceConfig {
|
||||
fn default() -> Self {
|
||||
UserInterfaceConfig {
|
||||
text_editor: match edit::get_editor() {
|
||||
Ok(p) => p,
|
||||
Err(_) => PathBuf::from("nano"), // Default to nano
|
||||
},
|
||||
default_protocol: FileTransferProtocol::Sftp.to_string(),
|
||||
show_hidden_files: false,
|
||||
check_for_updates: Some(true),
|
||||
group_dirs: None,
|
||||
file_fmt: None,
|
||||
remote_file_fmt: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RemoteConfig {
|
||||
fn default() -> Self {
|
||||
RemoteConfig {
|
||||
ssh_keys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Errors
|
||||
|
||||
/// ## SerializerError
|
||||
///
|
||||
/// Contains the error for serializer/deserializer
|
||||
#[derive(std::fmt::Debug)]
|
||||
pub struct SerializerError {
|
||||
kind: SerializerErrorKind,
|
||||
msg: Option<String>,
|
||||
}
|
||||
|
||||
/// ## SerializerErrorKind
|
||||
///
|
||||
/// Describes the kind of error for the serializer/deserializer
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializerErrorKind {
|
||||
#[error("IO error")]
|
||||
IoError,
|
||||
#[error("Serialization error")]
|
||||
SerializationError,
|
||||
#[error("Syntax error")]
|
||||
SyntaxError,
|
||||
}
|
||||
|
||||
impl SerializerError {
|
||||
/// ### new
|
||||
///
|
||||
/// Instantiate a new `SerializerError`
|
||||
pub fn new(kind: SerializerErrorKind) -> SerializerError {
|
||||
SerializerError { kind, msg: None }
|
||||
}
|
||||
|
||||
/// ### new_ex
|
||||
///
|
||||
/// Instantiates a new `SerializerError` with description message
|
||||
pub fn new_ex(kind: SerializerErrorKind, msg: String) -> SerializerError {
|
||||
let mut err: SerializerError = SerializerError::new(kind);
|
||||
err.msg = Some(msg);
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SerializerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match &self.msg {
|
||||
Some(msg) => write!(f, "{} ({})", self.kind, msg),
|
||||
None => write!(f, "{}", self.kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::env;
|
||||
|
||||
#[test]
|
||||
fn test_config_mod_new() {
|
||||
let mut keys: HashMap<String, PathBuf> = HashMap::with_capacity(1);
|
||||
keys.insert(
|
||||
String::from("192.168.1.31"),
|
||||
PathBuf::from("/tmp/private.key"),
|
||||
);
|
||||
let remote: RemoteConfig = RemoteConfig { ssh_keys: keys };
|
||||
let ui: UserInterfaceConfig = UserInterfaceConfig {
|
||||
default_protocol: String::from("SFTP"),
|
||||
text_editor: PathBuf::from("nano"),
|
||||
show_hidden_files: true,
|
||||
check_for_updates: Some(true),
|
||||
group_dirs: Some(String::from("first")),
|
||||
file_fmt: Some(String::from("{NAME}")),
|
||||
remote_file_fmt: Some(String::from("{USER}")),
|
||||
};
|
||||
assert_eq!(ui.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(ui.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(ui.show_hidden_files, true);
|
||||
assert_eq!(ui.check_for_updates, Some(true));
|
||||
assert_eq!(ui.group_dirs, Some(String::from("first")));
|
||||
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
||||
let cfg: UserConfig = UserConfig {
|
||||
user_interface: ui,
|
||||
remote: remote,
|
||||
};
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/tmp/private.key")
|
||||
);
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, 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.file_fmt, Some(String::from("{NAME}")));
|
||||
assert_eq!(
|
||||
cfg.user_interface.remote_file_fmt,
|
||||
Some(String::from("{USER}"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_mod_new_default() {
|
||||
// Force vim editor
|
||||
env::set_var(String::from("EDITOR"), String::from("vim"));
|
||||
// Get default
|
||||
let cfg: UserConfig = UserConfig::default();
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
||||
// Text editor
|
||||
#[cfg(target_os = "windows")]
|
||||
assert_eq!(
|
||||
PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used
|
||||
PathBuf::from("vim.EXE")
|
||||
);
|
||||
#[cfg(target_family = "unix")]
|
||||
assert_eq!(
|
||||
PathBuf::from(cfg.user_interface.text_editor.file_name().unwrap()), // NOTE: since edit 0.1.3 real path is used
|
||||
PathBuf::from("vim")
|
||||
);
|
||||
assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true);
|
||||
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]
|
||||
fn test_config_mod_errors() {
|
||||
let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError);
|
||||
assert!(error.msg.is_none());
|
||||
assert_eq!(format!("{}", error), String::from("Syntax error"));
|
||||
let error: SerializerError =
|
||||
SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax"));
|
||||
assert!(error.msg.is_some());
|
||||
assert_eq!(
|
||||
format!("{}", error),
|
||||
String::from("Syntax error (bad syntax)")
|
||||
);
|
||||
// Fmt
|
||||
assert_eq!(
|
||||
format!("{}", SerializerError::new(SerializerErrorKind::IoError)),
|
||||
String::from("IO error")
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
SerializerError::new(SerializerErrorKind::SerializationError)
|
||||
),
|
||||
String::from("Serialization error")
|
||||
);
|
||||
}
|
||||
}
|
||||
pub mod bookmarks;
|
||||
pub mod params;
|
||||
pub mod serialization;
|
||||
pub mod themes;
|
||||
|
||||
155
src/config/params.rs
Normal file
155
src/config/params.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
//! ## Config
|
||||
//!
|
||||
//! `config` is the module which provides access to termscp configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// Locals
|
||||
use crate::filetransfer::FileTransferProtocol;
|
||||
|
||||
// Ext
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||
/// ## UserConfig
|
||||
///
|
||||
/// UserConfig contains all the configurations for the user,
|
||||
/// supported by termscp
|
||||
pub struct UserConfig {
|
||||
pub user_interface: UserInterfaceConfig,
|
||||
pub remote: RemoteConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, std::fmt::Debug)]
|
||||
/// ## UserInterfaceConfig
|
||||
///
|
||||
/// UserInterfaceConfig provides all the keys to configure the user interface
|
||||
pub struct UserInterfaceConfig {
|
||||
pub text_editor: PathBuf,
|
||||
pub default_protocol: String,
|
||||
pub show_hidden_files: bool,
|
||||
pub check_for_updates: Option<bool>, // @! Since 0.3.3
|
||||
pub group_dirs: 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)]
|
||||
/// ## RemoteConfig
|
||||
///
|
||||
/// Contains configuratio related to remote hosts
|
||||
pub struct RemoteConfig {
|
||||
pub ssh_keys: HashMap<String, PathBuf>, // Association between host name and path to private key
|
||||
}
|
||||
|
||||
impl Default for UserConfig {
|
||||
fn default() -> Self {
|
||||
UserConfig {
|
||||
user_interface: UserInterfaceConfig::default(),
|
||||
remote: RemoteConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserInterfaceConfig {
|
||||
fn default() -> Self {
|
||||
UserInterfaceConfig {
|
||||
text_editor: match edit::get_editor() {
|
||||
Ok(p) => p,
|
||||
Err(_) => PathBuf::from("nano"), // Default to nano
|
||||
},
|
||||
default_protocol: FileTransferProtocol::Sftp.to_string(),
|
||||
show_hidden_files: false,
|
||||
check_for_updates: Some(true),
|
||||
group_dirs: None,
|
||||
file_fmt: None,
|
||||
remote_file_fmt: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RemoteConfig {
|
||||
fn default() -> Self {
|
||||
RemoteConfig {
|
||||
ssh_keys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_config_mod_new() {
|
||||
let mut keys: HashMap<String, PathBuf> = HashMap::with_capacity(1);
|
||||
keys.insert(
|
||||
String::from("192.168.1.31"),
|
||||
PathBuf::from("/tmp/private.key"),
|
||||
);
|
||||
let remote: RemoteConfig = RemoteConfig { ssh_keys: keys };
|
||||
let ui: UserInterfaceConfig = UserInterfaceConfig {
|
||||
default_protocol: String::from("SFTP"),
|
||||
text_editor: PathBuf::from("nano"),
|
||||
show_hidden_files: true,
|
||||
check_for_updates: Some(true),
|
||||
group_dirs: Some(String::from("first")),
|
||||
file_fmt: Some(String::from("{NAME}")),
|
||||
remote_file_fmt: Some(String::from("{USER}")),
|
||||
};
|
||||
assert_eq!(ui.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(ui.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(ui.show_hidden_files, true);
|
||||
assert_eq!(ui.check_for_updates, Some(true));
|
||||
assert_eq!(ui.group_dirs, Some(String::from("first")));
|
||||
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
||||
let cfg: UserConfig = UserConfig {
|
||||
user_interface: ui,
|
||||
remote: remote,
|
||||
};
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/tmp/private.key")
|
||||
);
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, 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.file_fmt, Some(String::from("{NAME}")));
|
||||
assert_eq!(
|
||||
cfg.user_interface.remote_file_fmt,
|
||||
Some(String::from("{USER}"))
|
||||
);
|
||||
}
|
||||
}
|
||||
574
src/config/serialization.rs
Normal file
574
src/config/serialization.rs
Normal file
@@ -0,0 +1,574 @@
|
||||
//! ## Serialization
|
||||
//!
|
||||
//! `serialization` provides serialization and deserialization for configurations
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::io::{Read, Write};
|
||||
use thiserror::Error;
|
||||
|
||||
/// ## SerializerError
|
||||
///
|
||||
/// Contains the error for serializer/deserializer
|
||||
#[derive(std::fmt::Debug)]
|
||||
pub struct SerializerError {
|
||||
kind: SerializerErrorKind,
|
||||
msg: Option<String>,
|
||||
}
|
||||
|
||||
/// ## SerializerErrorKind
|
||||
///
|
||||
/// Describes the kind of error for the serializer/deserializer
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SerializerErrorKind {
|
||||
#[error("Operation failed")]
|
||||
GenericError,
|
||||
#[error("IO error")]
|
||||
IoError,
|
||||
#[error("Serialization error")]
|
||||
SerializationError,
|
||||
#[error("Syntax error")]
|
||||
SyntaxError,
|
||||
}
|
||||
|
||||
impl SerializerError {
|
||||
/// ### new
|
||||
///
|
||||
/// Instantiate a new `SerializerError`
|
||||
pub fn new(kind: SerializerErrorKind) -> SerializerError {
|
||||
SerializerError { kind, msg: None }
|
||||
}
|
||||
|
||||
/// ### new_ex
|
||||
///
|
||||
/// Instantiates a new `SerializerError` with description message
|
||||
pub fn new_ex(kind: SerializerErrorKind, msg: String) -> SerializerError {
|
||||
let mut err: SerializerError = SerializerError::new(kind);
|
||||
err.msg = Some(msg);
|
||||
err
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SerializerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match &self.msg {
|
||||
Some(msg) => write!(f, "{} ({})", self.kind, msg),
|
||||
None => write!(f, "{}", self.kind),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ### serialize
|
||||
///
|
||||
/// Serialize `UserHosts` into TOML and write content to writable
|
||||
pub fn serialize<S>(serializable: &S, mut writable: Box<dyn Write>) -> Result<(), SerializerError>
|
||||
where
|
||||
S: Serialize + Sized,
|
||||
{
|
||||
// Serialize content
|
||||
let data: String = match toml::ser::to_string(serializable) {
|
||||
Ok(dt) => dt,
|
||||
Err(err) => {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::SerializationError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
trace!("Serialized new bookmarks data: {}", data);
|
||||
// Write file
|
||||
match writable.write_all(data.as_bytes()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### deserialize
|
||||
///
|
||||
/// Read data from readable and deserialize its content as TOML
|
||||
pub fn deserialize<S>(mut readable: Box<dyn Read>) -> Result<S, SerializerError>
|
||||
where
|
||||
S: DeserializeOwned + Sized + std::fmt::Debug,
|
||||
{
|
||||
// Read file content
|
||||
let mut data: String = String::new();
|
||||
if let Err(err) = readable.read_to_string(&mut data) {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
trace!("Read bookmarks from file: {}", data);
|
||||
// Deserialize
|
||||
match toml::de::from_str(data.as_str()) {
|
||||
Ok(deserialized) => {
|
||||
debug!("Read bookmarks from file {:?}", deserialized);
|
||||
Ok(deserialized)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::SyntaxError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
use tuirealm::tui::style::Color;
|
||||
|
||||
use crate::config::bookmarks::{Bookmark, UserHosts};
|
||||
use crate::config::params::UserConfig;
|
||||
use crate::config::themes::Theme;
|
||||
use crate::utils::test_helpers::create_file_ioers;
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_errors() {
|
||||
let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError);
|
||||
assert!(error.msg.is_none());
|
||||
assert_eq!(format!("{}", error), String::from("Syntax error"));
|
||||
let error: SerializerError =
|
||||
SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax"));
|
||||
assert!(error.msg.is_some());
|
||||
assert_eq!(
|
||||
format!("{}", error),
|
||||
String::from("Syntax error (bad syntax)")
|
||||
);
|
||||
// Fmt
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
SerializerError::new(SerializerErrorKind::GenericError)
|
||||
),
|
||||
String::from("Operation failed")
|
||||
);
|
||||
assert_eq!(
|
||||
format!("{}", SerializerError::new(SerializerErrorKind::IoError)),
|
||||
String::from("IO error")
|
||||
);
|
||||
assert_eq!(
|
||||
format!(
|
||||
"{}",
|
||||
SerializerError::new(SerializerErrorKind::SerializationError)
|
||||
),
|
||||
String::from("Serialization error")
|
||||
);
|
||||
}
|
||||
|
||||
// -- Serialization of params
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_deserialize_ok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_good_toml_bookmarks_params();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let cfg = deserialize(Box::new(toml_file));
|
||||
assert!(cfg.is_ok());
|
||||
let cfg: UserConfig = cfg.ok().unwrap();
|
||||
// Verify configuration
|
||||
// Verify ui
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||
assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true);
|
||||
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
|
||||
assert_eq!(
|
||||
cfg.user_interface.file_fmt,
|
||||
Some(String::from("{NAME} {PEX}"))
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.user_interface.remote_file_fmt,
|
||||
Some(String::from("{NAME} {USER}")),
|
||||
);
|
||||
// Verify keys
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/raspberry.key")
|
||||
);
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.32"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/beaglebone.key")
|
||||
);
|
||||
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_deserialize_ok_no_opts() {
|
||||
let toml_file: tempfile::NamedTempFile = create_good_toml_bookmarks_params_no_opts();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let cfg = deserialize(Box::new(toml_file));
|
||||
assert!(cfg.is_ok());
|
||||
let cfg: UserConfig = cfg.ok().unwrap();
|
||||
// Verify configuration
|
||||
// Verify ui
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||
assert_eq!(cfg.user_interface.group_dirs, None);
|
||||
assert!(cfg.user_interface.check_for_updates.is_none());
|
||||
assert!(cfg.user_interface.file_fmt.is_none());
|
||||
assert!(cfg.user_interface.remote_file_fmt.is_none());
|
||||
// Verify keys
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/raspberry.key")
|
||||
);
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.32"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/beaglebone.key")
|
||||
);
|
||||
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_deserialize_nok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_bad_toml_bookmarks_params();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
assert!(deserialize::<UserConfig>(Box::new(toml_file)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_serialize() {
|
||||
let mut cfg: UserConfig = UserConfig::default();
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
// Insert key
|
||||
cfg.remote.ssh_keys.insert(
|
||||
String::from("192.168.1.31"),
|
||||
PathBuf::from("/home/omar/.ssh/id_rsa"),
|
||||
);
|
||||
// Serialize
|
||||
let writer: Box<dyn Write> = Box::new(std::fs::File::create(toml_file.path()).unwrap());
|
||||
assert!(serialize(&cfg, writer).is_ok());
|
||||
// Reload configuration and check if it's ok
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
assert!(deserialize::<UserConfig>(Box::new(toml_file)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_fail_write() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let writer: Box<dyn Write> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
let cfg: UserConfig = UserConfig::default();
|
||||
assert!(serialize(&cfg, writer).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_params_fail_read() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let reader: Box<dyn Read> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
assert!(deserialize::<UserConfig>(reader).is_err());
|
||||
}
|
||||
|
||||
fn create_good_toml_bookmarks_params() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SCP"
|
||||
text_editor = "vim"
|
||||
show_hidden_files = true
|
||||
check_for_updates = true
|
||||
group_dirs = "last"
|
||||
file_fmt = "{NAME} {PEX}"
|
||||
remote_file_fmt = "{NAME} {USER}"
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_good_toml_bookmarks_params_no_opts() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SCP"
|
||||
text_editor = "vim"
|
||||
show_hidden_files = true
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_bad_toml_bookmarks_params() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SFTP"
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
// -- bookmarks
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_bookmarks_serializer_deserialize_ok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_good_toml_bookmarks();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let hosts = deserialize(Box::new(toml_file));
|
||||
assert!(hosts.is_ok());
|
||||
let hosts: UserHosts = hosts.ok().unwrap();
|
||||
// Verify hosts
|
||||
// Verify recents
|
||||
assert_eq!(hosts.recents.len(), 1);
|
||||
let host: &Bookmark = hosts.recents.get("ISO20201215T094000Z").unwrap();
|
||||
assert_eq!(host.address, String::from("172.16.104.10"));
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SCP"));
|
||||
assert_eq!(host.username, String::from("root"));
|
||||
assert_eq!(host.password, None);
|
||||
// Verify bookmarks
|
||||
assert_eq!(hosts.bookmarks.len(), 3);
|
||||
let host: &Bookmark = hosts.bookmarks.get("raspberrypi2").unwrap();
|
||||
assert_eq!(host.address, String::from("192.168.1.31"));
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SFTP"));
|
||||
assert_eq!(host.username, String::from("root"));
|
||||
assert_eq!(*host.password.as_ref().unwrap(), String::from("mypassword"));
|
||||
let host: &Bookmark = hosts.bookmarks.get("msi-estrem").unwrap();
|
||||
assert_eq!(host.address, String::from("192.168.1.30"));
|
||||
assert_eq!(host.port, 22);
|
||||
assert_eq!(host.protocol, String::from("SFTP"));
|
||||
assert_eq!(host.username, String::from("cvisintin"));
|
||||
assert_eq!(*host.password.as_ref().unwrap(), String::from("mysecret"));
|
||||
let host: &Bookmark = hosts.bookmarks.get("aws-server-prod1").unwrap();
|
||||
assert_eq!(host.address, String::from("51.23.67.12"));
|
||||
assert_eq!(host.port, 21);
|
||||
assert_eq!(host.protocol, String::from("FTPS"));
|
||||
assert_eq!(host.username, String::from("aws001"));
|
||||
assert_eq!(host.password, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_bookmarks_serializer_deserialize_nok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_bad_toml_bookmarks();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
assert!(deserialize::<UserHosts>(Box::new(toml_file)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_bookmarks_serializer_serialize() {
|
||||
let mut bookmarks: HashMap<String, Bookmark> = HashMap::with_capacity(2);
|
||||
// Push two samples
|
||||
bookmarks.insert(
|
||||
String::from("raspberrypi2"),
|
||||
Bookmark {
|
||||
address: String::from("192.168.1.31"),
|
||||
port: 22,
|
||||
protocol: String::from("SFTP"),
|
||||
username: String::from("root"),
|
||||
password: None,
|
||||
},
|
||||
);
|
||||
bookmarks.insert(
|
||||
String::from("msi-estrem"),
|
||||
Bookmark {
|
||||
address: String::from("192.168.1.30"),
|
||||
port: 4022,
|
||||
protocol: String::from("SFTP"),
|
||||
username: String::from("cvisintin"),
|
||||
password: Some(String::from("password")),
|
||||
},
|
||||
);
|
||||
let mut recents: HashMap<String, Bookmark> = HashMap::with_capacity(1);
|
||||
recents.insert(
|
||||
String::from("ISO20201215T094000Z"),
|
||||
Bookmark {
|
||||
address: String::from("192.168.1.254"),
|
||||
port: 3022,
|
||||
protocol: String::from("SCP"),
|
||||
username: String::from("omar"),
|
||||
password: Some(String::from("aaa")),
|
||||
},
|
||||
);
|
||||
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
// Serialize
|
||||
let hosts: UserHosts = UserHosts { bookmarks, recents };
|
||||
assert!(serialize(&hosts, Box::new(tmpfile)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_theme_serialize() {
|
||||
let mut theme: Theme = Theme::default();
|
||||
theme.auth_address = Color::Rgb(240, 240, 240);
|
||||
let tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let (reader, writer) = create_file_ioers(tmpfile.path());
|
||||
assert!(serialize(&theme, Box::new(writer)).is_ok());
|
||||
// Try to deserialize
|
||||
let deserialized_theme: Theme = deserialize(Box::new(reader)).ok().unwrap();
|
||||
assert_eq!(theme, deserialized_theme);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serialization_theme_deserialize() {
|
||||
let toml_file = create_good_toml_theme();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
assert!(deserialize::<Theme>(Box::new(toml_file)).is_ok());
|
||||
let toml_file = create_bad_toml_theme();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
assert!(deserialize::<Theme>(Box::new(toml_file)).is_err());
|
||||
}
|
||||
|
||||
fn create_good_toml_bookmarks() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[bookmarks]
|
||||
raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root", password = "mypassword" }
|
||||
msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP", username = "cvisintin", password = "mysecret" }
|
||||
aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" }
|
||||
|
||||
[recents]
|
||||
ISO20201215T094000Z = { address = "172.16.104.10", port = 22, protocol = "SCP", username = "root" }
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
//write!(tmpfile, "[bookmarks]\nraspberrypi2 = {{ address = \"192.168.1.31\", port = 22, protocol = \"SFTP\", username = \"root\" }}\nmsi-estrem = {{ address = \"192.168.1.30\", port = 22, protocol = \"SFTP\", username = \"cvisintin\" }}\naws-server-prod1 = {{ address = \"51.23.67.12\", port = 21, protocol = \"FTPS\", username = \"aws001\" }}\n\n[recents]\nISO20201215T094000Z = {{ address = \"172.16.104.10\", port = 22, protocol = \"SCP\", username = \"root\" }}\n");
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_bad_toml_bookmarks() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[bookmarks]
|
||||
raspberrypi2 = { address = "192.168.1.31", port = 22, protocol = "SFTP", username = "root"}
|
||||
msi-estrem = { address = "192.168.1.30", port = 22, protocol = "SFTP" }
|
||||
aws-server-prod1 = { address = "51.23.67.12", port = 21, protocol = "FTPS", username = "aws001" }
|
||||
|
||||
[recents]
|
||||
ISO20201215T094000Z = { address = "172.16.104.10", protocol = "SCP", username = "root", port = 22 }
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_good_toml_theme() -> tempfile::NamedTempFile {
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r##"auth_address = "Yellow"
|
||||
auth_bookmarks = "LightGreen"
|
||||
auth_password = "LightBlue"
|
||||
auth_port = "LightCyan"
|
||||
auth_protocol = "LightGreen"
|
||||
auth_recents = "LightBlue"
|
||||
auth_username = "LightMagenta"
|
||||
misc_error_dialog = "Red"
|
||||
misc_input_dialog = "240,240,240"
|
||||
misc_keys = "Cyan"
|
||||
misc_quit_dialog = "Yellow"
|
||||
misc_save_dialog = "Cyan"
|
||||
misc_warn_dialog = "LightRed"
|
||||
transfer_local_explorer_background = "rgb(240, 240, 240)"
|
||||
transfer_local_explorer_foreground = "rgb(60, 60, 60)"
|
||||
transfer_local_explorer_highlighted = "Yellow"
|
||||
transfer_log_background = "255, 255, 255"
|
||||
transfer_log_window = "LightGreen"
|
||||
transfer_progress_bar = "Green"
|
||||
transfer_remote_explorer_background = "#f0f0f0"
|
||||
transfer_remote_explorer_foreground = "rgb(40, 40, 40)"
|
||||
transfer_remote_explorer_highlighted = "LightBlue"
|
||||
transfer_status_hidden = "LightBlue"
|
||||
transfer_status_sorting = "LightYellow"
|
||||
transfer_status_sync_browsing = "LightGreen"
|
||||
"##;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_bad_toml_theme() -> tempfile::NamedTempFile {
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
auth_address = "Yellow"
|
||||
auth_bookmarks = "LightGreen"
|
||||
auth_password = "LightBlue"
|
||||
auth_port = "LightCyan"
|
||||
auth_protocol = "LightGreen"
|
||||
auth_recents = "LightBlue"
|
||||
auth_username = "LightMagenta"
|
||||
misc_error_dialog = "Red"
|
||||
misc_input_dialog = "240,240,240"
|
||||
misc_keys = "Cyan"
|
||||
misc_quit_dialog = "Yellow"
|
||||
misc_warn_dialog = "LightRed"
|
||||
transfer_local_explorer_text = "rgb(240, 240, 240)"
|
||||
transfer_local_explorer_window = "Yellow"
|
||||
transfer_log_text = "255, 255, 255"
|
||||
transfer_log_window = "LightGreen"
|
||||
transfer_progress_bar = "Green"
|
||||
transfer_remote_explorer_text = "verdazzurro"
|
||||
transfer_remote_explorer_window = "LightBlue"
|
||||
transfer_status_hidden = "LightBlue"
|
||||
transfer_status_sorting = "LightYellow"
|
||||
transfer_status_sync_browsing = "LightGreen"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
//! ## Serializer
|
||||
//!
|
||||
//! `serializer` is the module which provides the serializer/deserializer for configuration
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
use super::{SerializerError, SerializerErrorKind, UserConfig};
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
pub struct ConfigSerializer;
|
||||
|
||||
impl ConfigSerializer {
|
||||
/// ### serialize
|
||||
///
|
||||
/// Serialize `UserConfig` into TOML and write content to writable
|
||||
pub fn serialize(
|
||||
&self,
|
||||
mut writable: Box<dyn Write>,
|
||||
cfg: &UserConfig,
|
||||
) -> Result<(), SerializerError> {
|
||||
// Serialize content
|
||||
let data: String = match toml::ser::to_string(cfg) {
|
||||
Ok(dt) => dt,
|
||||
Err(err) => {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::SerializationError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
trace!("Serialized new configuration data: {}", data);
|
||||
// Write file
|
||||
match writable.write_all(data.as_bytes()) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// ### deserialize
|
||||
///
|
||||
/// Read data from readable and deserialize its content as TOML
|
||||
pub fn deserialize(&self, mut readable: Box<dyn Read>) -> Result<UserConfig, SerializerError> {
|
||||
// Read file content
|
||||
let mut data: String = String::new();
|
||||
if let Err(err) = readable.read_to_string(&mut data) {
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
trace!("Read configuration from file: {}", data);
|
||||
// Deserialize
|
||||
match toml::de::from_str(data.as_str()) {
|
||||
Ok(config) => {
|
||||
debug!("Read config from file {:?}", config);
|
||||
Ok(config)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::SyntaxError,
|
||||
err.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_deserialize_ok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_good_toml();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let deserializer: ConfigSerializer = ConfigSerializer {};
|
||||
let cfg = deserializer.deserialize(Box::new(toml_file));
|
||||
assert!(cfg.is_ok());
|
||||
let cfg: UserConfig = cfg.ok().unwrap();
|
||||
// Verify configuration
|
||||
// Verify ui
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||
assert_eq!(cfg.user_interface.check_for_updates.unwrap(), true);
|
||||
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
|
||||
assert_eq!(
|
||||
cfg.user_interface.file_fmt,
|
||||
Some(String::from("{NAME} {PEX}"))
|
||||
);
|
||||
assert_eq!(
|
||||
cfg.user_interface.remote_file_fmt,
|
||||
Some(String::from("{NAME} {USER}")),
|
||||
);
|
||||
// Verify keys
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/raspberry.key")
|
||||
);
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.32"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/beaglebone.key")
|
||||
);
|
||||
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_deserialize_ok_no_opts() {
|
||||
let toml_file: tempfile::NamedTempFile = create_good_toml_no_opts();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let deserializer: ConfigSerializer = ConfigSerializer {};
|
||||
let cfg = deserializer.deserialize(Box::new(toml_file));
|
||||
assert!(cfg.is_ok());
|
||||
let cfg: UserConfig = cfg.ok().unwrap();
|
||||
// Verify configuration
|
||||
// Verify ui
|
||||
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
|
||||
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
|
||||
assert_eq!(cfg.user_interface.show_hidden_files, true);
|
||||
assert_eq!(cfg.user_interface.group_dirs, None);
|
||||
assert!(cfg.user_interface.check_for_updates.is_none());
|
||||
assert!(cfg.user_interface.file_fmt.is_none());
|
||||
assert!(cfg.user_interface.remote_file_fmt.is_none());
|
||||
// Verify keys
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.31"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/raspberry.key")
|
||||
);
|
||||
assert_eq!(
|
||||
*cfg.remote
|
||||
.ssh_keys
|
||||
.get(&String::from("192.168.1.32"))
|
||||
.unwrap(),
|
||||
PathBuf::from("/home/omar/.ssh/beaglebone.key")
|
||||
);
|
||||
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_deserialize_nok() {
|
||||
let toml_file: tempfile::NamedTempFile = create_bad_toml();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
// Parse
|
||||
let deserializer: ConfigSerializer = ConfigSerializer {};
|
||||
assert!(deserializer.deserialize(Box::new(toml_file)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_serialize() {
|
||||
let mut cfg: UserConfig = UserConfig::default();
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
// Insert key
|
||||
cfg.remote.ssh_keys.insert(
|
||||
String::from("192.168.1.31"),
|
||||
PathBuf::from("/home/omar/.ssh/id_rsa"),
|
||||
);
|
||||
// Serialize
|
||||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
let writer: Box<dyn Write> = Box::new(std::fs::File::create(toml_file.path()).unwrap());
|
||||
assert!(serializer.serialize(writer, &cfg).is_ok());
|
||||
// Reload configuration and check if it's ok
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
|
||||
assert!(serializer.deserialize(Box::new(toml_file)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_fail_write() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let writer: Box<dyn Write> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
let cfg: UserConfig = UserConfig::default();
|
||||
assert!(serializer.serialize(writer, &cfg).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_config_serializer_fail_read() {
|
||||
let toml_file: tempfile::NamedTempFile = tempfile::NamedTempFile::new().ok().unwrap();
|
||||
let reader: Box<dyn Read> = Box::new(std::fs::File::open(toml_file.path()).unwrap());
|
||||
// Try to write unexisting file
|
||||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
assert!(serializer.deserialize(reader).is_err());
|
||||
}
|
||||
|
||||
fn create_good_toml() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SCP"
|
||||
text_editor = "vim"
|
||||
show_hidden_files = true
|
||||
check_for_updates = true
|
||||
group_dirs = "last"
|
||||
file_fmt = "{NAME} {PEX}"
|
||||
remote_file_fmt = "{NAME} {USER}"
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_good_toml_no_opts() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SCP"
|
||||
text_editor = "vim"
|
||||
show_hidden_files = true
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
|
||||
fn create_bad_toml() -> tempfile::NamedTempFile {
|
||||
// Write
|
||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||
let file_content: &str = r#"
|
||||
[user_interface]
|
||||
default_protocol = "SFTP"
|
||||
|
||||
[remote.ssh_keys]
|
||||
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
|
||||
"#;
|
||||
tmpfile.write_all(file_content.as_bytes()).unwrap();
|
||||
tmpfile
|
||||
}
|
||||
}
|
||||
260
src/config/themes.rs
Normal file
260
src/config/themes.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
//! ## Themes
|
||||
//!
|
||||
//! `themes` is the module which provides the themes configurations and the serializers
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* termscp - Copyright (c) 2021 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
// locals
|
||||
use crate::utils::fmt::fmt_color;
|
||||
use crate::utils::parser::parse_color;
|
||||
// ext
|
||||
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use tuirealm::tui::style::Color;
|
||||
|
||||
/// ### Theme
|
||||
///
|
||||
/// Theme contains all the colors lookup table for termscp
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Theme {
|
||||
// -- auth
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_address: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_bookmarks: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_password: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_port: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_protocol: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_recents: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub auth_username: Color,
|
||||
// -- misc
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_error_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_input_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_keys: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_quit_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_save_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub misc_warn_dialog: Color,
|
||||
// -- transfer
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_local_explorer_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_local_explorer_foreground: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_local_explorer_highlighted: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_log_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_log_window: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_progress_bar: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_remote_explorer_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_remote_explorer_foreground: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_remote_explorer_highlighted: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_status_hidden: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_status_sorting: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
pub transfer_status_sync_browsing: Color,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
auth_address: Color::Yellow,
|
||||
auth_bookmarks: Color::LightGreen,
|
||||
auth_password: Color::LightBlue,
|
||||
auth_port: Color::LightCyan,
|
||||
auth_protocol: Color::LightGreen,
|
||||
auth_recents: Color::LightBlue,
|
||||
auth_username: Color::LightMagenta,
|
||||
misc_error_dialog: Color::Red,
|
||||
misc_input_dialog: Color::Reset,
|
||||
misc_keys: Color::Cyan,
|
||||
misc_quit_dialog: Color::Yellow,
|
||||
misc_save_dialog: Color::LightCyan,
|
||||
misc_warn_dialog: Color::LightRed,
|
||||
transfer_local_explorer_background: Color::Reset,
|
||||
transfer_local_explorer_foreground: Color::Reset,
|
||||
transfer_local_explorer_highlighted: Color::Yellow,
|
||||
transfer_log_background: Color::Reset,
|
||||
transfer_log_window: Color::LightGreen,
|
||||
transfer_progress_bar: Color::Green,
|
||||
transfer_remote_explorer_background: Color::Reset,
|
||||
transfer_remote_explorer_foreground: Color::Reset,
|
||||
transfer_remote_explorer_highlighted: Color::LightBlue,
|
||||
transfer_status_hidden: Color::LightBlue,
|
||||
transfer_status_sorting: Color::LightYellow,
|
||||
transfer_status_sync_browsing: Color::LightGreen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- deserializer
|
||||
|
||||
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: &str = Deserialize::deserialize(deserializer)?;
|
||||
// Parse color
|
||||
match parse_color(s) {
|
||||
None => Err(DeError::custom("Invalid color")),
|
||||
Some(color) => Ok(color),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_color<S>(color: &Color, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
// Convert color to string
|
||||
let s: String = fmt_color(color);
|
||||
serializer.serialize_str(s.as_str())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn test_config_themes_default() {
|
||||
let theme: Theme = Theme::default();
|
||||
assert_eq!(theme.auth_address, Color::Yellow);
|
||||
assert_eq!(theme.auth_bookmarks, Color::LightGreen);
|
||||
assert_eq!(theme.auth_password, Color::LightBlue);
|
||||
assert_eq!(theme.auth_port, Color::LightCyan);
|
||||
assert_eq!(theme.auth_protocol, Color::LightGreen);
|
||||
assert_eq!(theme.auth_recents, Color::LightBlue);
|
||||
assert_eq!(theme.auth_username, Color::LightMagenta);
|
||||
assert_eq!(theme.misc_error_dialog, Color::Red);
|
||||
assert_eq!(theme.misc_input_dialog, Color::Reset);
|
||||
assert_eq!(theme.misc_keys, Color::Cyan);
|
||||
assert_eq!(theme.misc_quit_dialog, Color::Yellow);
|
||||
assert_eq!(theme.misc_save_dialog, Color::LightCyan);
|
||||
assert_eq!(theme.misc_warn_dialog, Color::LightRed);
|
||||
assert_eq!(theme.transfer_local_explorer_background, Color::Reset);
|
||||
assert_eq!(theme.transfer_local_explorer_foreground, Color::Reset);
|
||||
assert_eq!(theme.transfer_local_explorer_highlighted, Color::Yellow);
|
||||
assert_eq!(theme.transfer_log_background, Color::Reset);
|
||||
assert_eq!(theme.transfer_log_window, Color::LightGreen);
|
||||
assert_eq!(theme.transfer_progress_bar, Color::Green);
|
||||
assert_eq!(theme.transfer_remote_explorer_background, Color::Reset);
|
||||
assert_eq!(theme.transfer_remote_explorer_foreground, Color::Reset);
|
||||
assert_eq!(theme.transfer_remote_explorer_highlighted, Color::LightBlue);
|
||||
assert_eq!(theme.transfer_status_hidden, Color::LightBlue);
|
||||
assert_eq!(theme.transfer_status_sorting, Color::LightYellow);
|
||||
assert_eq!(theme.transfer_status_sync_browsing, Color::LightGreen);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user