mirror of
https://github.com/lsd-rs/lsd
synced 2024-12-15 06:22:47 +00:00
419 lines
12 KiB
Rust
419 lines
12 KiB
Rust
use crate::flags::display::Display;
|
|
use crate::flags::icons::{IconOption, IconTheme};
|
|
use crate::flags::layout::Layout;
|
|
use crate::flags::permission::PermissionFlag;
|
|
use crate::flags::size::SizeFlag;
|
|
use crate::flags::sorting::{DirGrouping, SortColumn};
|
|
use crate::flags::HyperlinkOption;
|
|
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::{Path, PathBuf};
|
|
|
|
use serde::Deserialize;
|
|
|
|
use std::fs;
|
|
use std::io;
|
|
|
|
const CONF_DIR: &str = "lsd";
|
|
const CONF_FILE_NAME: &str = "config.yaml";
|
|
|
|
/// A struct to hold an optional configuration items, and provides methods
|
|
/// around error handling in a config file.
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
#[serde(deny_unknown_fields)]
|
|
pub struct Config {
|
|
pub classic: Option<bool>,
|
|
pub blocks: Option<Vec<String>>,
|
|
pub color: Option<Color>,
|
|
pub date: Option<String>,
|
|
pub dereference: Option<bool>,
|
|
pub display: Option<Display>,
|
|
pub icons: Option<Icons>,
|
|
pub ignore_globs: Option<Vec<String>>,
|
|
pub indicators: Option<bool>,
|
|
pub layout: Option<Layout>,
|
|
pub recursion: Option<Recursion>,
|
|
pub size: Option<SizeFlag>,
|
|
pub permission: Option<PermissionFlag>,
|
|
pub sorting: Option<Sorting>,
|
|
pub no_symlink: Option<bool>,
|
|
pub total_size: Option<bool>,
|
|
pub symlink_arrow: Option<String>,
|
|
pub hyperlink: Option<HyperlinkOption>,
|
|
pub header: Option<bool>,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
pub struct Color {
|
|
pub when: Option<ColorOption>,
|
|
pub theme: Option<ThemeOption>,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
pub struct Icons {
|
|
pub when: Option<IconOption>,
|
|
pub theme: Option<IconTheme>,
|
|
pub separator: Option<String>,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
pub struct Recursion {
|
|
pub enabled: Option<bool>,
|
|
pub depth: Option<usize>,
|
|
}
|
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct Sorting {
|
|
pub column: Option<SortColumn>,
|
|
pub reverse: Option<bool>,
|
|
pub dir_grouping: Option<DirGrouping>,
|
|
}
|
|
|
|
impl Config {
|
|
/// This constructs a Config struct with all None
|
|
pub fn with_none() -> Self {
|
|
Self {
|
|
classic: None,
|
|
blocks: None,
|
|
color: None,
|
|
date: None,
|
|
dereference: None,
|
|
display: None,
|
|
icons: None,
|
|
ignore_globs: None,
|
|
indicators: None,
|
|
layout: None,
|
|
recursion: None,
|
|
size: None,
|
|
permission: None,
|
|
sorting: None,
|
|
no_symlink: None,
|
|
total_size: None,
|
|
symlink_arrow: None,
|
|
hyperlink: None,
|
|
header: None,
|
|
}
|
|
}
|
|
|
|
/// This constructs a Config struct with a passed file path.
|
|
pub fn from_file<P: AsRef<Path>>(file: P) -> Option<Self> {
|
|
let file = file.as_ref();
|
|
match fs::read(file) {
|
|
Ok(f) => match Self::from_yaml(&String::from_utf8_lossy(&f)) {
|
|
Ok(c) => Some(c),
|
|
Err(e) => {
|
|
print_error!(
|
|
"Configuration file {} format error, {}.",
|
|
file.to_string_lossy(),
|
|
e
|
|
);
|
|
None
|
|
}
|
|
},
|
|
Err(e) => {
|
|
if e.kind() != io::ErrorKind::NotFound {
|
|
print_error!(
|
|
"Can not open config file {}: {}.",
|
|
file.to_string_lossy(),
|
|
e
|
|
);
|
|
}
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This constructs a Config struct with a passed [Yaml] str.
|
|
/// If error happened, return the [serde_yaml::Error].
|
|
fn from_yaml(yaml: &str) -> Result<Self, serde_yaml::Error> {
|
|
serde_yaml::from_str::<Self>(yaml)
|
|
}
|
|
|
|
/// This provides the path for a configuration file, according to the XDG_BASE_DIRS specification.
|
|
/// return None if error like PermissionDenied
|
|
#[cfg(not(windows))]
|
|
pub fn config_file_path() -> Option<PathBuf> {
|
|
use xdg::BaseDirectories;
|
|
match BaseDirectories::with_prefix(CONF_DIR) {
|
|
Ok(p) => Some(p.get_config_home()),
|
|
Err(e) => {
|
|
print_error!("Can not open config file: {}.", e);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This provides the path for a configuration file, inside the %APPDATA% directory.
|
|
/// return None if error like PermissionDenied
|
|
#[cfg(windows)]
|
|
pub fn config_file_path() -> Option<PathBuf> {
|
|
dirs::config_dir().map(|x| x.join(CONF_DIR))
|
|
}
|
|
|
|
/// 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 {
|
|
fn default() -> Self {
|
|
if let Some(p) = Self::config_file_path() {
|
|
if let Some(c) = Self::from_file(p.join(CONF_FILE_NAME)) {
|
|
return c;
|
|
}
|
|
}
|
|
Self::from_yaml(DEFAULT_CONFIG).unwrap()
|
|
}
|
|
}
|
|
|
|
const DEFAULT_CONFIG: &str = r#"---
|
|
# == Classic ==
|
|
# This is a shorthand to override some of the options to be backwards compatible
|
|
# with `ls`. It affects the "color"->"when", "sorting"->"dir-grouping", "date"
|
|
# and "icons"->"when" options.
|
|
# Possible values: false, true
|
|
classic: false
|
|
|
|
# == Blocks ==
|
|
# This specifies the columns and their order when using the long and the tree
|
|
# layout.
|
|
# Possible values: permission, user, group, context, size, date, name, inode
|
|
blocks:
|
|
- permission
|
|
- user
|
|
- group
|
|
- size
|
|
- date
|
|
- name
|
|
|
|
# == Color ==
|
|
# This has various color options. (Will be expanded in the future.)
|
|
color:
|
|
# When to colorize the output.
|
|
# 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, <theme-file-name>
|
|
# when specifying <theme-file-name>, 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
|
|
# accepts an strftime like string.
|
|
# When "classic" is set, this is set to "date".
|
|
# Possible values: date, relative, +<date_format>
|
|
# date: date
|
|
|
|
# == Dereference ==
|
|
# Whether to dereference symbolic links.
|
|
# Possible values: false, true
|
|
dereference: false
|
|
|
|
# == Display ==
|
|
# What items to display. Do not specify this for the default behavior.
|
|
# Possible values: all, almost-all, directory-only
|
|
# display: all
|
|
|
|
# == Icons ==
|
|
icons:
|
|
# When to use icons.
|
|
# When "classic" is set, this is set to "never".
|
|
# Possible values: always, auto, never
|
|
when: auto
|
|
# Which icon theme to use.
|
|
# Possible values: fancy, unicode
|
|
theme: fancy
|
|
# The string between the icons and the name.
|
|
# Possible values: any string (eg: " |")
|
|
separator: " "
|
|
|
|
# == Ignore Globs ==
|
|
# A list of globs to ignore when listing.
|
|
# ignore-globs:
|
|
# - .git
|
|
|
|
# == Indicators ==
|
|
# Whether to add indicator characters to certain listed files.
|
|
# Possible values: false, true
|
|
indicators: false
|
|
|
|
# == Layout ==
|
|
# Which layout to use. "oneline" might be a bit confusing here and should be
|
|
# called "one-per-line". It might be changed in the future.
|
|
# Possible values: grid, tree, oneline
|
|
layout: grid
|
|
|
|
# == Recursion ==
|
|
recursion:
|
|
# Whether to enable recursion.
|
|
# Possible values: false, true
|
|
enabled: false
|
|
# How deep the recursion should go. This has to be a positive integer. Leave
|
|
# it unspecified for (virtually) infinite.
|
|
# depth: 3
|
|
|
|
# == Size ==
|
|
# Specifies the format of the size column.
|
|
# Possible values: default, short, bytes
|
|
size: default
|
|
|
|
# == Permission ==
|
|
# Specify the format of the permission column.
|
|
# Possible value: rwx, octal
|
|
permission: rwx
|
|
|
|
# == Sorting ==
|
|
sorting:
|
|
# Specify what to sort by.
|
|
# Possible values: extension, name, time, size, version
|
|
column: name
|
|
# Whether to reverse the sorting.
|
|
# Possible values: false, true
|
|
reverse: false
|
|
# Whether to group directories together and where.
|
|
# When "classic" is set, this is set to "none".
|
|
# Possible values: first, last, none
|
|
dir-grouping: none
|
|
|
|
# == No Symlink ==
|
|
# Whether to omit showing symlink targets
|
|
# Possible values: false, true
|
|
no-symlink: false
|
|
|
|
# == Total size ==
|
|
# Whether to display the total size of directories.
|
|
# Possible values: false, true
|
|
total-size: false
|
|
|
|
# == Hyperlink ==
|
|
# Whether to display the total size of directories.
|
|
# Possible values: always, auto, never
|
|
hyperlink: never
|
|
|
|
# == Symlink arrow ==
|
|
# Specifies how the symlink arrow display, chars in both ascii and utf8
|
|
symlink-arrow: ⇒
|
|
"#;
|
|
|
|
#[cfg(test)]
|
|
impl Config {
|
|
pub fn builtin() -> Self {
|
|
Self::from_yaml(DEFAULT_CONFIG).unwrap()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Config;
|
|
use crate::config_file;
|
|
use crate::flags::color::{ColorOption, ThemeOption};
|
|
use crate::flags::icons::{IconOption, IconTheme};
|
|
use crate::flags::layout::Layout;
|
|
use crate::flags::permission::PermissionFlag;
|
|
use crate::flags::size::SizeFlag;
|
|
use crate::flags::sorting::{DirGrouping, SortColumn};
|
|
use crate::flags::HyperlinkOption;
|
|
|
|
#[test]
|
|
fn test_read_default() {
|
|
let c = Config::from_yaml(config_file::DEFAULT_CONFIG).unwrap();
|
|
assert_eq!(
|
|
Config {
|
|
classic: Some(false),
|
|
blocks: Some(vec![
|
|
"permission".into(),
|
|
"user".into(),
|
|
"group".into(),
|
|
"size".into(),
|
|
"date".into(),
|
|
"name".into(),
|
|
]),
|
|
color: Some(config_file::Color {
|
|
when: Some(ColorOption::Auto),
|
|
theme: Some(ThemeOption::Default)
|
|
}),
|
|
date: None,
|
|
dereference: Some(false),
|
|
display: None,
|
|
icons: Some(config_file::Icons {
|
|
when: Some(IconOption::Auto),
|
|
theme: Some(IconTheme::Fancy),
|
|
separator: Some(" ".to_string()),
|
|
}),
|
|
ignore_globs: None,
|
|
indicators: Some(false),
|
|
layout: Some(Layout::Grid),
|
|
recursion: Some(config_file::Recursion {
|
|
enabled: Some(false),
|
|
depth: None,
|
|
}),
|
|
size: Some(SizeFlag::Default),
|
|
permission: Some(PermissionFlag::Rwx),
|
|
sorting: Some(config_file::Sorting {
|
|
column: Some(SortColumn::Name),
|
|
reverse: Some(false),
|
|
dir_grouping: Some(DirGrouping::None),
|
|
}),
|
|
no_symlink: Some(false),
|
|
total_size: Some(false),
|
|
symlink_arrow: Some("⇒".into()),
|
|
hyperlink: Some(HyperlinkOption::Never),
|
|
header: None
|
|
},
|
|
c
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_config_ok() {
|
|
let c = Config::from_yaml("classic: true").unwrap();
|
|
assert!(c.classic.unwrap())
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_config_bad_bool() {
|
|
let c = Config::from_yaml("classic: notbool");
|
|
assert!(c.is_err())
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_config_file_not_found() {
|
|
let c = Config::from_file("not-existed");
|
|
assert!(c.is_none())
|
|
}
|
|
|
|
#[test]
|
|
fn test_read_bad_display() {
|
|
assert!(Config::from_yaml("display: bad").is_err())
|
|
}
|
|
}
|