mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
Use formatter to fmt fs entries instead of fmt::Display trait
This commit is contained in:
@@ -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
|
||||
|
||||
0
src/fs/explorer/formatter.rs
Executable file → Normal file
0
src/fs/explorer/formatter.rs
Executable file → Normal file
@@ -77,6 +77,7 @@ pub struct FileExplorer {
|
||||
pub(crate) file_sorting: FileSorting, // File sorting criteria
|
||||
pub(crate) group_dirs: Option<GroupDirs>, // 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<FsEntry>, // 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
|
||||
|
||||
270
src/fs/mod.rs
270
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")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ impl FileTransferActivity {
|
||||
let files: Vec<ListItem> = 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<ListItem> = 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 {
|
||||
|
||||
Reference in New Issue
Block a user