mirror of
https://github.com/lsd-rs/lsd
synced 2024-12-14 14:12:31 +00:00
color: ✨ add parse theme file
Signed-off-by: zwPapEr <zw.paper@gmail.com>
This commit is contained in:
parent
ec77d91952
commit
b553d07faa
8 changed files with 374 additions and 123 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -24,6 +24,7 @@ version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ clap = "2.33.*"
|
||||||
version_check = "0.9.*"
|
version_check = "0.9.*"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi_term = "0.12.*"
|
ansi_term = { version = "0.12.*", features = ["derive_serde_style"] }
|
||||||
dirs = "3.0.*"
|
dirs = "3.0.*"
|
||||||
libc = "0.2.*"
|
libc = "0.2.*"
|
||||||
human-sort = "0.2.2"
|
human-sort = "0.2.2"
|
||||||
|
|
170
src/color.rs
170
src/color.rs
|
@ -1,6 +1,11 @@
|
||||||
|
mod theme;
|
||||||
|
|
||||||
|
use theme::Theme;
|
||||||
|
|
||||||
|
use crate::flags::color::ThemeOption;
|
||||||
|
|
||||||
use ansi_term::{ANSIString, Colour, Style};
|
use ansi_term::{ANSIString, Colour, Style};
|
||||||
use lscolors::{Indicator, LsColors};
|
use lscolors::{Indicator, LsColors};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -60,37 +65,81 @@ impl Elem {
|
||||||
pub fn has_suid(&self) -> bool {
|
pub fn has_suid(&self) -> bool {
|
||||||
matches!(self, Elem::Dir { uid: true } | Elem::File { uid: true, .. })
|
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>;
|
pub type ColoredString<'a> = ANSIString<'a>;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Theme {
|
|
||||||
NoColor,
|
|
||||||
Default,
|
|
||||||
NoLscolors,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Colors {
|
pub struct Colors {
|
||||||
colors: Option<HashMap<Elem, Colour>>,
|
theme: Option<Theme>,
|
||||||
lscolors: Option<LsColors>,
|
lscolors: Option<LsColors>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Colors {
|
impl Colors {
|
||||||
pub fn new(theme: Theme) -> Self {
|
pub fn new(t: ThemeOption) -> Self {
|
||||||
let colors = match theme {
|
let theme = match t {
|
||||||
Theme::NoColor => None,
|
ThemeOption::NoColor => None,
|
||||||
Theme::Default => Some(Self::get_light_theme_colour_map()),
|
ThemeOption::Default => Some(Theme::default_dark()),
|
||||||
Theme::NoLscolors => Some(Self::get_light_theme_colour_map()),
|
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 {
|
let lscolors = match t {
|
||||||
Theme::NoColor => None,
|
ThemeOption::Default => Some(LsColors::from_env().unwrap_or_default()),
|
||||||
Theme::Default => Some(LsColors::from_env().unwrap_or_default()),
|
_ => None,
|
||||||
Theme::NoLscolors => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { colors, lscolors }
|
Self { theme, lscolors }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn colorize<'a>(&self, input: String, elem: &Elem) -> ColoredString<'a> {
|
pub fn colorize<'a>(&self, input: String, elem: &Elem) -> ColoredString<'a> {
|
||||||
|
@ -135,8 +184,8 @@ impl Colors {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style_default(&self, elem: &Elem) -> Style {
|
fn style_default(&self, elem: &Elem) -> Style {
|
||||||
if let Some(ref colors) = self.colors {
|
if let Some(t) = &self.theme {
|
||||||
let style_fg = Style::default().fg(colors[elem]);
|
let style_fg = Style::default().fg(elem.get_color(&t));
|
||||||
if elem.has_suid() {
|
if elem.has_suid() {
|
||||||
style_fg.on(Colour::Fixed(124)) // Red3
|
style_fg.on(Colour::Fixed(124)) // Red3
|
||||||
} else {
|
} else {
|
||||||
|
@ -183,81 +232,4 @@ impl Colors {
|
||||||
None => None,
|
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<Elem, Colour> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
188
src/color/theme.rs
Normal file
188
src/color/theme.rs
Normal file
|
@ -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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
match serde_yaml::from_str::<Self>(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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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::display::Display;
|
||||||
use crate::flags::icons::{IconOption, IconTheme};
|
use crate::flags::icons::{IconOption, IconTheme};
|
||||||
use crate::flags::layout::Layout;
|
use crate::flags::layout::Layout;
|
||||||
use crate::flags::size::SizeFlag;
|
use crate::flags::size::SizeFlag;
|
||||||
use crate::flags::sorting::{DirGrouping, SortColumn};
|
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 crate::print_error;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ pub struct Config {
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
pub when: ColorOption,
|
pub when: Option<ColorOption>,
|
||||||
|
pub theme: Option<ThemeOption>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
#[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.
|
/// This provides the path for a configuration file, according to the XDG_BASE_DIRS specification.
|
||||||
/// return None if error like PermissionDenied
|
/// return None if error like PermissionDenied
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
fn config_file_path() -> Option<PathBuf> {
|
pub fn config_file_path() -> Option<PathBuf> {
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
match BaseDirectories::with_prefix(CONF_DIR) {
|
match BaseDirectories::with_prefix(CONF_DIR) {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
if let Ok(p) = p.place_config_file([CONF_FILE_NAME, YAML_LONG_EXT].join(".")) {
|
return Some(p.get_config_home());
|
||||||
return Some(p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => print_error!("Can not open config file: {}.", e),
|
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.
|
/// This provides the path for a configuration file, inside the %APPDATA% directory.
|
||||||
/// return None if error like PermissionDenied
|
/// return None if error like PermissionDenied
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn config_file_path() -> Option<PathBuf> {
|
pub fn config_file_path() -> Option<PathBuf> {
|
||||||
if let Some(p) = dirs::config_dir() {
|
if let Some(p) = dirs::config_dir() {
|
||||||
return Some(
|
return Some(p.join(CONF_DIR));
|
||||||
p.join(CONF_DIR)
|
|
||||||
.join(CONF_FILE_NAME)
|
|
||||||
.with_extension(YAML_LONG_EXT),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
None
|
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<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
|
||||||
|
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 {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
if let Some(p) = Self::config_file_path() {
|
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;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/core.rs
10
src/core.rs
|
@ -1,6 +1,8 @@
|
||||||
use crate::color::{self, Colors};
|
use crate::color::Colors;
|
||||||
use crate::display;
|
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::icon::{self, Icons};
|
||||||
use crate::meta::Meta;
|
use crate::meta::Meta;
|
||||||
use crate::{print_error, print_output, sort};
|
use crate::{print_error, print_output, sort};
|
||||||
|
@ -41,8 +43,8 @@ impl Core {
|
||||||
let mut inner_flags = flags.clone();
|
let mut inner_flags = flags.clone();
|
||||||
|
|
||||||
let color_theme = match (tty_available && console_color_ok, flags.color.when) {
|
let color_theme = match (tty_available && console_color_ok, flags.color.when) {
|
||||||
(_, ColorOption::Never) | (false, ColorOption::Auto) => color::Theme::NoColor,
|
(_, ColorOption::Never) | (false, ColorOption::Auto) => ThemeOption::NoColor,
|
||||||
_ => color::Theme::Default,
|
_ => flags.color.theme.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon_theme = match (tty_available, flags.icons.when, flags.icons.theme) {
|
let icon_theme = match (tty_available, flags.icons.when, flags.icons.theme) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub mod total_size;
|
||||||
pub use blocks::Block;
|
pub use blocks::Block;
|
||||||
pub use blocks::Blocks;
|
pub use blocks::Blocks;
|
||||||
pub use color::Color;
|
pub use color::Color;
|
||||||
pub use color::ColorOption;
|
pub use color::{ColorOption, ThemeOption};
|
||||||
pub use date::DateFlag;
|
pub use date::DateFlag;
|
||||||
pub use dereference::Dereference;
|
pub use dereference::Dereference;
|
||||||
pub use display::Display;
|
pub use display::Display;
|
||||||
|
|
|
@ -7,14 +7,17 @@ use crate::config_file::Config;
|
||||||
use crate::print_error;
|
use crate::print_error;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use serde::de::{self, Deserializer, Visitor};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// A collection of flags on how to use colors.
|
/// A collection of flags on how to use colors.
|
||||||
#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
/// When to use color.
|
/// When to use color.
|
||||||
pub when: ColorOption,
|
pub when: ColorOption,
|
||||||
|
pub theme: ThemeOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
|
@ -23,7 +26,68 @@ impl Color {
|
||||||
/// The [ColorOption] is configured with their respective [Configurable] implementation.
|
/// The [ColorOption] is configured with their respective [Configurable] implementation.
|
||||||
pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self {
|
pub fn configure_from(matches: &ArgMatches, config: &Config) -> Self {
|
||||||
let when = ColorOption::configure_from(matches, config);
|
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<D>(deserializer: D) -> Result<ThemeOption, D::Error>
|
||||||
|
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 <theme-file-path>")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<ThemeOption, E>
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue