From 3dc1689e6581f437ad4ef83ae46e57591585574b Mon Sep 17 00:00:00 2001 From: Peltoche Date: Mon, 12 Nov 2018 13:33:47 +0100 Subject: [PATCH] Add the first functionalities --- .gitignore | 2 + Cargo.lock | 150 +++++++++++++++++++++++++++++++++++ Cargo.toml | 10 +++ src/formatter.rs | 194 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 40 ++++++++++ src/path_lister.rs | 80 +++++++++++++++++++ src/presenter.rs | 48 +++++++++++ 7 files changed, 524 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/formatter.rs create mode 100644 src/main.rs create mode 100644 src/path_lister.rs create mode 100644 src/presenter.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9bc9b76 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,150 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lsd" +version = "0.1.0" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" +"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b697fda --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +authors = ["Peltoche "] +name = "lsd" +version = "0.1.0" + +[dependencies] +ansi_term = "0.11.0" +clap = "2.32.0" +lazy_static = "1.2.0" +time = "0.1.40" diff --git a/src/formatter.rs b/src/formatter.rs new file mode 100644 index 0000000..51d70ec --- /dev/null +++ b/src/formatter.rs @@ -0,0 +1,194 @@ +use ansi_term::Colour; +use path_lister::Path; +use std::collections::HashMap; +use std::os::unix::fs::PermissionsExt; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use time::Timespec; + +const HOURE: u64 = 3600; // 1 HOURE == 3600 seconds +const DAY: u64 = HOURE * 24; // 1 DAY == 25 HOURE + +#[derive(Hash, Debug, Eq, PartialEq, Copy, Clone)] +pub enum PathKind { + UnrecognizedFile, + RecognizedFile, + Dir, +} + +lazy_static! { + pub static ref PathKindColor: HashMap = { + let mut m = HashMap::new(); + + m.insert(PathKind::UnrecognizedFile, (0xFF, 0xFF, 0x04)); // gold + m.insert(PathKind::RecognizedFile, (0x04, 0xFF, 0x04)); // limon + m.insert(PathKind::Dir, (0x00, 0xAF, 0xFF)); // dodgerblue + + m + }; +} + +#[derive(Hash, Debug, Eq, PartialEq, Copy, Clone)] +pub enum LastTimeModified { + DayOld, + HourOld, + Older, +} + +lazy_static! { + pub static ref LastTimeModifiedColor: HashMap = { + let mut m = HashMap::new(); + + m.insert(LastTimeModified::HourOld, (0x2C, 0xFF, 0x2C)); + m.insert(LastTimeModified::DayOld, (0x1C, 0xFF, 0xB7)); + m.insert(LastTimeModified::Older, (0x63, 0xB1, 0x8A)); + + m + }; +} + +#[allow(dead_code)] +#[derive(Hash, Debug, Eq, PartialEq, Copy, Clone)] +pub enum Elem { + /// Link + DeadLink, + Link, + + /// File Size + FileLarge, + FileMedium, + FileSmall, + + /// Random + Report, + User, + Tree, + Empty, + Error, + Normal, + + /// Git + Addition, + Modification, + Deletion, + Untracked, + Unchanged, +} + +pub struct Formatter {} + +impl Formatter { + pub fn new() -> Formatter { + Formatter {} + } + + pub fn format_path(&self, content: &str, path: &Path) -> String { + let color = match path.metadata.is_dir() { + true => PathKindColor[&PathKind::Dir], + false => PathKindColor[&PathKind::UnrecognizedFile], + }; + + Colour::RGB(color.0, color.1, color.2) + .paint(content) + .to_string() + } + + pub fn format_date(&self, path: &Path) -> String { + let modified_time = path.metadata.modified().unwrap(); + + let now = SystemTime::now(); + + let color; + if modified_time > now - Duration::new(HOURE, 0) { + color = LastTimeModifiedColor[&LastTimeModified::HourOld]; + } else if modified_time > now - Duration::new(DAY, 0) { + color = LastTimeModifiedColor[&LastTimeModified::DayOld]; + } else { + color = LastTimeModifiedColor[&LastTimeModified::Older]; + } + + let modified_time_since_epoch = modified_time.duration_since(UNIX_EPOCH).unwrap(); + let time = time::at(Timespec::new( + modified_time_since_epoch.as_secs() as i64, + modified_time_since_epoch.subsec_nanos() as i32, + )); + + Colour::RGB(color.0, color.1, color.2) + .paint(time.ctime().to_string()) + .to_string() + } + + pub fn format_permissions(&self, path: &Path) -> String { + let mut res = String::with_capacity(10); + + let mode = path.metadata.permissions().mode(); + + let read_perm = Colour::RGB(0x5f, 0xD7, 0x5F) + .paint(String::from("r")) + .to_string(); + let write_perm = Colour::RGB(0xD7, 0xD7, 0x87) + .paint(String::from("w")) + .to_string(); + let exec_perm = Colour::RGB(0xCD, 0x3A, 0x3A) + .paint(String::from("x")) + .to_string(); + let no_access = Colour::RGB(0xD7, 0x89, 0x89) + .paint(String::from("-")) + .to_string(); + + // User Read Permisssions + match mode & 0o400 { + 0 => res = res + no_access.as_str(), + _ => res = res + read_perm.as_str(), + } + + // User Write Permisssions + match mode & 0o200 { + 0 => res = res + no_access.as_str(), + _ => res = res + write_perm.as_str(), + } + + // User Exec Permisssions + match mode & 0o100 { + 0 => res = res + no_access.as_str(), + _ => res = res + exec_perm.as_str(), + } + + // Group Read Permisssions + match mode & 0o040 { + 0 => res = res + no_access.as_str(), + _ => res = res + read_perm.as_str(), + } + + // Group Write Permisssions + match mode & 0o020 { + 0 => res = res + no_access.as_str(), + _ => res = res + write_perm.as_str(), + } + + // Group Exec Permisssions + match mode & 0o010 { + 0 => res = res + no_access.as_str(), + _ => res = res + exec_perm.as_str(), + } + + // Other Read Permisssions + match mode & 0o040 { + 0 => res = res + no_access.as_str(), + _ => res = res + read_perm.as_str(), + } + + // Other Write Permisssions + match mode & 0o020 { + 0 => res = res + no_access.as_str(), + _ => res = res + write_perm.as_str(), + } + + // Other Exec Permisssions + match mode & 0o010 { + 0 => res = res + no_access.as_str(), + _ => res = res + exec_perm.as_str(), + } + + res.to_string() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9a3a84c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,40 @@ +extern crate clap; +#[macro_use] +extern crate lazy_static; +extern crate ansi_term; +extern crate time; + +mod formatter; +mod path_lister; +mod presenter; + +use clap::{App, Arg}; +use path_lister::PathLister; +use presenter::Presenter; + +pub struct Options { + pub display_long: bool, + pub display_all: bool, +} + +fn main() { + let matches = App::new("lsd") + .about("A ls comment with a lot of pretty colors and some other stuff.") + .arg(Arg::with_name("FILE").multiple(true).default_value(".")) + .arg(Arg::with_name("long").short("l")) + .arg(Arg::with_name("all").short("a")) + .get_matches(); + + let options = Options { + display_long: matches.is_present("long"), + display_all: matches.is_present("all"), + }; + + let inputs: Vec<&str> = matches.values_of("FILE").unwrap().collect(); + + let path_lister = PathLister::new(&options); + + let presenter = Presenter::new(&options); + + presenter.print(path_lister.list_paths_to_print(inputs)); +} diff --git a/src/path_lister.rs b/src/path_lister.rs new file mode 100644 index 0000000..86c8cd5 --- /dev/null +++ b/src/path_lister.rs @@ -0,0 +1,80 @@ +use std::fs::Metadata; +use std::io::ErrorKind; +use std::path::{self, PathBuf}; +use Options; + +pub struct Path { + pub path: PathBuf, + pub metadata: Metadata, +} + +pub struct PathLister<'a> { + options: &'a Options, +} + +impl<'a> PathLister<'a> { + pub fn new(options: &'a Options) -> PathLister<'a> { + PathLister { options: options } + } + + pub fn list_paths_to_print(&self, inputs: Vec<&'a str>) -> Vec { + let mut res = Vec::new(); + + for input in inputs.iter() { + let path = path::Path::new(input); + + let res_meta = path.metadata(); + + if let Err(err) = res_meta { + match err.kind() { + ErrorKind::NotFound => println!("Specified path \"{}\" doesn't exists.", input), + ErrorKind::PermissionDenied => { + println!("Cannot open \"{}\": Permission denied", input) + } + _ => println!("Cannot open \"{}\": {}", input, err), + } + + continue; + } + + if let Ok(meta) = res_meta { + let file_type = meta.file_type(); + + if file_type.is_file() { + self.add_path_to_list(&mut res, path.to_path_buf(), meta); + continue; + } + + if file_type.is_dir() { + self.read_paths_from_dir(&mut res, path.to_path_buf()); + continue; + } + + if file_type.is_symlink() {} + } + } + + res + } + + fn read_paths_from_dir(&self, path_list: &mut Vec, path: PathBuf) { + for entry in path.read_dir().expect("read_dir call failed") { + if let Ok(entry) = entry { + self.add_path_to_list(path_list, entry.path(), entry.metadata().unwrap()); + } + } + } + + fn add_path_to_list(&self, path_list: &mut Vec, path: PathBuf, meta: Metadata) { + // Skip the hidden files if the 'display_all' option is not set. + if path.file_name().unwrap().to_str().unwrap().starts_with(".") && !self.options.display_all + { + return; + } + + path_list.push(Path { + path: path.to_path_buf(), + metadata: meta, + }); + } +} diff --git a/src/presenter.rs b/src/presenter.rs new file mode 100644 index 0000000..248ee19 --- /dev/null +++ b/src/presenter.rs @@ -0,0 +1,48 @@ +use formatter::*; +use path_lister::Path; +use Options; + +pub struct Presenter<'a> { + formatter: Formatter, + options: &'a Options, +} + +impl<'a> Presenter<'a> { + pub fn new(options: &'a Options) -> Presenter<'a> { + Presenter { + options: options, + formatter: Formatter::new(), + } + } + + pub fn print(&self, paths: Vec) { + if self.options.display_long { + self.print_long(&paths); + return; + } + + self.print_simple(&paths) + } + + fn print_long(&self, paths: &Vec) { + for path in paths { + print!( + " {} {} {}\n", + self.formatter.format_permissions(path), + self.formatter.format_date(path), + self.formatter + .format_path(path.path.file_name().unwrap().to_str().unwrap(), &path) + ); + } + } + + fn print_simple(&self, paths: &Vec) { + for path in paths { + print!( + "{}\n", + self.formatter + .format_path(path.path.file_name().unwrap().to_str().unwrap(), &path) + ); + } + } +}