From da0d5231bf0c2d3c21f037d316eedc70b7dc2380 Mon Sep 17 00:00:00 2001 From: veeso Date: Sat, 23 Jan 2021 15:51:46 +0100 Subject: [PATCH] Use formatter to fmt fs entries instead of fmt::Display trait --- README.md | 8 +- src/fs/explorer/formatter.rs | 0 src/fs/explorer/mod.rs | 52 +++- src/fs/mod.rs | 270 ------------------ .../filetransfer_activity/layout.rs | 4 +- 5 files changed, 59 insertions(+), 275 deletions(-) mode change 100755 => 100644 src/fs/explorer/formatter.rs diff --git a/README.md b/README.md index 7f329f6..db87795 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,16 @@ It happens quite often to me, when using SCP at work to forget the path of a fil - SFTP - SCP - FTP and FTPS +- Compatible with Windows, Linux, BSD and MacOS - Practical user interface to explore and operate on the remote and on the local machine file system - Bookmarks and recent connections can be saved to access quickly to your favourite hosts - Supports text editors to view and edit text files - Supports both SFTP/SCP authentication through SSH keys and username/password -- User customization directly from the user interface -- Compatible with Windows, Linux, BSD and MacOS +- Customizations: + - Custom file explorer format + - Customizable text editor + - Customizable file sorting +- SSH key storage - Written in Rust - Easy to extend with new file transfers protocols - Developed keeping an eye on performance diff --git a/src/fs/explorer/formatter.rs b/src/fs/explorer/formatter.rs old mode 100755 new mode 100644 diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index e58d612..ee7b0af 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -77,6 +77,7 @@ pub struct FileExplorer { pub(crate) file_sorting: FileSorting, // File sorting criteria pub(crate) group_dirs: Option, // If Some, defines how to group directories pub(crate) opts: ExplorerOpts, // Explorer options + pub(crate) fmt: Formatter, // FsEntry formatter index: usize, // Selected file files: Vec, // Files in directory } @@ -90,6 +91,7 @@ impl Default for FileExplorer { file_sorting: FileSorting::ByName, group_dirs: None, opts: ExplorerOpts::empty(), + fmt: Formatter::default(), index: 0, files: Vec::new(), } @@ -168,6 +170,15 @@ impl FileExplorer { self.files.get(self.index) } + // Formatting + + /// ### fmt_file + /// + /// Format a file entry + pub fn fmt_file(&self, entry: &FsEntry) -> String { + self.fmt.fmt(entry) + } + // Sorting /// ### sort_by @@ -241,7 +252,8 @@ impl FileExplorer { /// /// Sort files by creation time; the newest comes first fn sort_files_by_creation_time(&mut self) { - self.files.sort_by_key(|b: &FsEntry| Reverse(b.get_creation_time())); + self.files + .sort_by_key(|b: &FsEntry| Reverse(b.get_creation_time())); } /// ### sort_files_by_size @@ -509,6 +521,7 @@ mod tests { use super::*; use crate::fs::{FsDirectory, FsFile}; + use crate::utils::fmt::fmt_time; use std::thread::sleep; use std::time::{Duration, SystemTime}; @@ -856,6 +869,43 @@ mod tests { assert_eq!(explorer.files.get(7).unwrap().get_name(), "README.md"); } + #[test] + fn test_fs_explorer_fmt() { + let explorer: FileExplorer = FileExplorer::default(); + // Create fs entry + let t: SystemTime = SystemTime::now(); + let entry: FsEntry = FsEntry::File(FsFile { + name: String::from("bar.txt"), + abs_path: PathBuf::from("/bar.txt"), + last_change_time: t, + last_access_time: t, + creation_time: t, + size: 8192, + readonly: false, + ftype: Some(String::from("txt")), + symlink: None, // UNIX only + user: Some(0), // UNIX only + group: Some(0), // UNIX only + unix_pex: Some((6, 4, 4)), // UNIX only + }); + #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] + assert_eq!( + explorer.fmt_file(&entry), + format!( + "bar.txt -rw-r--r-- root 8.2 KB {}", + fmt_time(t, "%b %d %Y %H:%M") + ) + ); + #[cfg(target_os = "windows")] + assert_eq!( + explorer.fmt_file(&entry), + format!( + "bar.txt -rw-r--r-- 0 8.2 KB {}", + fmt_time(t, "%b %d %Y %H:%M") + ) + ); + } + #[test] fn test_fs_explorer_to_string_from_str_traits() { // File Sorting diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 96f14e8..61645be 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -25,19 +25,9 @@ // Mod pub mod explorer; - -// Deps -extern crate bytesize; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] -extern crate users; -// Locals -use crate::utils::fmt::{fmt_pex, fmt_time}; // Ext -use bytesize::ByteSize; use std::path::PathBuf; use std::time::SystemTime; -#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] -use users::get_user_by_uid; /// ## FsEntry /// @@ -236,76 +226,6 @@ impl FsEntry { } } -impl std::fmt::Display for FsEntry { - /// ### fmt_ls - /// - /// Format File Entry as `ls` does - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - // Create mode string - let mut mode: String = String::with_capacity(10); - let file_type: char = match self.is_symlink() { - true => 'l', - false => match self.is_dir() { - true => 'd', - false => '-', - }, - }; - mode.push(file_type); - match self.get_unix_pex() { - None => mode.push_str("?????????"), - Some((owner, group, others)) => mode.push_str(fmt_pex(owner, group, others).as_str()), - } - // Get username - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - let username: String = match self.get_user() { - Some(uid) => match get_user_by_uid(uid) { - Some(user) => user.name().to_string_lossy().to_string(), - None => uid.to_string(), - }, - None => 0.to_string(), - }; - #[cfg(target_os = "windows")] - let username: String = match self.get_user() { - Some(uid) => uid.to_string(), - None => 0.to_string(), - }; - // Get group - /* - let group: String = match self.get_group() { - Some(gid) => match get_group_by_gid(gid) { - Some(group) => group.name().to_string_lossy().to_string(), - None => gid.to_string(), - }, - None => String::from("0"), - }; - */ - // Get byte size - let size: ByteSize = ByteSize(self.get_size() as u64); - // Get date - let datetime: String = fmt_time(self.get_last_change_time(), "%b %d %Y %H:%M"); - // Set file name (or elide if too long) - let name: &str = self.get_name(); - let last_idx: usize = match self.is_dir() { - // NOTE: For directories is 19, since we push '/' to name - true => 19, - false => 20, - }; - let mut name: String = match name.len() >= 24 { - false => name.to_string(), - true => format!("{}...", &name[0..last_idx]), - }; - // If is directory, append '/' - if self.is_dir() { - name.push('/'); - } - write!( - f, - "{:24}\t{:12}\t{:12}\t{:10}\t{:17}", - name, mode, username, size, datetime - ) - } -} - #[cfg(test)] mod tests { @@ -512,194 +432,4 @@ mod tests { PathBuf::from("/home/cvisintin/projects") ); } - - #[test] - fn test_fs_fmt_file() { - let t: SystemTime = SystemTime::now(); - let entry: FsEntry = FsEntry::File(FsFile { - name: String::from("bar.txt"), - abs_path: PathBuf::from("/bar.txt"), - last_change_time: t, - last_access_time: t, - creation_time: t, - size: 8192, - readonly: false, - ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-rw-r--r-- \troot \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-rw-r--r-- \t0 \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - // Elide name - let entry: FsEntry = FsEntry::File(FsFile { - name: String::from("piroparoporoperoperupupu.txt"), - abs_path: PathBuf::from("/bar.txt"), - last_change_time: t, - last_access_time: t, - creation_time: t, - size: 8192, - readonly: false, - ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((6, 4, 4)), // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "piroparoporoperoperu... \t-rw-r--r-- \troot \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "piroparoporoperoperu... \t-rw-r--r-- \t0 \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - // No pex - let entry: FsEntry = FsEntry::File(FsFile { - name: String::from("bar.txt"), - abs_path: PathBuf::from("/bar.txt"), - last_change_time: t, - last_access_time: t, - creation_time: t, - size: 8192, - readonly: false, - ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: None, // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-????????? \troot \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-????????? \t0 \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - // No user - let entry: FsEntry = FsEntry::File(FsFile { - name: String::from("bar.txt"), - abs_path: PathBuf::from("/bar.txt"), - last_change_time: t, - last_access_time: t, - creation_time: t, - size: 8192, - readonly: false, - ftype: Some(String::from("txt")), - symlink: None, // UNIX only - user: None, // UNIX only - group: Some(0), // UNIX only - unix_pex: None, // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-????????? \t0 \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "bar.txt \t-????????? \t0 \t8.2 KB \t{}", - fmt_time(t, "%b %d %Y %H:%M") - ) - ); - } - - #[test] - fn test_fs_fmt_dir() { - let t_now: SystemTime = SystemTime::now(); - let entry: FsEntry = FsEntry::Directory(FsDirectory { - name: String::from("projects"), - abs_path: PathBuf::from("/home/cvisintin/projects"), - last_change_time: t_now, - last_access_time: t_now, - creation_time: t_now, - readonly: false, - symlink: None, // UNIX only - user: Some(0), // UNIX only - group: Some(0), // UNIX only - unix_pex: Some((7, 5, 5)), // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "projects/ \tdrwxr-xr-x \troot \t4.1 KB \t{}", - fmt_time(t_now, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "projects/ \tdrwxr-xr-x \t0 \t4.1 KB \t{}", - fmt_time(t_now, "%b %d %Y %H:%M") - ) - ); - // No pex, no user - let entry: FsEntry = FsEntry::Directory(FsDirectory { - name: String::from("projects"), - abs_path: PathBuf::from("/home/cvisintin/projects"), - last_change_time: t_now, - last_access_time: t_now, - creation_time: t_now, - readonly: false, - symlink: None, // UNIX only - user: None, // UNIX only - group: Some(0), // UNIX only - unix_pex: None, // UNIX only - }); - #[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))] - assert_eq!( - format!("{}", entry), - format!( - "projects/ \td????????? \t0 \t4.1 KB \t{}", - fmt_time(t_now, "%b %d %Y %H:%M") - ) - ); - #[cfg(target_os = "windows")] - assert_eq!( - format!("{}", entry), - format!( - "projects/ \td????????? \t0 \t4.1 KB \t{}", - fmt_time(t_now, "%b %d %Y %H:%M") - ) - ); - } } diff --git a/src/ui/activities/filetransfer_activity/layout.rs b/src/ui/activities/filetransfer_activity/layout.rs index 641a2f6..ae9b3d2 100644 --- a/src/ui/activities/filetransfer_activity/layout.rs +++ b/src/ui/activities/filetransfer_activity/layout.rs @@ -169,7 +169,7 @@ impl FileTransferActivity { let files: Vec = self .local .iter_files() - .map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry)))) + .map(|entry: &FsEntry| ListItem::new(Span::from(self.local.fmt_file(entry)))) .collect(); // Get colors to use; highlight element inverting fg/bg only when tab is active let (fg, bg): (Color, Color) = match self.tab { @@ -209,7 +209,7 @@ impl FileTransferActivity { let files: Vec = self .remote .iter_files() - .map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry)))) + .map(|entry: &FsEntry| ListItem::new(Span::from(self.remote.fmt_file(entry)))) .collect(); // Get colors to use; highlight element inverting fg/bg only when tab is active let (fg, bg): (Color, Color) = match self.tab {