summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPierre Peltier <8608160+Peltoche@users.noreply.github.com>2020-04-09 15:12:17 +0200
committerGitHub <noreply@github.com>2020-04-09 15:12:17 +0200
commita7ea265a6b6176940a2b4715470f691d848138b0 (patch)
treeda04e92ebabaa5e3e061e10a1ffb77dea7414691 /src
parentf444ef754474e898a3427d440e5cc40f8e9c99a7 (diff)
parent6353fe92303fbe1f20b41d6a7d79afad524b6333 (diff)
Merge branch 'master' into add-the-WIP-actionadd-the-WIP-action
Diffstat (limited to 'src')
-rw-r--r--src/app.rs55
-rw-r--r--src/color.rs13
-rw-r--r--src/core.rs14
-rw-r--r--src/display.rs1
-rw-r--r--src/flags.rs19
-rw-r--r--src/icon.rs6
-rw-r--r--src/main.rs47
-rw-r--r--src/meta/date.rs3
-rw-r--r--src/meta/inode.rs62
-rw-r--r--src/meta/mod.rs18
10 files changed, 215 insertions, 23 deletions
diff --git a/src/app.rs b/src/app.rs
index 6f7ac19..af906c8 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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),