diff options
author | Pierre Peltier <8608160+Peltoche@users.noreply.github.com> | 2020-04-09 15:12:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-09 15:12:17 +0200 |
commit | a7ea265a6b6176940a2b4715470f691d848138b0 (patch) | |
tree | da04e92ebabaa5e3e061e10a1ffb77dea7414691 /src | |
parent | f444ef754474e898a3427d440e5cc40f8e9c99a7 (diff) | |
parent | 6353fe92303fbe1f20b41d6a7d79afad524b6333 (diff) |
Merge branch 'master' into add-the-WIP-actionadd-the-WIP-action
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 55 | ||||
-rw-r--r-- | src/color.rs | 13 | ||||
-rw-r--r-- | src/core.rs | 14 | ||||
-rw-r--r-- | src/display.rs | 1 | ||||
-rw-r--r-- | src/flags.rs | 19 | ||||
-rw-r--r-- | src/icon.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 47 | ||||
-rw-r--r-- | src/meta/date.rs | 3 | ||||
-rw-r--r-- | src/meta/inode.rs | 62 | ||||
-rw-r--r-- | src/meta/mod.rs | 18 |
10 files changed, 215 insertions, 23 deletions
@@ -134,12 +134,11 @@ pub fn build() -> App<'static, 'static> { .arg( Arg::with_name("date") .long("date") - .possible_value("date") - .possible_value("relative") + .validator(validate_date_argument) .default_value("date") .multiple(true) .number_of_values(1) - .help("How to display date"), + .help("How to display date [possible values: date, relative, +date-time-format]"), ) .arg( Arg::with_name("timesort") @@ -179,7 +178,15 @@ pub fn build() -> App<'static, 'static> { .multiple(true) .number_of_values(1) .require_delimiter(true) - .possible_values(&["permission", "user", "group", "size", "date", "name"]) + .possible_values(&[ + "permission", + "user", + "group", + "size", + "date", + "name", + "inode", + ]) .help("Specify the blocks that will be displayed and in what order"), ) .arg( @@ -203,4 +210,44 @@ pub fn build() -> App<'static, 'static> { .default_value("") .help("Do not display files/directories with names matching the glob pattern(s)"), ) + .arg( + Arg::with_name("inode") + .short("i") + .long("inode") + .multiple(true) + .help("Display the index number of each file"), + ) +} + +fn validate_date_argument(arg: String) -> Result<(), String> { + if arg.starts_with('+') { + validate_time_format(&arg).map_err(|err| err.to_string()) + } else if &arg == "date" || &arg == "relative" { + Result::Ok(()) + } else { + Result::Err("possible values: date, relative, +date-time-format".to_owned()) + } +} + +fn validate_time_format(formatter: &str) -> Result<(), time::ParseError> { + let mut chars = formatter.chars(); + loop { + match chars.next() { + Some('%') => match chars.next() { + Some('A') | Some('a') | Some('B') | Some('b') | Some('C') | Some('c') + | Some('D') | Some('d') | Some('e') | Some('F') | Some('f') | Some('G') + | Some('g') | Some('H') | Some('h') | Some('I') | Some('j') | Some('k') + | Some('l') | Some('M') | Some('m') | Some('n') | Some('P') | Some('p') + | Some('R') | Some('r') | Some('S') | Some('s') | Some('T') | Some('t') + | Some('U') | Some('u') | Some('V') | Some('v') | Some('W') | Some('w') + | Some('X') | Some('x') | Some('Y') | Some('y') | Some('Z') | Some('z') + | Some('+') | Some('%') => (), + Some(c) => return Err(time::ParseError::InvalidFormatSpecifier(c)), + None => return Err(time::ParseError::MissingFormatConverter), + }, + None => break, + _ => continue, + } + } + Ok(()) } diff --git a/src/color.rs b/src/color.rs index b18a857..943a1d0 100644 --- a/src/color.rs +++ b/src/color.rs @@ -42,6 +42,11 @@ pub enum Elem { FileLarge, FileMedium, FileSmall, + + /// INode + INode { + valid: bool, + }, } impl Elem { @@ -158,6 +163,10 @@ impl Colors { Elem::BlockDevice => Some("bd"), Elem::CharDevice => Some("cd"), Elem::BrokenSymLink => Some("or"), + Elem::INode { valid } => match valid { + true => Some("so"), + false => Some("no"), + }, _ => None, }; @@ -233,6 +242,10 @@ impl Colors { m.insert(Elem::FileMedium, Colour::Fixed(216)); // LightSalmon1 m.insert(Elem::FileLarge, Colour::Fixed(172)); // Orange3 + // INode + m.insert(Elem::INode { valid: true }, Colour::Fixed(13)); // Pink + m.insert(Elem::INode { valid: false }, Colour::Fixed(245)); // Grey + m } } diff --git a/src/core.rs b/src/core.rs index 60c73e7..16aaf8f 100644 --- a/src/core.rs +++ b/src/core.rs @@ -3,8 +3,7 @@ use crate::display; use crate::flags::{Display, Flags, IconTheme, Layout, WhenFlag}; use crate::icon::{self, Icons}; use crate::meta::Meta; -use crate::sort; -use std::fs; +use crate::{print_error, print_output, sort}; use std::path::PathBuf; #[cfg(not(target_os = "windows"))] @@ -83,15 +82,10 @@ impl Core { }; for path in paths { - if let Err(err) = fs::canonicalize(&path) { - eprintln!("cannot access '{}': {}", path.display(), err); - continue; - } - let mut meta = match Meta::from_path(&path) { Ok(meta) => meta, Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); continue; } }; @@ -107,7 +101,7 @@ impl Core { meta_list.push(meta); } Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); continue; } }; @@ -140,6 +134,6 @@ impl Core { display::grid(&metas, &self.flags, &self.colors, &self.icons) }; - print!("{}", output); + print_output!("{}", output); } } diff --git a/src/display.rs b/src/display.rs index 93179d8..6cde985 100644 --- a/src/display.rs +++ b/src/display.rs @@ -221,6 +221,7 @@ fn get_output<'a>( let mut strings: Vec<ANSIString> = Vec::new(); for block in flags.blocks.iter() { match block { + Block::INode => strings.push(meta.inode.render(colors)), Block::Permission => { let s: &[ColoredString] = &[ meta.file_type.render(colors), diff --git a/src/flags.rs b/src/flags.rs index 848b158..1d3b860 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -15,6 +15,7 @@ pub struct Flags { pub color: WhenFlag, pub icon: WhenFlag, pub icon_theme: IconTheme, + pub inode: bool, pub recursion_depth: usize, pub blocks: Vec<Block>, pub no_symlink: bool, @@ -32,6 +33,8 @@ impl Flags { let date_inputs: Vec<&str> = matches.values_of("date").unwrap().collect(); let dir_order_inputs: Vec<&str> = matches.values_of("group-dirs").unwrap().collect(); let ignore_globs_inputs: Vec<&str> = matches.values_of("ignore-glob").unwrap().collect(); + // inode set layout to oneline and blocks to inode,name + let inode = matches.is_present("inode"); let blocks_inputs: Vec<&str> = if let Some(blocks) = matches.values_of("blocks") { blocks.collect() } else { @@ -67,6 +70,7 @@ impl Flags { } else if matches.is_present("long") || matches.is_present("oneline") || blocks_inputs.len() > 1 + || inode { Layout::OneLine } else { @@ -94,7 +98,7 @@ impl Flags { None => usize::max_value(), }; - let blocks: Vec<Block> = if !blocks_inputs.is_empty() { + let mut blocks: Vec<Block> = if !blocks_inputs.is_empty() { blocks_inputs.into_iter().map(Block::from).collect() } else if matches.is_present("long") { vec![ @@ -109,6 +113,11 @@ impl Flags { vec![Block::Name] }; + // Add inode as first column if with inode flag + if inode && !blocks.contains(&Block::INode) { + blocks.insert(0, Block::INode); + } + let mut ignore_globs_builder = GlobSetBuilder::new(); for pattern in ignore_globs_inputs { let glob = match Glob::new(pattern) { @@ -168,6 +177,7 @@ impl Flags { }, no_symlink: matches.is_present("no-symlink"), total_size: matches.is_present("total-size"), + inode, }) } } @@ -192,6 +202,7 @@ impl Default for Flags { no_symlink: false, total_size: false, ignore_globs: GlobSet::empty(), + inode: false, } } } @@ -205,6 +216,7 @@ pub enum Block { SizeValue, Date, Name, + INode, } impl<'a> From<&'a str> for Block { fn from(block: &'a str) -> Self { @@ -217,6 +229,7 @@ impl<'a> From<&'a str> for Block { "size_value" => Block::SizeValue, "date" => Block::Date, "name" => Block::Name, + "inode" => Block::INode, _ => panic!("invalid \"time\" flag: {}", block), } } @@ -248,10 +261,11 @@ impl<'a> From<&'a str> for SizeFlag { } } -#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum DateFlag { Date, Relative, + Formatted(String), } impl<'a> From<&'a str> for DateFlag { @@ -259,6 +273,7 @@ impl<'a> From<&'a str> for DateFlag { match time { "date" => DateFlag::Date, "relative" => DateFlag::Relative, + time if time.starts_with('+') => DateFlag::Formatted(time[1..].to_owned()), _ => panic!("invalid \"time\" flag: {}", time), } } diff --git a/src/icon.rs b/src/icon.rs index dc6bdd6..43e71da 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -181,6 +181,10 @@ impl Icons { m.insert("conf", "\u{e615}"); // "" m.insert("cp", "\u{e61d}"); // "" m.insert("cpp", "\u{e61d}"); // "" + m.insert("cs", "\u{f81a}"); // "" + m.insert("cshtml", "\u{f1fa}"); // "" + m.insert("csproj", "\u{f81a}"); // "" + m.insert("csx", "\u{f81a}"); // "" m.insert("csh", "\u{f489}"); // "" m.insert("css", "\u{e749}"); // "" m.insert("csv", "\u{f1c3}"); // "" @@ -278,6 +282,7 @@ impl Icons { m.insert("r", "\u{f25d}"); // "" m.insert("rakefile", "\u{e21e}"); // "" m.insert("rar", "\u{f410}"); // "" + m.insert("razor", "\u{f1fa}"); // "" m.insert("rb", "\u{e21e}"); // "" m.insert("rdata", "\u{f25d}"); // "" m.insert("rdb", "\u{e76d}"); // "" @@ -299,6 +304,7 @@ impl Icons { m.insert("sh", "\u{f489}"); // "" m.insert("shell", "\u{f489}"); // "" m.insert("slim", "\u{e73b}"); // "" + m.insert("sln", "\u{e70c}"); // "" m.insert("sql", "\u{f1c0}"); // "" m.insert("sqlite3", "\u{e7c4}"); // "" m.insert("styl", "\u{e600}"); // "" diff --git a/src/main.rs b/src/main.rs index 87440b6..e1537ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,9 +38,56 @@ use crate::core::Core; use crate::flags::Flags; use std::path::PathBuf; +/// Macro used to avoid panicking when the lsd method is used with a pipe and +/// stderr close before our program. +#[macro_export] +macro_rules! print_error { + ($($arg:tt)*) => { + use std::io::Write; + + let stderr = std::io::stderr(); + + { + let mut handle = stderr.lock(); + // We can write on stderr, so we simply ignore the error and don't print + // and stop with success. + let res = handle.write_all(std::format!($($arg)*).as_bytes()); + if res.is_err() { + std::process::exit(0); + } + } + }; +} + +/// Macro used to avoid panicking when the lsd method is used with a pipe and +/// stdout close before our program. +#[macro_export] +macro_rules! print_output { + ($($arg:tt)*) => { + use std::io::Write; + + let stderr = std::io::stdout(); + + + { + let mut handle = stderr.lock(); + // We can write on stdout, so we simply ignore the error and don't print + // and stop with success. + let res = handle.write_all(std::format!($($arg)*).as_bytes()); + if res.is_err() { + std::process::exit(0); + } + } + }; +} + fn main() { let matches = app::build().get_matches_from(wild::args_os()); + // input translate glob FILE without single quote into real names + // for example: + // * to all files matched + // '*' remain as '*' let inputs = matches .values_of("FILE") .expect("failed to retrieve cli value") diff --git a/src/meta/date.rs b/src/meta/date.rs index a909983..fcfb8c9 100644 --- a/src/meta/date.rs +++ b/src/meta/date.rs @@ -41,9 +41,10 @@ impl Date { } pub fn date_string(&self, flags: &Flags) -> String { - match flags.date { + match &flags.date { DateFlag::Date => self.0.ctime().to_string(), DateFlag::Relative => format!("{}", HumanTime::from(self.0 - time::now())), + DateFlag::Formatted(format) => self.0.to_local().strftime(&format).unwrap().to_string(), } } } diff --git a/src/meta/inode.rs b/src/meta/inode.rs new file mode 100644 index 0000000..6849947 --- /dev/null +++ b/src/meta/inode.rs @@ -0,0 +1,62 @@ +use crate::color::{ColoredString, Colors, Elem}; +use std::fs::Metadata; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct INode { + index: Option<u64>, +} + +impl<'a> From<&'a Metadata> for INode { + #[cfg(unix)] + fn from(meta: &Metadata) -> Self { + use std::os::unix::fs::MetadataExt; + + let index = meta.ino(); + + Self { index: Some(index) } + } + + #[cfg(windows)] + fn from(_: &Metadata) -> Self { + Self { index: None } + } +} + +impl INode { + pub fn render(&self, colors: &Colors) -> ColoredString { + match self.index { + Some(i) => colors.colorize(i.to_string(), &Elem::INode { valid: true }), + None => colors.colorize(String::from("-"), &Elem::INode { valid: false }), + } + } +} + +#[cfg(test)] +#[cfg(unix)] +mod tests { + use super::INode; + use std::env; + use std::io; + use std::path::Path; + use std::process::{Command, ExitStatus}; + + fn cross_platform_touch(path: &Path) -> io::Result<ExitStatus> { + Command::new("touch").arg(&path).status() + } + + #[test] + fn test_inode_no_zero() { + let mut file_path = env::temp_dir(); + file_path.push("inode.tmp"); + + let success = cross_platform_touch(&file_path).unwrap().success(); + assert!(success, "failed to exec touch"); + + let inode = INode::from(&file_path.metadata().unwrap()); + + #[cfg(unix)] + assert!(inode.index.is_some()); + #[cfg(windows)] + assert!(inode.index.is_none()); + } +} diff --git a/src/meta/mod.rs b/src/meta/mod.rs index 4f51191..b29a57a 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -1,6 +1,7 @@ mod date; mod filetype; mod indicator; +mod inode; mod name; mod owner; mod permissions; @@ -13,6 +14,7 @@ mod windows_utils; pub use self::date::Date; pub use self::filetype::FileType; pub use self::indicator::Indicator; +pub use self::inode::INode; pub use self::name::Name; pub use self::owner::Owner; pub use self::permissions::Permissions; @@ -20,6 +22,7 @@ pub use self::size::Size; pub use self::symlink::SymLink; pub use crate::flags::Display; pub use crate::icon::Icons; +use crate::print_error; use std::fs; use std::fs::read_link; @@ -39,6 +42,7 @@ pub struct Meta { pub size: Size, pub symlink: SymLink, pub indicator: Indicator, + pub inode: INode, pub content: Option<Vec<Meta>>, } @@ -65,7 +69,7 @@ impl Meta { let entries = match self.path.read_dir() { Ok(entries) => entries, Err(err) => { - eprintln!("cannot access '{}': {}", self.path.display(), err); + print_error!("lsd: {}: {}\n", self.path.display(), err); return Ok(None); } }; @@ -112,7 +116,7 @@ impl Meta { let mut entry_meta = match Self::from_path(&path) { Ok(res) => res, Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); continue; } }; @@ -120,7 +124,7 @@ impl Meta { match entry_meta.recurse_into(depth - 1, display, ignore_globs) { Ok(content) => entry_meta.content = content, Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); continue; } }; @@ -158,7 +162,7 @@ impl Meta { let metadata = match metadata { Ok(meta) => meta, Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); return 0; } }; @@ -171,7 +175,7 @@ impl Meta { let entries = match path.read_dir() { Ok(entries) => entries, Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); return size; } }; @@ -179,7 +183,7 @@ impl Meta { let path = match entry { Ok(entry) => entry.path(), Err(err) => { - eprintln!("cannot access '{}': {}", path.display(), err); + print_error!("lsd: {}: {}\n", path.display(), err); continue; } }; @@ -210,8 +214,10 @@ impl Meta { let file_type = FileType::new(&metadata, &permissions); let name = Name::new(&path, file_type); + let inode = INode::from(&metadata); Ok(Self { + inode, path: path.to_path_buf(), symlink: SymLink::from(path.as_path()), size: Size::from(&metadata), |