From 718d6112c857ba9310d24635888ff7f46d933f48 Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sat, 21 Nov 2020 23:40:24 +0800 Subject: [PATCH 01/19] config: :hammer: using target enum and vec string for config options Signed-off-by: zwPapEr --- src/flags/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/flags/layout.rs b/src/flags/layout.rs index 68d6a453d..74c176143 100644 --- a/src/flags/layout.rs +++ b/src/flags/layout.rs @@ -10,7 +10,7 @@ use serde::Deserialize; /// The flag showing which output layout to print. #[derive(Clone, Debug, Copy, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[serde(rename_all = "lowercase")] pub enum Layout { Grid, Tree, From 6ef0db1ee642fdad7e979c8d308113374a5a17dc Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sat, 28 Nov 2020 23:19:50 +0800 Subject: [PATCH 02/19] log: :doc: unique error output and update comment to fit config --- src/flags/icons.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/flags/icons.rs b/src/flags/icons.rs index 78d0fc6de..2b62913da 100644 --- a/src/flags/icons.rs +++ b/src/flags/icons.rs @@ -100,6 +100,20 @@ pub enum IconTheme { Fancy, } +impl IconTheme { + /// Get a value from a string. + fn from_str(value: &str) -> Option { + match value { + "fancy" => Some(Self::Fancy), + "unicode" => Some(Self::Unicode), + _ => { + print_error!("Bad icons.theme config, {}", &value); + None + } + } + } +} + impl Configurable for IconTheme { /// Get a potential `IconTheme` variant from [ArgMatches]. /// From 83b3d75097cb6bc0b998496746e43dda6bed7b4a Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Wed, 25 Nov 2020 15:52:54 +0800 Subject: [PATCH 03/19] color: :sparkles: add parse theme file Signed-off-by: zwPapEr --- Cargo.lock | 1 + Cargo.toml | 2 +- src/color.rs | 170 +++++++++++++++++----------------------- src/color/theme.rs | 188 +++++++++++++++++++++++++++++++++++++++++++++ src/config_file.rs | 56 ++++++++++---- src/core.rs | 10 ++- src/flags.rs | 2 +- src/flags/color.rs | 68 +++++++++++++++- 8 files changed, 374 insertions(+), 123 deletions(-) create mode 100644 src/color/theme.rs diff --git a/Cargo.lock b/Cargo.lock index b18797588..fcdae76b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ + "serde", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index eb33d3643..62302d038 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ clap = "2.33.*" version_check = "0.9.*" [dependencies] -ansi_term = "0.12.*" +ansi_term = { version = "0.12.*", features = ["derive_serde_style"] } dirs = "3.0.*" libc = "0.2.*" human-sort = "0.2.2" diff --git a/src/color.rs b/src/color.rs index 5670e334f..6867bf426 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,6 +1,11 @@ +mod theme; + +use theme::Theme; + +use crate::flags::color::ThemeOption; + use ansi_term::{ANSIString, Colour, Style}; use lscolors::{Indicator, LsColors}; -use std::collections::HashMap; use std::path::Path; #[allow(dead_code)] @@ -60,37 +65,81 @@ impl Elem { pub fn has_suid(&self) -> bool { matches!(self, Elem::Dir { uid: true } | Elem::File { uid: true, .. }) } + pub fn get_color(&self, theme: &theme::Theme) -> Colour { + match self { + Elem::File { + exec: true, + uid: true, + } => theme.file_type.file.exec_uid, + Elem::File { + exec: false, + uid: true, + } => theme.file_type.file.uid_no_exec, + Elem::File { + exec: true, + uid: false, + } => theme.file_type.file.exec_no_uid, + Elem::File { + exec: false, + uid: false, + } => theme.file_type.file.no_exec_no_uid, + Elem::SymLink => theme.file_type.symlink.default, + Elem::BrokenSymLink => theme.file_type.symlink.broken, + Elem::Dir { uid: true } => theme.file_type.dir.uid, + Elem::Dir { uid: false } => theme.file_type.dir.no_uid, + Elem::Pipe => theme.file_type.pipe, + Elem::BlockDevice => theme.file_type.block_device, + Elem::CharDevice => theme.file_type.char_device, + Elem::Socket => theme.file_type.socket, + Elem::Special => theme.file_type.special, + + Elem::Read => theme.permissions.read, + Elem::Write => theme.permissions.write, + Elem::Exec => theme.permissions.exec, + Elem::ExecSticky => theme.permissions.exec_sticky, + Elem::NoAccess => theme.permissions.no_access, + + Elem::DayOld => theme.modified.day_old, + Elem::HourOld => theme.modified.hour_old, + Elem::Older => theme.modified.older, + + Elem::User => theme.user, + Elem::Group => theme.group, + + Elem::NonFile => theme.size.none, + Elem::FileLarge => theme.size.large, + Elem::FileMedium => theme.size.medium, + Elem::FileSmall => theme.size.small, + + Elem::INode { valid: false } => theme.inode.valid, + Elem::INode { valid: true } => theme.inode.invalid, + } + } } pub type ColoredString<'a> = ANSIString<'a>; -#[allow(dead_code)] -#[derive(Debug, Copy, Clone)] -pub enum Theme { - NoColor, - Default, - NoLscolors, -} - pub struct Colors { - colors: Option>, + theme: Option, lscolors: Option, } impl Colors { - pub fn new(theme: Theme) -> Self { - let colors = match theme { - Theme::NoColor => None, - Theme::Default => Some(Self::get_light_theme_colour_map()), - Theme::NoLscolors => Some(Self::get_light_theme_colour_map()), + pub fn new(t: ThemeOption) -> Self { + let theme = match t { + ThemeOption::NoColor => None, + ThemeOption::Default => Some(Theme::default_dark()), + ThemeOption::NoLscolors => Some(Theme::default_dark()), + ThemeOption::Custom(ref file) => { + Some(Theme::from_path(file).unwrap_or_else(Theme::default_dark)) + } }; - let lscolors = match theme { - Theme::NoColor => None, - Theme::Default => Some(LsColors::from_env().unwrap_or_default()), - Theme::NoLscolors => None, + let lscolors = match t { + ThemeOption::Default => Some(LsColors::from_env().unwrap_or_default()), + _ => None, }; - Self { colors, lscolors } + Self { theme, lscolors } } pub fn colorize<'a>(&self, input: String, elem: &Elem) -> ColoredString<'a> { @@ -135,8 +184,8 @@ impl Colors { } fn style_default(&self, elem: &Elem) -> Style { - if let Some(ref colors) = self.colors { - let style_fg = Style::default().fg(colors[elem]); + if let Some(t) = &self.theme { + let style_fg = Style::default().fg(elem.get_color(&t)); if elem.has_suid() { style_fg.on(Colour::Fixed(124)) // Red3 } else { @@ -183,81 +232,4 @@ impl Colors { None => None, } } - - // You can find the table for each color, code, and display at: - // - //https://jonasjacek.github.io/colors/ - fn get_light_theme_colour_map() -> HashMap { - let mut m = HashMap::new(); - // User / Group - m.insert(Elem::User, Colour::Fixed(230)); // Cornsilk1 - m.insert(Elem::Group, Colour::Fixed(187)); // LightYellow3 - - // Permissions - m.insert(Elem::Read, Colour::Green); - m.insert(Elem::Write, Colour::Yellow); - m.insert(Elem::Exec, Colour::Red); - m.insert(Elem::ExecSticky, Colour::Purple); - m.insert(Elem::NoAccess, Colour::Fixed(245)); // Grey - - // File Types - m.insert( - Elem::File { - exec: false, - uid: false, - }, - Colour::Fixed(184), - ); // Yellow3 - m.insert( - Elem::File { - exec: false, - uid: true, - }, - Colour::Fixed(184), - ); // Yellow3 - m.insert( - Elem::File { - exec: true, - uid: false, - }, - Colour::Fixed(40), - ); // Green3 - m.insert( - Elem::File { - exec: true, - uid: true, - }, - Colour::Fixed(40), - ); // Green3 - m.insert(Elem::Dir { uid: true }, Colour::Fixed(33)); // DodgerBlue1 - m.insert(Elem::Dir { uid: false }, Colour::Fixed(33)); // DodgerBlue1 - m.insert(Elem::Pipe, Colour::Fixed(44)); // DarkTurquoise - m.insert(Elem::SymLink, Colour::Fixed(44)); // DarkTurquoise - m.insert(Elem::BrokenSymLink, Colour::Fixed(124)); // Red3 - m.insert(Elem::BlockDevice, Colour::Fixed(44)); // DarkTurquoise - m.insert(Elem::CharDevice, Colour::Fixed(172)); // Orange3 - m.insert(Elem::Socket, Colour::Fixed(44)); // DarkTurquoise - m.insert(Elem::Special, Colour::Fixed(44)); // DarkTurquoise - - // Last Time Modified - m.insert(Elem::HourOld, Colour::Fixed(40)); // Green3 - m.insert(Elem::DayOld, Colour::Fixed(42)); // SpringGreen2 - m.insert(Elem::Older, Colour::Fixed(36)); // DarkCyan - - // Last Time Modified - m.insert(Elem::NonFile, Colour::Fixed(245)); // Grey - m.insert(Elem::FileSmall, Colour::Fixed(229)); // Wheat1 - 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.insert(Elem::Links { valid: true }, Colour::Fixed(13)); - m.insert(Elem::Links { valid: false }, Colour::Fixed(245)); - - // TODO add this after we can use file to configure theme - // m.insert(Elem::TreeEdge, Colour::Fixed(44)); // DarkTurquoise - m - } } diff --git a/src/color/theme.rs b/src/color/theme.rs new file mode 100644 index 000000000..ee27f3ef4 --- /dev/null +++ b/src/color/theme.rs @@ -0,0 +1,188 @@ +///! This module provides methods to create theme from files and operations related to +///! this. +use crate::config_file; +use crate::print_error; + +use ansi_term::Colour; +use serde::Deserialize; +use std::fs; +use std::path::Path; + +/// A struct holding the theme configuration +/// Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.avg +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Theme { + pub user: Colour, + pub group: Colour, + pub permissions: Permissions, + pub file_type: FileType, + pub modified: Modified, + pub size: Size, + pub inode: INode, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Permissions { + pub read: Colour, + pub write: Colour, + pub exec: Colour, + pub exec_sticky: Colour, + pub no_access: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct FileType { + pub file: File, + pub dir: Dir, + pub pipe: Colour, + pub symlink: Symlink, + pub block_device: Colour, + pub char_device: Colour, + pub socket: Colour, + pub special: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct File { + pub exec_uid: Colour, + pub uid_no_exec: Colour, + pub exec_no_uid: Colour, + pub no_exec_no_uid: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Dir { + pub uid: Colour, + pub no_uid: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Symlink { + pub default: Colour, + pub broken: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Modified { + pub hour_old: Colour, + pub day_old: Colour, + pub older: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct Size { + pub none: Colour, + pub small: Colour, + pub medium: Colour, + pub large: Colour, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct INode { + pub valid: Colour, + pub invalid: Colour, +} + +impl Theme { + /// This read theme from file, + /// use the file path if it is absolute + /// prefix the config_file dir to it if it is not + pub fn from_path(file: &str) -> Option { + let real = if let Some(path) = config_file::Config::expand_home(file) { + path + } else { + print_error!("Bad theme file path: {}.", &file); + return None; + }; + let path = if Path::new(&real).is_absolute() { + real + } else { + config_file::Config::config_file_path().unwrap().join(real) + }; + match fs::read(&path) { + Ok(f) => Self::with_yaml(&String::from_utf8_lossy(&f)), + Err(e) => { + print_error!("bad theme file: {}, {}\n", path.to_string_lossy(), e); + None + } + } + } + + /// This constructs a Theme struct with a passed [Yaml] str. + fn with_yaml(yaml: &str) -> Option { + match serde_yaml::from_str::(yaml) { + Ok(c) => Some(c), + Err(e) => { + print_error!("theme file format error, {}\n\n", e); + None + } + } + } + pub fn default_dark() -> Self { + Theme { + user: Colour::Fixed(230), // Cornsilk1 + group: Colour::Fixed(187), // LightYellow3 + permissions: Permissions { + read: Colour::Green, + write: Colour::Yellow, + exec: Colour::Red, + exec_sticky: Colour::Purple, + no_access: Colour::Fixed(245), // Grey + }, + file_type: FileType { + file: File { + exec_uid: Colour::Fixed(40), // Green3 + uid_no_exec: Colour::Fixed(184), // Yellow3 + exec_no_uid: Colour::Fixed(40), // Green3 + no_exec_no_uid: Colour::Fixed(184), // Yellow3 + }, + dir: Dir { + uid: Colour::Fixed(33), // DodgerBlue1 + no_uid: Colour::Fixed(33), // DodgerBlue1 + }, + pipe: Colour::Fixed(44), // DarkTurquoise + symlink: Symlink { + default: Colour::Fixed(44), // DarkTurquoise + broken: Colour::Fixed(124), // Red3 + }, + block_device: Colour::Fixed(44), // DarkTurquoise + char_device: Colour::Fixed(172), // Orange3 + socket: Colour::Fixed(44), // DarkTurquoise + special: Colour::Fixed(44), // DarkTurquoise + }, + modified: Modified { + hour_old: Colour::Fixed(40), // Green3 + day_old: Colour::Fixed(42), // SpringGreen2 + older: Colour::Fixed(36), // DarkCyan + }, + size: Size { + none: Colour::Fixed(245), // Grey + small: Colour::Fixed(229), // Wheat1 + medium: Colour::Fixed(216), // LightSalmon1 + large: Colour::Fixed(172), // Orange3 + }, + inode: INode { + valid: Colour::Fixed(13), // Pink + invalid: Colour::Fixed(245), // Grey + }, + } + } +} diff --git a/src/config_file.rs b/src/config_file.rs index c7c1b3d5c..721dfaa99 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -1,14 +1,14 @@ -///! This module provides methods to handle the program's config files and operations related to -///! this. -use crate::flags::color::ColorOption; use crate::flags::display::Display; use crate::flags::icons::{IconOption, IconTheme}; use crate::flags::layout::Layout; use crate::flags::size::SizeFlag; use crate::flags::sorting::{DirGrouping, SortColumn}; +use crate::flags::{ColorOption, ThemeOption}; +///! This module provides methods to handle the program's config files and operations related to +///! this. use crate::print_error; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use serde::Deserialize; @@ -44,7 +44,8 @@ pub struct Config { #[derive(Eq, PartialEq, Debug, Deserialize)] pub struct Color { - pub when: ColorOption, + pub when: Option, + pub theme: Option, } #[derive(Eq, PartialEq, Debug, Deserialize)] @@ -120,13 +121,11 @@ impl Config { /// This provides the path for a configuration file, according to the XDG_BASE_DIRS specification. /// return None if error like PermissionDenied #[cfg(not(windows))] - fn config_file_path() -> Option { + pub fn config_file_path() -> Option { use xdg::BaseDirectories; match BaseDirectories::with_prefix(CONF_DIR) { Ok(p) => { - if let Ok(p) = p.place_config_file([CONF_FILE_NAME, YAML_LONG_EXT].join(".")) { - return Some(p); - } + return Some(p.get_config_home()); } Err(e) => print_error!("Can not open config file: {}.", e), } @@ -136,22 +135,47 @@ impl Config { /// This provides the path for a configuration file, inside the %APPDATA% directory. /// return None if error like PermissionDenied #[cfg(windows)] - fn config_file_path() -> Option { + pub fn config_file_path() -> Option { if let Some(p) = dirs::config_dir() { - return Some( - p.join(CONF_DIR) - .join(CONF_FILE_NAME) - .with_extension(YAML_LONG_EXT), - ); + return Some(p.join(CONF_DIR)); } None } + + /// This expand the `~` in path to HOME dir + /// returns the origin one if no `~` found; + /// returns None if error happened when getting home dir + /// + /// Implementing this to reuse the `dirs` dependency, avoid adding new one + pub fn expand_home>(path: P) -> Option { + let p = path.as_ref(); + if !p.starts_with("~") { + return Some(p.to_path_buf()); + } + if p == Path::new("~") { + return dirs::home_dir(); + } + dirs::home_dir().map(|mut h| { + if h == Path::new("/") { + // Corner case: `h` root directory; + // don't prepend extra `/`, just drop the tilde. + p.strip_prefix("~").unwrap().to_path_buf() + } else { + h.push(p.strip_prefix("~/").unwrap()); + h + } + }) + } } impl Default for Config { fn default() -> Self { if let Some(p) = Self::config_file_path() { - if let Some(c) = Self::from_file(p.to_string_lossy().to_string()) { + if let Some(c) = Self::from_file( + p.join([CONF_FILE_NAME, YAML_LONG_EXT].join(".")) + .to_string_lossy() + .to_string(), + ) { return c; } } diff --git a/src/core.rs b/src/core.rs index 9a11e87e6..ab876f3cf 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,6 +1,8 @@ -use crate::color::{self, Colors}; +use crate::color::Colors; use crate::display; -use crate::flags::{ColorOption, Display, Flags, IconOption, IconTheme, Layout, SortOrder}; +use crate::flags::{ + ColorOption, Display, Flags, IconOption, IconTheme, Layout, SortOrder, ThemeOption, +}; use crate::icon::{self, Icons}; use crate::meta::Meta; use crate::{print_error, print_output, sort}; @@ -41,8 +43,8 @@ impl Core { let mut inner_flags = flags.clone(); let color_theme = match (tty_available && console_color_ok, flags.color.when) { - (_, ColorOption::Never) | (false, ColorOption::Auto) => color::Theme::NoColor, - _ => color::Theme::Default, + (_, ColorOption::Never) | (false, ColorOption::Auto) => ThemeOption::NoColor, + _ => flags.color.theme.clone(), }; let icon_theme = match (tty_available, flags.icons.when, flags.icons.theme) { diff --git a/src/flags.rs b/src/flags.rs index 676a84fae..10ea6ab76 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -17,7 +17,7 @@ pub mod total_size; pub use blocks::Block; pub use blocks::Blocks; pub use color::Color; -pub use color::ColorOption; +pub use color::{ColorOption, ThemeOption}; pub use date::DateFlag; pub use dereference::Dereference; pub use display::Display; diff --git a/src/flags/color.rs b/src/flags/color.rs index 95c66e6b2..8944252c8 100644 --- a/src/flags/color.rs +++ b/src/flags/color.rs @@ -7,14 +7,17 @@ use crate::config_file::Config; use crate::print_error; use clap::ArgMatches; +use serde::de::{self, Deserializer, Visitor}; use serde::Deserialize; use std::env; +use std::fmt; /// A collection of flags on how to use colors. -#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default)] pub struct Color { /// When to use color. pub when: ColorOption, + pub theme: ThemeOption, } impl Color { @@ -23,7 +26,68 @@ impl Color { /// The [ColorOption] is configured with their respective [Configurable] implementation. pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self { let when = ColorOption::configure_from(matches, config); - Self { when } + let theme = ThemeOption::from_config(config); + Self { when, theme } + } +} + +/// ThemeOption could be one of the following: +/// Custom(*.yaml): use the YAML theme file as theme file +/// if error happened, use the default theme +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum ThemeOption { + NoColor, + Default, + NoLscolors, + Custom(String), +} + +impl ThemeOption { + fn from_config(config: &Config) -> ThemeOption { + if let Some(c) = &config.color { + if let Some(t) = &c.theme { + return t.clone(); + } + } + + ThemeOption::default() + } +} + +impl Default for ThemeOption { + fn default() -> Self { + ThemeOption::Default + } +} + +impl<'de> de::Deserialize<'de> for ThemeOption { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ThemeOptionVisitor; + + impl<'de> Visitor<'de> for ThemeOptionVisitor { + type Value = ThemeOption; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("`no-color`, `default`, `no-lscolors` or ") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "no-color" => Ok(ThemeOption::NoColor), + "default" => Ok(ThemeOption::Default), + "no-lscolors" => Ok(ThemeOption::NoLscolors), + str => Ok(ThemeOption::Custom(str.to_string())), + } + } + } + + deserializer.deserialize_identifier(ThemeOptionVisitor) } } From 00a6b3df6d8cfa8230a52cf23d51376b4054301a Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sun, 29 Nov 2020 21:14:29 +0800 Subject: [PATCH 04/19] theme/test: :mag: :hammer: update tests to fit theme update Signed-off-by: zwPapEr --- src/color.rs | 2 +- src/config_file.rs | 6 ++++-- src/display.rs | 8 ++++---- src/flags/color.rs | 12 ++++++++---- src/meta/date.rs | 12 ++++++------ src/meta/filetype.rs | 16 ++++++++-------- src/meta/name.rs | 16 ++++++++-------- src/meta/size.rs | 4 ++-- src/meta/symlink.rs | 6 +++--- 9 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/color.rs b/src/color.rs index 6867bf426..1431c9e63 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,7 +2,7 @@ mod theme; use theme::Theme; -use crate::flags::color::ThemeOption; +pub use crate::flags::color::ThemeOption; use ansi_term::{ANSIString, Colour, Style}; use lscolors::{Indicator, LsColors}; diff --git a/src/config_file.rs b/src/config_file.rs index 721dfaa99..cd841f65a 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -210,6 +210,7 @@ color: # When "classic" is set, this is set to "never". # Possible values: never, auto, always when: auto + theme: default # == Date == # This specifies the date format for the date column. The freeform format @@ -310,7 +311,7 @@ impl Config { mod tests { use super::Config; use crate::config_file; - use crate::flags::color::ColorOption; + use crate::flags::color::{ColorOption, ThemeOption}; use crate::flags::icons::{IconOption, IconTheme}; use crate::flags::layout::Layout; use crate::flags::size::SizeFlag; @@ -334,7 +335,8 @@ mod tests { .into() ), color: Some(config_file::Color { - when: ColorOption::Auto, + when: Some(ColorOption::Auto), + theme: Some(ThemeOption::Default) }), date: None, dereference: Some(false), diff --git a/src/display.rs b/src/display.rs index 73c2302f5..7b45e9acc 100644 --- a/src/display.rs +++ b/src/display.rs @@ -386,7 +386,7 @@ mod tests { }, ); let output = name.render( - &Colors::new(color::Theme::NoColor), + &Colors::new(color::ThemeOption::NoColor), &Icons::new(icon::Theme::NoIcon, " ".to_string()), &DisplayOption::FileName, ); @@ -418,7 +418,7 @@ mod tests { ); let output = name .render( - &Colors::new(color::Theme::NoColor), + &Colors::new(color::ThemeOption::NoColor), &Icons::new(icon::Theme::Fancy, " ".to_string()), &DisplayOption::FileName, ) @@ -450,7 +450,7 @@ mod tests { ); let output = name .render( - &Colors::new(color::Theme::NoLscolors), + &Colors::new(color::ThemeOption::NoLscolors), &Icons::new(icon::Theme::NoIcon, " ".to_string()), &DisplayOption::FileName, ) @@ -486,7 +486,7 @@ mod tests { ); let output = name .render( - &Colors::new(color::Theme::NoColor), + &Colors::new(color::ThemeOption::NoColor), &Icons::new(icon::Theme::NoIcon, " ".to_string()), &DisplayOption::FileName, ) diff --git a/src/flags/color.rs b/src/flags/color.rs index 8944252c8..b6c8d2550 100644 --- a/src/flags/color.rs +++ b/src/flags/color.rs @@ -249,7 +249,8 @@ mod test_color_option { fn test_from_config_always() { let mut c = Config::with_none(); c.color = Some(config_file::Color { - when: ColorOption::Always, + when: Some(ColorOption::Always), + theme: None, }); assert_eq!(Some(ColorOption::Always), ColorOption::from_config(&c)); @@ -259,7 +260,8 @@ mod test_color_option { fn test_from_config_auto() { let mut c = Config::with_none(); c.color = Some(config_file::Color { - when: ColorOption::Auto, + when: Some(ColorOption::Auto), + theme: None, }); assert_eq!(Some(ColorOption::Auto), ColorOption::from_config(&c)); } @@ -268,7 +270,8 @@ mod test_color_option { fn test_from_config_never() { let mut c = Config::with_none(); c.color = Some(config_file::Color { - when: ColorOption::Never, + when: Some(ColorOption::Never), + theme: None, }); assert_eq!(Some(ColorOption::Never), ColorOption::from_config(&c)); } @@ -277,7 +280,8 @@ mod test_color_option { fn test_from_config_classic_mode() { let mut c = Config::with_none(); c.color = Some(config_file::Color { - when: ColorOption::Always, + when: Some(ColorOption::Always), + theme: None, }); c.classic = Some(true); assert_eq!(Some(ColorOption::Never), ColorOption::from_config(&c)); diff --git a/src/meta/date.rs b/src/meta/date.rs index 32dc687dd..d8d2d9462 100644 --- a/src/meta/date.rs +++ b/src/meta/date.rs @@ -53,7 +53,7 @@ impl Date { #[cfg(test)] mod test { use super::Date; - use crate::color::{Colors, Theme}; + use crate::color::{Colors, ThemeOption}; use crate::flags::{DateFlag, Flags}; use ansi_term::Colour; use chrono::{DateTime, Duration, Local}; @@ -108,7 +108,7 @@ mod test { .success(); assert!(success, "failed to exec touch"); - let colors = Colors::new(Theme::Default); + let colors = Colors::new(ThemeOption::Default); let date = Date::from(&file_path.metadata().unwrap()); let flags = Flags::default(); @@ -132,7 +132,7 @@ mod test { .success(); assert!(success, "failed to exec touch"); - let colors = Colors::new(Theme::Default); + let colors = Colors::new(ThemeOption::Default); let date = Date::from(&file_path.metadata().unwrap()); let flags = Flags::default(); @@ -156,7 +156,7 @@ mod test { .success(); assert!(success, "failed to exec touch"); - let colors = Colors::new(Theme::Default); + let colors = Colors::new(ThemeOption::Default); let date = Date::from(&file_path.metadata().unwrap()); let flags = Flags::default(); @@ -180,7 +180,7 @@ mod test { .success(); assert!(success, "failed to exec touch"); - let colors = Colors::new(Theme::Default); + let colors = Colors::new(ThemeOption::Default); let date = Date::from(&file_path.metadata().unwrap()); let mut flags = Flags::default(); @@ -205,7 +205,7 @@ mod test { .success(); assert_eq!(true, success, "failed to exec touch"); - let colors = Colors::new(Theme::Default); + let colors = Colors::new(ThemeOption::Default); let date = Date::from(&file_path.metadata().unwrap()); let mut flags = Flags::default(); diff --git a/src/meta/filetype.rs b/src/meta/filetype.rs index 0787d14dd..00530cb4a 100644 --- a/src/meta/filetype.rs +++ b/src/meta/filetype.rs @@ -110,7 +110,7 @@ impl FileType { #[cfg(test)] mod test { use super::FileType; - use crate::color::{Colors, Theme}; + use crate::color::{Colors, ThemeOption}; use crate::meta::Meta; #[cfg(unix)] use crate::meta::Permissions; @@ -135,7 +135,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = file_path.metadata().expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); assert_eq!(Colour::Fixed(184).paint("."), file_type.render(&colors)); @@ -148,7 +148,7 @@ mod test { .expect("failed to get tempdir path"); let metadata = tmp_dir.path().metadata().expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&metadata, None, &meta.permissions); assert_eq!(Colour::Fixed(33).paint("d"), file_type.render(&colors)); @@ -170,7 +170,7 @@ mod test { .symlink_metadata() .expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, Some(&meta), &Permissions::from(&meta)); assert_eq!(Colour::Fixed(44).paint("l"), file_type.render(&colors)); @@ -192,7 +192,7 @@ mod test { .symlink_metadata() .expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, Some(&meta), &Permissions::from(&meta)); assert_eq!(Colour::Fixed(44).paint("l"), file_type.render(&colors)); @@ -213,7 +213,7 @@ mod test { assert_eq!(true, success, "failed to exec mkfifo"); let meta = pipe_path.metadata().expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); assert_eq!(Colour::Fixed(44).paint("|"), file_type.render(&colors)); @@ -238,7 +238,7 @@ mod test { assert_eq!(true, success, "failed to exec mknod"); let meta = char_device_path.metadata().expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); assert_eq!(Colour::Fixed(44).paint("c"), file_type.render(&colors)); @@ -254,7 +254,7 @@ mod test { UnixListener::bind(&socket_path).expect("failed to create the socket"); let meta = socket_path.metadata().expect("failed to get metas"); - let colors = Colors::new(Theme::NoLscolors); + let colors = Colors::new(ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); assert_eq!(Colour::Fixed(44).paint("s"), file_type.render(&colors)); diff --git a/src/meta/name.rs b/src/meta/name.rs index ecb932813..746a311de 100644 --- a/src/meta/name.rs +++ b/src/meta/name.rs @@ -192,7 +192,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = file_path.metadata().expect("failed to get metas"); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); let name = Name::new(&file_path, file_type); @@ -212,7 +212,7 @@ mod test { fs::create_dir(&dir_path).expect("failed to create the dir"); let meta = Meta::from_path(&dir_path, false).unwrap(); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); assert_eq!( Colour::Fixed(33).paint(" directory"), @@ -238,7 +238,7 @@ mod test { .expect("failed to get metas"); let target_meta = symlink_path.metadata().ok(); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, target_meta.as_ref(), &Permissions::from(&meta)); let name = Name::new(&symlink_path, file_type); @@ -266,7 +266,7 @@ mod test { .expect("failed to get metas"); let target_meta = symlink_path.metadata().ok(); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, target_meta.as_ref(), &Permissions::from(&meta)); let name = Name::new(&symlink_path, file_type); @@ -292,7 +292,7 @@ mod test { assert_eq!(true, success, "failed to exec mkfifo"); let meta = pipe_path.metadata().expect("failed to get metas"); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); let name = Name::new(&pipe_path, file_type); @@ -312,7 +312,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = Meta::from_path(&file_path, false).unwrap(); - let colors = Colors::new(color::Theme::NoColor); + let colors = Colors::new(color::ThemeOption::NoColor); assert_eq!( "file.txt", @@ -527,7 +527,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = file_path.metadata().expect("failed to get metas"); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); let name = Name::new(&file_path, file_type); @@ -540,7 +540,7 @@ mod test { File::create(&file_path).expect("failed to create file"); let meta = file_path.metadata().expect("failed to get metas"); - let colors = Colors::new(color::Theme::NoLscolors); + let colors = Colors::new(color::ThemeOption::NoLscolors); let file_type = FileType::new(&meta, None, &Permissions::from(&meta)); let name = Name::new(&file_path, file_type); diff --git a/src/meta/size.rs b/src/meta/size.rs index 13b5fbfd5..c1d5eab88 100644 --- a/src/meta/size.rs +++ b/src/meta/size.rs @@ -153,7 +153,7 @@ impl Size { #[cfg(test)] mod test { use super::Size; - use crate::color::{Colors, Theme}; + use crate::color::{Colors, ThemeOption}; use crate::flags::{Flags, SizeFlag}; #[test] @@ -325,7 +325,7 @@ mod test { let size = Size::new(42 * 1024); // 42 kilobytes let mut flags = Flags::default(); flags.size = SizeFlag::Short; - let colors = Colors::new(Theme::NoColor); + let colors = Colors::new(ThemeOption::NoColor); assert_eq!(size.render(&colors, &flags, Some(2)).to_string(), "42K"); assert_eq!(size.render(&colors, &flags, Some(3)).to_string(), " 42K"); diff --git a/src/meta/symlink.rs b/src/meta/symlink.rs index d7fd340a7..e8815052f 100644 --- a/src/meta/symlink.rs +++ b/src/meta/symlink.rs @@ -73,7 +73,7 @@ impl SymLink { mod tests { use super::SymLink; use crate::app; - use crate::color::{Colors, Theme}; + use crate::color::{Colors, ThemeOption}; use crate::config_file::Config; use crate::flags::Flags; @@ -88,7 +88,7 @@ mod tests { assert_eq!( format!("{}", " ⇒ /target"), link.render( - &Colors::new(Theme::NoColor), + &Colors::new(ThemeOption::NoColor), &Flags::configure_from(&matches, &Config::with_none()).unwrap() ) .to_string() @@ -106,7 +106,7 @@ mod tests { assert_eq!( format!("{}", " ⇒ /target"), link.render( - &Colors::new(Theme::NoColor), + &Colors::new(ThemeOption::NoColor), &Flags::configure_from(&matches, &Config::with_none()).unwrap() ) .to_string() From af15854db3ef55e529c86fbf0333b80691885bfe Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sun, 29 Nov 2020 23:05:24 +0800 Subject: [PATCH 05/19] theme/test: :mag: :sparkles: add tests for theme Signed-off-by: zwPapEr --- src/color.rs | 27 +++++++++++ src/color/theme.rs | 114 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 9 deletions(-) diff --git a/src/color.rs b/src/color.rs index 1431c9e63..af60ddc13 100644 --- a/src/color.rs +++ b/src/color.rs @@ -233,3 +233,30 @@ impl Colors { } } } + +#[cfg(test)] +mod tests { + use super::Colors; + use crate::color::Theme; + use crate::color::ThemeOption; + #[test] + fn test_color_new_no_color_theme() { + assert!(Colors::new(ThemeOption::NoColor).theme.is_none()); + } + + #[test] + fn test_color_new_default_theme() { + assert_eq!( + Colors::new(ThemeOption::Default).theme, + Some(Theme::default_dark()), + ); + } + + #[test] + fn test_color_new_bad_custom_theme() { + assert_eq!( + Colors::new(ThemeOption::Custom("not_existed".to_string())).theme, + Some(Theme::default_dark()), + ); + } +} diff --git a/src/color/theme.rs b/src/color/theme.rs index ee27f3ef4..c33dad484 100644 --- a/src/color/theme.rs +++ b/src/color/theme.rs @@ -10,7 +10,7 @@ use std::path::Path; /// A struct holding the theme configuration /// Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.avg -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Theme { @@ -23,7 +23,7 @@ pub struct Theme { pub inode: INode, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Permissions { @@ -34,7 +34,7 @@ pub struct Permissions { pub no_access: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct FileType { @@ -48,7 +48,7 @@ pub struct FileType { pub special: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct File { @@ -58,7 +58,7 @@ pub struct File { pub no_exec_no_uid: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Dir { @@ -66,7 +66,7 @@ pub struct Dir { pub no_uid: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Symlink { @@ -74,7 +74,7 @@ pub struct Symlink { pub broken: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Modified { @@ -83,7 +83,7 @@ pub struct Modified { pub older: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct Size { @@ -93,7 +93,7 @@ pub struct Size { pub large: Colour, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, PartialEq)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] pub struct INode { @@ -185,4 +185,100 @@ impl Theme { }, } } + + #[cfg(test)] + pub fn default_yaml() -> &'static str { + r#"--- +user: + Fixed: 230 +group: + Fixed: 187 +permissions: + read: Green + write: Yellow + exec: Red + exec-sticky: Purple + no-access: + Fixed: 245 +file-type: + file: + exec-uid: + Fixed: 40 + uid-no-exec: + Fixed: 184 + exec-no-uid: + Fixed: 40 + no-exec-no-uid: + Fixed: 184 + dir: + uid: + Fixed: 33 + no-uid: + Fixed: 33 + pipe: + Fixed: 44 + symlink: + default: + Fixed: 44 + broken: + Fixed: 124 + block-device: + Fixed: 44 + char-device: + Fixed: 172 + socket: + Fixed: 44 + special: + Fixed: 44 +modified: + hour-old: + Fixed: 40 + day-old: + Fixed: 42 + older: + Fixed: 36 +size: + none: + Fixed: 245 + small: + Fixed: 229 + medium: + Fixed: 216 + large: + Fixed: 172 +inode: + valid: + Fixed: 13 + invalid: + Fixed: 245 +"# + } +} + +#[cfg(test)] +mod tests { + use super::Theme; + + #[test] + fn test_default_theme() { + assert_eq!( + Theme::default_dark(), + Theme::with_yaml(Theme::default_yaml()).unwrap() + ); + } + + #[test] + fn test_default_theme_file() { + use std::fs::File; + use std::io::Write; + let dir = assert_fs::TempDir::new().unwrap(); + let theme = dir.path().join("theme.yaml"); + let mut file = File::create(&theme).unwrap(); + writeln!(file, "{}", Theme::default_yaml()).unwrap(); + + assert_eq!( + Theme::default_dark(), + Theme::from_path(theme.to_str().unwrap()).unwrap() + ); + } } From a3a45e5b78bb3db00e47fa93af999f7862b3c6ed Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sat, 5 Dec 2020 15:17:03 +0800 Subject: [PATCH 06/19] theme: :hammer: using default to return dark theme and more tests Signed-off-by: zwPapEr --- src/color.rs | 102 +++++++++++++++++++++++++++++++++++++++++++-- src/color/theme.rs | 7 ++++ src/flags/color.rs | 74 ++++++++++++++++++++++++++++++++ src/flags/size.rs | 11 +++++ src/meta/mod.rs | 13 ++++++ 5 files changed, 203 insertions(+), 4 deletions(-) diff --git a/src/color.rs b/src/color.rs index af60ddc13..ec4437e22 100644 --- a/src/color.rs +++ b/src/color.rs @@ -128,10 +128,10 @@ impl Colors { pub fn new(t: ThemeOption) -> Self { let theme = match t { ThemeOption::NoColor => None, - ThemeOption::Default => Some(Theme::default_dark()), - ThemeOption::NoLscolors => Some(Theme::default_dark()), + ThemeOption::Default => Some(Theme::default()), + ThemeOption::NoLscolors => Some(Theme::default()), ThemeOption::Custom(ref file) => { - Some(Theme::from_path(file).unwrap_or_else(Theme::default_dark)) + Some(Theme::from_path(file).unwrap_or_else(Theme::default)) } }; let lscolors = match t { @@ -255,8 +255,102 @@ mod tests { #[test] fn test_color_new_bad_custom_theme() { assert_eq!( - Colors::new(ThemeOption::Custom("not_existed".to_string())).theme, + Colors::new(ThemeOption::Custom("not-existed".to_string())).theme, Some(Theme::default_dark()), ); } } + +#[cfg(test)] +mod elem { + use super::Elem; + use crate::color::{theme, Theme}; + use ansi_term::Colour; + + #[cfg(test)] + fn test_theme() -> Theme { + Theme { + user: Colour::Fixed(230), // Cornsilk1 + group: Colour::Fixed(187), // LightYellow3 + permissions: theme::Permissions { + read: Colour::Green, + write: Colour::Yellow, + exec: Colour::Red, + exec_sticky: Colour::Purple, + no_access: Colour::Fixed(245), // Grey + }, + file_type: theme::FileType { + file: theme::File { + exec_uid: Colour::Fixed(40), // Green3 + uid_no_exec: Colour::Fixed(184), // Yellow3 + exec_no_uid: Colour::Fixed(40), // Green3 + no_exec_no_uid: Colour::Fixed(184), // Yellow3 + }, + dir: theme::Dir { + uid: Colour::Fixed(33), // DodgerBlue1 + no_uid: Colour::Fixed(33), // DodgerBlue1 + }, + pipe: Colour::Fixed(44), // DarkTurquoise + symlink: theme::Symlink { + default: Colour::Fixed(44), // DarkTurquoise + broken: Colour::Fixed(124), // Red3 + }, + block_device: Colour::Fixed(44), // DarkTurquoise + char_device: Colour::Fixed(172), // Orange3 + socket: Colour::Fixed(44), // DarkTurquoise + special: Colour::Fixed(44), // DarkTurquoise + }, + modified: theme::Modified { + hour_old: Colour::Fixed(40), // Green3 + day_old: Colour::Fixed(42), // SpringGreen2 + older: Colour::Fixed(36), // DarkCyan + }, + size: theme::Size { + none: Colour::Fixed(245), // Grey + small: Colour::Fixed(229), // Wheat1 + medium: Colour::Fixed(216), // LightSalmon1 + large: Colour::Fixed(172), // Orange3 + }, + inode: theme::INode { + valid: Colour::Fixed(13), // Pink + invalid: Colour::Fixed(245), // Grey + }, + } + } + + #[test] + fn test_default_file() { + assert_eq!( + Elem::File { + exec: true, + uid: true + } + .get_color(&test_theme()), + Colour::Fixed(40), + ); + assert_eq!( + Elem::File { + exec: false, + uid: true + } + .get_color(&test_theme()), + Colour::Fixed(184), + ); + assert_eq!( + Elem::File { + exec: true, + uid: false + } + .get_color(&test_theme()), + Colour::Fixed(40), + ); + assert_eq!( + Elem::File { + exec: false, + uid: false + } + .get_color(&test_theme()), + Colour::Fixed(184), + ); + } +} diff --git a/src/color/theme.rs b/src/color/theme.rs index c33dad484..d2dd46b5e 100644 --- a/src/color/theme.rs +++ b/src/color/theme.rs @@ -101,6 +101,13 @@ pub struct INode { pub invalid: Colour, } +impl Default for Theme { + fn default() -> Self { + // TODO: check terminal color and return light or dark + Self::default_dark() + } +} + impl Theme { /// This read theme from file, /// use the file path if it is absolute diff --git a/src/flags/color.rs b/src/flags/color.rs index b6c8d2550..ec327197c 100644 --- a/src/flags/color.rs +++ b/src/flags/color.rs @@ -44,6 +44,11 @@ pub enum ThemeOption { impl ThemeOption { fn from_config(config: &Config) -> ThemeOption { + if let Some(classic) = config.classic { + if classic { + return ThemeOption::NoColor; + } + } if let Some(c) = &config.color { if let Some(t) = &c.theme { return t.clone(); @@ -287,3 +292,72 @@ mod test_color_option { assert_eq!(Some(ColorOption::Never), ColorOption::from_config(&c)); } } + +#[cfg(test)] +mod test_theme_option { + use super::ThemeOption; + use crate::config_file::{self, Config}; + + #[test] + fn test_from_config_none_default() { + assert_eq!( + ThemeOption::Default, + ThemeOption::from_config(&Config::with_none()) + ); + } + + #[test] + fn test_from_config_default() { + let mut c = Config::with_none(); + c.color = Some(config_file::Color { + when: None, + theme: Some(ThemeOption::Default), + }); + + assert_eq!(ThemeOption::Default, ThemeOption::from_config(&c)); + } + + #[test] + fn test_from_config_no_color() { + let mut c = Config::with_none(); + c.color = Some(config_file::Color { + when: None, + theme: Some(ThemeOption::NoColor), + }); + assert_eq!(ThemeOption::NoColor, ThemeOption::from_config(&c)); + } + + #[test] + fn test_from_config_no_lscolor() { + let mut c = Config::with_none(); + c.color = Some(config_file::Color { + when: None, + theme: Some(ThemeOption::NoLscolors), + }); + assert_eq!(ThemeOption::NoLscolors, ThemeOption::from_config(&c)); + } + + #[test] + fn test_from_config_bad_file_flag() { + let mut c = Config::with_none(); + c.color = Some(config_file::Color { + when: None, + theme: Some(ThemeOption::Custom("not-existed".to_string())), + }); + assert_eq!( + ThemeOption::Custom("not-existed".to_string()), + ThemeOption::from_config(&c) + ); + } + + #[test] + fn test_from_config_classic_mode() { + let mut c = Config::with_none(); + c.color = Some(config_file::Color { + when: None, + theme: Some(ThemeOption::Default), + }); + c.classic = Some(true); + assert_eq!(ThemeOption::NoColor, ThemeOption::from_config(&c)); + } +} diff --git a/src/flags/size.rs b/src/flags/size.rs index c7d3e78c5..9f40d6a5d 100644 --- a/src/flags/size.rs +++ b/src/flags/size.rs @@ -82,6 +82,11 @@ mod test { use crate::config_file::Config; use crate::flags::Configurable; + #[test] + fn test_default() { + assert_eq!(SizeFlag::Default, SizeFlag::default()); + } + #[test] fn test_from_arg_matches_none() { let argv = vec!["lsd"]; @@ -113,6 +118,12 @@ mod test { assert_eq!(Some(SizeFlag::Bytes), SizeFlag::from_arg_matches(&matches)); } + #[test] + #[should_panic] + fn test_from_arg_matches_unknonwn() { + let args = vec!["lsd", "--size", "unknown"]; + let _ = app::build().get_matches_from_safe(args).unwrap(); + } #[test] fn test_from_arg_matches_size_multi() { let args = vec!["lsd", "--size", "bytes", "--size", "short"]; diff --git a/src/meta/mod.rs b/src/meta/mod.rs index 078ce738f..476b08d47 100644 --- a/src/meta/mod.rs +++ b/src/meta/mod.rs @@ -248,3 +248,16 @@ impl Meta { }) } } + +#[cfg(test)] +mod tests { + use super::Meta; + + #[test] + #[cfg(unix)] + fn test_from_path_path() { + let dir = assert_fs::TempDir::new().unwrap(); + let meta = Meta::from_path(dir.path(), false).unwrap(); + assert_eq!(meta.path, dir.path()) + } +} From 9ed80bedff1f579cbac9f1740d2c9cb3b87599ff Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Sun, 6 Dec 2020 15:39:35 +0800 Subject: [PATCH 07/19] theme: :art: :memo: update readme, change log, error log Signed-off-by: zwPapEr --- CHANGELOG.md | 3 +- README.md | 101 +++++++++++++++++++++++++++++++++++++++++++++ src/color/theme.rs | 23 ++++++----- src/config_file.rs | 6 +++ 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74494e03c..78dbc56e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate ### Added -- update minimal rust version to 1.42.0 from [zwpaper](https://github.com/zwpaper) [#534](https://github.com/Peltoche/lsd/issues/534) +- Add support for theme from [zwpaper](https://github.com/zwpaper) [#452](https://github.com/Peltoche/lsd/pull/452) +- Update minimal rust version to 1.42.0 from [zwpaper](https://github.com/zwpaper) [#534](https://github.com/Peltoche/lsd/issues/534) - [`NO_COLOR`](https://no-color.org/) environment variable support from [AnInternetTroll](https://github.com/aninternettroll) ### Changed - Change size to use btyes in classic mode from [meain](https://github.com/meain) diff --git a/README.md b/README.md index a9d5c2ffe..e3e9b06a4 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,13 @@ color: # When "classic" is set, this is set to "never". # Possible values: never, auto, always when: auto + # How to colorize the output. + # When "classic" is set, this is set to "no-color". + # Possible values: default, no-color, no-lscolors, + # when specifying , lsd will look up theme file in + # XDG Base Directory if relative + # The file path if absolute + theme: default # == Date == # This specifies the date format for the date column. The freeform format @@ -304,6 +311,100 @@ total-size: false symlink-arrow: ⇒ ``` +## Theme + +`lsd` can be configured with a theme file to set the colors. + +Theme can be configured in the [configuration file](#configuration)(color.theme), +The valid theme configurations are: +- `default`: the default color scheme shipped in `lsd` +- `no-color`: classic mode, there will be just black and white. +- `no-lscolors`: do not check the `LSCOLORS` env +- theme-file-name(yaml): use the theme file to specify colors + +when configured with the `theme-file-name` which is a `yaml` file, +`lsd` will look up the theme file in the following way: +- relative name: check the XDG Base Directory +- absolute name: use the file path and name to find theme file + +Check [Theme file content](#theme-file-content) for details. + +### Theme file content + +Theme file use the [ansi_term](https://docs.rs/ansi_term) +configure the colors, check [ansi_term](https://docs.rs/ansi_term/0.12.1/ansi_term/enum.Colour.html) +for the supported colors. + +Color table: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg + +This is the default theme scheme shipped with `lsd`. + +```yaml +user: + Fixed: 230 +group: + Fixed: 187 +permissions: + read: Green + write: Yellow + exec: Red + exec-sticky: Purple + no-access: + Fixed: 245 +file-type: + file: + exec-uid: + Fixed: 40 + uid-no-exec: + Fixed: 184 + exec-no-uid: + Fixed: 40 + no-exec-no-uid: + Fixed: 184 + dir: + uid: + Fixed: 33 + no-uid: + Fixed: 33 + pipe: + Fixed: 44 + symlink: + default: + Fixed: 44 + broken: + Fixed: 124 + block-device: + Fixed: 44 + char-device: + Fixed: 172 + socket: + Fixed: 44 + special: + Fixed: 44 +modified: + hour-old: + Fixed: 40 + day-old: + Fixed: 42 + older: + Fixed: 36 +size: + none: + Fixed: 245 + small: + Fixed: 229 + medium: + Fixed: 216 + large: + Fixed: 172 +inode: + valid: + Fixed: 13 + invalid: + Fixed: 245 +``` + + ## External Configurations ### Required diff --git a/src/color/theme.rs b/src/color/theme.rs index d2dd46b5e..0c8dd6375 100644 --- a/src/color/theme.rs +++ b/src/color/theme.rs @@ -116,7 +116,7 @@ impl Theme { let real = if let Some(path) = config_file::Config::expand_home(file) { path } else { - print_error!("Bad theme file path: {}.", &file); + print_error!("Not a valid theme file path: {}.", &file); return None; }; let path = if Path::new(&real).is_absolute() { @@ -125,24 +125,25 @@ impl Theme { config_file::Config::config_file_path().unwrap().join(real) }; match fs::read(&path) { - Ok(f) => Self::with_yaml(&String::from_utf8_lossy(&f)), + Ok(f) => match Self::with_yaml(&String::from_utf8_lossy(&f)) { + Ok(t) => Some(t), + Err(e) => { + print_error!("Theme file {} format error: {}.", &file, e); + None + } + }, Err(e) => { - print_error!("bad theme file: {}, {}\n", path.to_string_lossy(), e); + print_error!("Not a valid theme: {}, {}.", path.to_string_lossy(), e); None } } } /// This constructs a Theme struct with a passed [Yaml] str. - fn with_yaml(yaml: &str) -> Option { - match serde_yaml::from_str::(yaml) { - Ok(c) => Some(c), - Err(e) => { - print_error!("theme file format error, {}\n\n", e); - None - } - } + fn with_yaml(yaml: &str) -> Result { + serde_yaml::from_str::(yaml) } + pub fn default_dark() -> Self { Theme { user: Colour::Fixed(230), // Cornsilk1 diff --git a/src/config_file.rs b/src/config_file.rs index cd841f65a..cf156399d 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -210,6 +210,12 @@ color: # When "classic" is set, this is set to "never". # Possible values: never, auto, always when: auto + # How to colorize the output. + # When "classic" is set, this is set to "no-color". + # Possible values: default, no-color, no-lscolors, + # when specifying , lsd will look up theme file in + # XDG Base Directory if relative + # The file path if absolute theme: default # == Date == From 49f58d1e70e8e07c8c2c42989d351491a70e589e Mon Sep 17 00:00:00 2001 From: zwPapEr Date: Tue, 6 Jul 2021 14:21:22 +0800 Subject: [PATCH 08/19] :sparkles: done use crossterm to impl theme, functionally works Signed-off-by: zwPapEr --- src/color.rs | 153 ++++++++++++++++++++----------- src/color/theme.rs | 193 +++++++++++++++++----------------------- src/display.rs | 19 ++-- src/flags/icons.rs | 19 ++-- src/meta/indicator.rs | 7 +- src/meta/permissions.rs | 9 +- src/meta/size.rs | 19 ++-- src/meta/symlink.rs | 13 +-- 8 files changed, 236 insertions(+), 196 deletions(-) diff --git a/src/color.rs b/src/color.rs index ec4437e22..627a358f6 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,10 +1,11 @@ mod theme; +use crossterm::style::{Attribute, ContentStyle, StyledContent, Stylize}; use theme::Theme; pub use crate::flags::color::ThemeOption; -use ansi_term::{ANSIString, Colour, Style}; +use crossterm::style::Color; use lscolors::{Indicator, LsColors}; use std::path::Path; @@ -65,7 +66,7 @@ impl Elem { pub fn has_suid(&self) -> bool { matches!(self, Elem::Dir { uid: true } | Elem::File { uid: true, .. }) } - pub fn get_color(&self, theme: &theme::Theme) -> Colour { + pub fn get_color(&self, theme: &theme::Theme) -> Color { match self { Elem::File { exec: true, @@ -113,11 +114,15 @@ impl Elem { Elem::INode { valid: false } => theme.inode.valid, Elem::INode { valid: true } => theme.inode.invalid, + + Elem::TreeEdge => theme.inode.invalid, + Elem::Links { valid: false } => theme.inode.invalid, + Elem::Links { valid: true } => theme.inode.invalid, } } } -pub type ColoredString<'a> = ANSIString<'a>; +pub type ColoredString = StyledContent; pub struct Colors { theme: Option, @@ -142,40 +147,35 @@ impl Colors { Self { theme, lscolors } } - pub fn colorize<'a>(&self, input: String, elem: &Elem) -> ColoredString<'a> { - self.style(elem).paint(input) + pub fn colorize(&self, input: String, elem: &Elem) -> ColoredString { + self.style(elem).apply(input) } - pub fn colorize_using_path<'a>( - &self, - input: String, - path: &Path, - elem: &Elem, - ) -> ColoredString<'a> { + pub fn colorize_using_path(&self, input: String, path: &Path, elem: &Elem) -> ColoredString { let style_from_path = self.style_from_path(path); match style_from_path { - Some(style_from_path) => style_from_path.paint(input), + Some(style_from_path) => style_from_path.apply(input), None => self.colorize(input, elem), } } - fn style_from_path(&self, path: &Path) -> Option