From c6209471a266656021747a5ceb580e8f3629ab0a Mon Sep 17 00:00:00 2001 From: sharkdp Date: Thu, 1 Jun 2017 22:46:15 +0200 Subject: [PATCH] Re-structure code, add unit tests, use LS_COLORS --- Cargo.toml | 8 +++ src/{ => bin}/main.rs | 101 +++++--------------------- src/fd.rs | 7 ++ src/lscolors/mod.rs | 161 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 83 deletions(-) rename src/{ => bin}/main.rs (64%) create mode 100644 src/fd.rs create mode 100644 src/lscolors/mod.rs diff --git a/Cargo.toml b/Cargo.toml index a2d7289..1836d12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,14 @@ name = "fd" version = "0.3.0" authors = ["David Peter "] +[[bin]] +name = "fd" +path = "src/bin/main.rs" + +[lib] +name = "fd" +path = "src/fd.rs" + [dependencies] ansi_term = "0.9" getopts = "0.2" diff --git a/src/main.rs b/src/bin/main.rs similarity index 64% rename from src/main.rs rename to src/bin/main.rs index e989f81..e0d0271 100644 --- a/src/main.rs +++ b/src/bin/main.rs @@ -3,24 +3,22 @@ extern crate getopts; extern crate isatty; extern crate regex; extern crate ignore; +extern crate fd; -use std::collections::HashMap; use std::env; use std::error::Error; use std::ffi::OsStr; -use std::fs::File; -use std::io::{Write, BufReader,BufRead}; +use std::io::Write; use std::path::{Path, Component}; use std::process; -use ansi_term::{Style, Colour}; +use ansi_term::Style; use getopts::Options; use isatty::stdout_isatty; use regex::{Regex, RegexBuilder}; use ignore::WalkBuilder; -/// Maps file extensions to ANSI colors / styles. -type ExtensionStyles = HashMap; +use fd::lscolors::LsColors; /// Configuration options for *fd*. struct FdOptions { @@ -31,7 +29,7 @@ struct FdOptions { follow_links: bool, max_depth: Option, colored: bool, - extension_styles: Option + ls_colors: LsColors } /// Print a search result to the console. @@ -61,21 +59,16 @@ fn print_entry(path_root: &Path, path_entry: &Path, config: &FdOptions) { if component_path.symlink_metadata() .map(|md| md.file_type().is_symlink()) .unwrap_or(false) { - Colour::Cyan.normal() + config.ls_colors.symlink } else if component_path.is_dir() { - Colour::Blue.bold() + config.ls_colors.directory } else { // Loop up file extension - if let Some(ref ext_styles) = config.extension_styles { - component_path.extension() - .and_then(|e| e.to_str()) - .and_then(|e| ext_styles.get(e)) - .map(|r| r.clone()) - .unwrap_or(Style::new()) - } - else { - Style::new() - } + component_path.extension() + .and_then(|e| e.to_str()) + .and_then(|e| config.ls_colors.extensions.get(e)) + .map(|r| r.clone()) + .unwrap_or(Style::new()) }; print!("{}", style.paint(comp_str.to_str().unwrap())); @@ -132,61 +125,6 @@ fn error(message: &str) -> ! { process::exit(1); } -/// Parse `dircolors` file. -fn parse_dircolors(path: &Path) -> std::io::Result { - let file = File::open(path)?; - let mut ext_styles = HashMap::new(); - - let pattern_ansi_256 = - Regex::new(r"^\.([A-Za-z0-9]+)\s*(?:00;)?38;5;([0-9]+)\b") - .unwrap(); - - let pattern_ansi = - Regex::new(r"^\.([A-Za-z0-9]+)\s*(?:([0-9]+);)?([0-9][0-9])\b") - .unwrap(); - - for line in BufReader::new(file).lines() { - let line_s = line.unwrap(); - if let Some(caps) = pattern_ansi_256.captures(line_s.as_str()) { - if let Some(ext) = caps.get(1).map(|m| m.as_str()) { - let fg = caps.get(2) - .map(|m| m.as_str()) - .and_then(|n| u8::from_str_radix(n, 10).ok()) - .unwrap_or(7); // white - ext_styles.insert(String::from(ext), - Colour::Fixed(fg).normal()); - } - } else if let Some(caps) = pattern_ansi.captures(line_s.as_str()) { - if let Some(ext) = caps.get(1).map(|m| m.as_str()) { - let color_s = caps.get(3) - .map_or("", |m| m.as_str()); - let color = match color_s { - "31" => Colour::Red, - "32" => Colour::Green, - "33" => Colour::Yellow, - "34" => Colour::Blue, - "35" => Colour::Purple, - "36" => Colour::Cyan, - _ => Colour::White - }; - let style_s = caps.get(2) - .map_or("", |m| m.as_str()); - let style = match style_s { - "1" => color.bold(), - "01" => color.bold(), - "3" => color.italic(), - "03" => color.italic(), - "4" => color.underline(), - "04" => color.underline(), - _ => color.normal() - }; - ext_styles.insert(String::from(ext), style); - } - } - } - Ok(ext_styles) -} - fn main() { let args: Vec = env::args().collect(); @@ -231,14 +169,11 @@ fn main() { let colored_output = !matches.opt_present("no-color") && stdout_isatty(); - let ext_styles = - if colored_output { - env::home_dir() - .map(|h| h.join(".dir_colors")) - .and_then(|path| parse_dircolors(&path).ok()) - } else { - None - }; + let ls_colors = + env::var("LS_COLORS") + .ok() + .map(|val| LsColors::from_string(&val)) + .unwrap_or(LsColors::default()); let config = FdOptions { // The search will be case-sensitive if the command line flag is set or @@ -253,7 +188,7 @@ fn main() { matches.opt_str("max-depth") .and_then(|ds| usize::from_str_radix(&ds, 10).ok()), colored: colored_output, - extension_styles: ext_styles + ls_colors: ls_colors }; match RegexBuilder::new(pattern) diff --git a/src/fd.rs b/src/fd.rs new file mode 100644 index 0000000..e86d801 --- /dev/null +++ b/src/fd.rs @@ -0,0 +1,7 @@ +extern crate ansi_term; +extern crate getopts; +extern crate isatty; +extern crate regex; +extern crate ignore; + +pub mod lscolors; diff --git a/src/lscolors/mod.rs b/src/lscolors/mod.rs new file mode 100644 index 0000000..b189ac9 --- /dev/null +++ b/src/lscolors/mod.rs @@ -0,0 +1,161 @@ +use std::collections::HashMap; + +use ansi_term::{Style, Colour}; + +/// Maps file extensions to ANSI colors / styles. +pub type ExtensionStyles = HashMap; + +/// Maps filenames to ANSI colors / styles. +pub type FilenameStyles = HashMap; + +const LS_CODES: &'static [&'static str] = + &["no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi", + "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", + "ec", "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", + "cl"]; + +#[derive(Debug, PartialEq)] +pub struct LsColors { + pub directory: Style, + pub symlink: Style, + pub extensions: ExtensionStyles, + pub filenames: FilenameStyles, +} + +impl LsColors { + /// Get a default LsColors structure. + pub fn default() -> LsColors { + LsColors { + directory: Colour::Blue.bold(), + symlink: Colour::Cyan.normal(), + extensions: HashMap::new(), + filenames: HashMap::new() + } + } + + /// Parse a single ANSI style like `38;5;10`. + fn parse_style(code: &str) -> Option