2020-09-24 18:09:26 +00:00
|
|
|
///! This module provides methods to handle the program's config files and operations related to
|
|
|
|
///! this.
|
2020-11-21 15:40:24 +00:00
|
|
|
use crate::flags::color::ColorOption;
|
|
|
|
use crate::flags::display::Display;
|
|
|
|
use crate::flags::icons::IconOption;
|
|
|
|
use crate::flags::layout::Layout;
|
|
|
|
use crate::flags::size::SizeFlag;
|
|
|
|
use crate::flags::sorting::{DirGrouping, SortColumn};
|
2020-11-21 09:03:25 +00:00
|
|
|
use crate::print_error;
|
|
|
|
|
2020-09-24 18:09:26 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
2020-11-16 06:34:53 +00:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
|
|
|
use std::fs;
|
2020-09-24 18:09:26 +00:00
|
|
|
|
|
|
|
const CONF_DIR: &str = "lsd";
|
|
|
|
const CONF_FILE_NAME: &str = "config";
|
|
|
|
const YAML_LONG_EXT: &str = "yaml";
|
|
|
|
|
|
|
|
/// A struct to hold an optional file path [String] and an optional [Yaml], and provides methods
|
|
|
|
/// around error handling in a config file.
|
2020-11-21 15:40:24 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
|
|
|
#[serde(deny_unknown_fields)]
|
2020-09-24 18:09:26 +00:00
|
|
|
pub struct Config {
|
2020-10-31 10:20:59 +00:00
|
|
|
pub classic: Option<bool>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub blocks: Option<Vec<String>>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub color: Option<Color>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub date: Option<String>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub dereference: Option<bool>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub display: Option<Display>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub icons: Option<Icons>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub ignore_globs: Option<Vec<String>>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub indicators: Option<bool>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub layout: Option<Layout>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub recursion: Option<Recursion>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub size: Option<SizeFlag>,
|
2020-10-31 10:20:59 +00:00
|
|
|
pub sorting: Option<Sorting>,
|
|
|
|
pub no_symlink: Option<bool>,
|
|
|
|
pub total_size: Option<bool>,
|
2020-11-16 06:34:53 +00:00
|
|
|
pub symlink_arrow: Option<String>,
|
2020-10-31 10:20:59 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 15:40:24 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
2020-10-31 10:20:59 +00:00
|
|
|
pub struct Color {
|
2020-11-21 15:40:24 +00:00
|
|
|
pub when: ColorOption,
|
2020-10-31 10:20:59 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 15:40:24 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
2020-10-31 10:20:59 +00:00
|
|
|
pub struct Icons {
|
2020-11-21 15:40:24 +00:00
|
|
|
pub when: Option<IconOption>,
|
2020-11-16 06:34:53 +00:00
|
|
|
pub theme: Option<String>,
|
2020-10-31 10:20:59 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 15:40:24 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
2020-10-31 10:20:59 +00:00
|
|
|
pub struct Recursion {
|
2020-11-16 06:34:53 +00:00
|
|
|
pub enabled: Option<bool>,
|
|
|
|
pub depth: Option<usize>,
|
2020-10-31 10:20:59 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 15:40:24 +00:00
|
|
|
#[derive(Eq, PartialEq, Debug, Deserialize)]
|
|
|
|
#[serde(rename_all = "kebab-case")]
|
2020-10-31 10:20:59 +00:00
|
|
|
pub struct Sorting {
|
2020-11-21 15:40:24 +00:00
|
|
|
pub column: Option<SortColumn>,
|
2020-11-16 06:34:53 +00:00
|
|
|
pub reverse: Option<bool>,
|
2020-11-21 15:40:24 +00:00
|
|
|
pub dir_grouping: Option<DirGrouping>,
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Config {
|
2020-11-16 06:34:53 +00:00
|
|
|
/// This constructs a Config struct with all None
|
2020-09-24 18:09:26 +00:00
|
|
|
pub fn with_none() -> Self {
|
2020-11-16 06:34:53 +00:00
|
|
|
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,
|
|
|
|
sorting: None,
|
|
|
|
no_symlink: None,
|
|
|
|
total_size: None,
|
|
|
|
symlink_arrow: None,
|
|
|
|
}
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
2020-11-21 05:29:14 +00:00
|
|
|
/// This constructs a Config struct with a passed file path [String].
|
2020-10-31 10:20:59 +00:00
|
|
|
pub fn with_file(file: String) -> Option<Self> {
|
|
|
|
match fs::read(&file) {
|
|
|
|
Ok(f) => Self::with_yaml(&String::from_utf8_lossy(&f)),
|
|
|
|
Err(e) => {
|
|
|
|
match e.kind() {
|
|
|
|
std::io::ErrorKind::NotFound => {}
|
|
|
|
_ => print_error!("bad config file: {}, {}\n", &file, e),
|
|
|
|
};
|
|
|
|
None
|
|
|
|
}
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-21 05:29:14 +00:00
|
|
|
/// This constructs a Config struct with a passed [Yaml] str.
|
2020-10-31 10:20:59 +00:00
|
|
|
fn with_yaml(yaml: &str) -> Option<Self> {
|
2020-11-21 15:40:24 +00:00
|
|
|
match serde_yaml::from_str::<Self>(yaml) {
|
2020-10-31 10:20:59 +00:00
|
|
|
Ok(c) => Some(c),
|
|
|
|
Err(e) => {
|
|
|
|
print_error!("configuration file format error, {}\n\n", e);
|
|
|
|
None
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
/// This provides the path for a configuration file, according to the XDG_BASE_DIRS specification.
|
2020-11-21 05:29:14 +00:00
|
|
|
/// return None if error like PermissionDenied
|
2020-09-24 18:09:26 +00:00
|
|
|
#[cfg(not(windows))]
|
2020-11-21 05:29:14 +00:00
|
|
|
fn config_file_path() -> Option<PathBuf> {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => print_error!("can not open config file: {}", e),
|
|
|
|
}
|
|
|
|
None
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
/// This provides the path for a configuration file, inside the %APPDATA% directory.
|
2020-11-21 05:29:14 +00:00
|
|
|
/// return None if error like PermissionDenied
|
2020-09-24 18:09:26 +00:00
|
|
|
#[cfg(windows)]
|
2020-11-21 05:29:14 +00:00
|
|
|
fn config_file_path() -> Option<PathBuf> {
|
|
|
|
if let Some(p) = dirs::config_dir() {
|
|
|
|
return Some(
|
|
|
|
p.join(CONF_DIR)
|
|
|
|
.join(CONF_FILE_NAME)
|
|
|
|
.with_extension(YAML_LONG_EXT),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
None
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
2020-10-31 10:20:59 +00:00
|
|
|
}
|
2020-09-24 18:09:26 +00:00
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Self {
|
2020-11-21 05:29:14 +00:00
|
|
|
if let Some(p) = Self::config_file_path() {
|
|
|
|
if let Some(c) = Self::with_file(p.to_string_lossy().to_string()) {
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
}
|
2020-11-21 15:40:24 +00:00
|
|
|
Self::with_yaml(DEFAULT_CONFIG).unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const DEFAULT_CONFIG: &str = r#"---
|
2020-11-16 06:34:53 +00:00
|
|
|
# == 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, size, size_value, 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
|
|
|
|
|
|
|
|
# == 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
|
|
|
|
|
|
|
|
# == 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
|
|
|
|
|
|
|
|
# == 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
|
|
|
|
|
|
|
|
# == Symlink arrow ==
|
|
|
|
# Specifies how the symlink arrow display, chars in both ascii and utf8
|
2020-11-21 15:40:24 +00:00
|
|
|
symlink-arrow: ⇒
|
|
|
|
"#;
|
2020-09-24 18:09:26 +00:00
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::Config;
|
2020-11-21 15:40:24 +00:00
|
|
|
use crate::config_file;
|
|
|
|
use crate::flags::color::ColorOption;
|
|
|
|
use crate::flags::icons::IconOption;
|
|
|
|
use crate::flags::layout::Layout;
|
|
|
|
use crate::flags::size::SizeFlag;
|
|
|
|
use crate::flags::sorting::{DirGrouping, SortColumn};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_read_default() {
|
|
|
|
let c = Config::with_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(),
|
|
|
|
]
|
|
|
|
.into()
|
|
|
|
),
|
|
|
|
color: Some(config_file::Color {
|
|
|
|
when: ColorOption::Auto,
|
|
|
|
}),
|
|
|
|
date: Some("date".into()),
|
|
|
|
dereference: Some(false),
|
|
|
|
display: None,
|
|
|
|
icons: Some(config_file::Icons {
|
|
|
|
when: Some(IconOption::Auto),
|
|
|
|
theme: Some("fancy".into()),
|
|
|
|
}),
|
|
|
|
ignore_globs: None,
|
|
|
|
indicators: Some(false),
|
|
|
|
layout: Some(Layout::Grid),
|
|
|
|
recursion: Some(config_file::Recursion {
|
|
|
|
enabled: Some(false),
|
|
|
|
depth: None,
|
|
|
|
}),
|
|
|
|
size: Some(SizeFlag::Default),
|
|
|
|
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()),
|
|
|
|
},
|
|
|
|
c
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_read_config_ok() {
|
|
|
|
let c = Config::with_yaml("classic: true").unwrap();
|
|
|
|
assert!(c.classic.unwrap())
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_read_config_bad_bool() {
|
|
|
|
let c = Config::with_yaml("classic: notbool");
|
2020-11-18 14:02:38 +00:00
|
|
|
assert!(c.is_none())
|
2020-09-24 18:09:26 +00:00
|
|
|
}
|
2020-10-25 16:01:36 +00:00
|
|
|
|
2020-10-31 10:20:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_read_config_file_not_found() {
|
|
|
|
let c = Config::with_file("not-existed".to_string());
|
|
|
|
assert!(c.is_none())
|
2020-10-25 16:01:36 +00:00
|
|
|
}
|
2020-11-21 15:40:24 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_read_bad_display() {
|
|
|
|
assert!(Config::with_yaml("display: bad").is_none())
|
|
|
|
}
|
2020-10-25 16:01:36 +00:00
|
|
|
}
|