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
|
- SFTP
|
||||||
- SCP
|
- SCP
|
||||||
- FTP and FTPS
|
- 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
|
- 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
|
- Bookmarks and recent connections can be saved to access quickly to your favourite hosts
|
||||||
- Supports text editors to view and edit text files
|
- Supports text editors to view and edit text files
|
||||||
- Supports both SFTP/SCP authentication through SSH keys and username/password
|
- Supports both SFTP/SCP authentication through SSH keys and username/password
|
||||||
- User customization directly from the user interface
|
- Customizations:
|
||||||
- Compatible with Windows, Linux, BSD and MacOS
|
- Custom file explorer format
|
||||||
|
- Customizable text editor
|
||||||
|
- Customizable file sorting
|
||||||
|
- SSH key storage
|
||||||
- Written in Rust
|
- Written in Rust
|
||||||
- Easy to extend with new file transfers protocols
|
- Easy to extend with new file transfers protocols
|
||||||
- Developed keeping an eye on performance
|
- 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) file_sorting: FileSorting, // File sorting criteria
|
||||||
pub(crate) group_dirs: Option<GroupDirs>, // If Some, defines how to group directories
|
pub(crate) group_dirs: Option<GroupDirs>, // If Some, defines how to group directories
|
||||||
pub(crate) opts: ExplorerOpts, // Explorer options
|
pub(crate) opts: ExplorerOpts, // Explorer options
|
||||||
|
pub(crate) fmt: Formatter, // FsEntry formatter
|
||||||
index: usize, // Selected file
|
index: usize, // Selected file
|
||||||
files: Vec<FsEntry>, // Files in directory
|
files: Vec<FsEntry>, // Files in directory
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,7 @@ impl Default for FileExplorer {
|
|||||||
file_sorting: FileSorting::ByName,
|
file_sorting: FileSorting::ByName,
|
||||||
group_dirs: None,
|
group_dirs: None,
|
||||||
opts: ExplorerOpts::empty(),
|
opts: ExplorerOpts::empty(),
|
||||||
|
fmt: Formatter::default(),
|
||||||
index: 0,
|
index: 0,
|
||||||
files: Vec::new(),
|
files: Vec::new(),
|
||||||
}
|
}
|
||||||
@@ -168,6 +170,15 @@ impl FileExplorer {
|
|||||||
self.files.get(self.index)
|
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
|
// Sorting
|
||||||
|
|
||||||
/// ### sort_by
|
/// ### sort_by
|
||||||
@@ -241,7 +252,8 @@ impl FileExplorer {
|
|||||||
///
|
///
|
||||||
/// Sort files by creation time; the newest comes first
|
/// Sort files by creation time; the newest comes first
|
||||||
fn sort_files_by_creation_time(&mut self) {
|
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
|
/// ### sort_files_by_size
|
||||||
@@ -509,6 +521,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::fs::{FsDirectory, FsFile};
|
use crate::fs::{FsDirectory, FsFile};
|
||||||
|
use crate::utils::fmt::fmt_time;
|
||||||
|
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
@@ -856,6 +869,43 @@ mod tests {
|
|||||||
assert_eq!(explorer.files.get(7).unwrap().get_name(), "README.md");
|
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]
|
#[test]
|
||||||
fn test_fs_explorer_to_string_from_str_traits() {
|
fn test_fs_explorer_to_string_from_str_traits() {
|
||||||
// File Sorting
|
// File Sorting
|
||||||
|
|||||||
270
src/fs/mod.rs
270
src/fs/mod.rs
@@ -25,19 +25,9 @@
|
|||||||
|
|
||||||
// Mod
|
// Mod
|
||||||
pub mod explorer;
|
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
|
// Ext
|
||||||
use bytesize::ByteSize;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
||||||
use users::get_user_by_uid;
|
|
||||||
|
|
||||||
/// ## FsEntry
|
/// ## 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
@@ -512,194 +432,4 @@ mod tests {
|
|||||||
PathBuf::from("/home/cvisintin/projects")
|
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
|
let files: Vec<ListItem> = self
|
||||||
.local
|
.local
|
||||||
.iter_files()
|
.iter_files()
|
||||||
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
.map(|entry: &FsEntry| ListItem::new(Span::from(self.local.fmt_file(entry))))
|
||||||
.collect();
|
.collect();
|
||||||
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
||||||
let (fg, bg): (Color, Color) = match self.tab {
|
let (fg, bg): (Color, Color) = match self.tab {
|
||||||
@@ -209,7 +209,7 @@ impl FileTransferActivity {
|
|||||||
let files: Vec<ListItem> = self
|
let files: Vec<ListItem> = self
|
||||||
.remote
|
.remote
|
||||||
.iter_files()
|
.iter_files()
|
||||||
.map(|entry: &FsEntry| ListItem::new(Span::from(format!("{}", entry))))
|
.map(|entry: &FsEntry| ListItem::new(Span::from(self.remote.fmt_file(entry))))
|
||||||
.collect();
|
.collect();
|
||||||
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
// Get colors to use; highlight element inverting fg/bg only when tab is active
|
||||||
let (fg, bg): (Color, Color) = match self.tab {
|
let (fg, bg): (Color, Color) = match self.tab {
|
||||||
|
|||||||
Reference in New Issue
Block a user