use {
crate::{
errors::ConfError,
keys,
skin_conf,
verb_conf::VerbConf,
},
crossterm::style::Attribute,
directories::ProjectDirs,
std::{
collections::HashMap,
fs, io,
path::{Path, PathBuf},
result::Result,
},
termimad::CompoundStyle,
toml::{self, Value},
};
#[derive(Default)]
pub struct Conf {
pub default_flags: String, pub verbs: Vec<VerbConf>,
pub skin: HashMap<String, CompoundStyle>,
}
fn string_field(value: &Value, field_name: &str) -> Option<String> {
if let Value::Table(tbl) = value {
if let Some(fv) = tbl.get(field_name) {
if let Some(s) = fv.as_str() {
return Some(s.to_string());
}
}
}
None
}
fn bool_field(value: &Value, field_name: &str) -> Option<bool> {
if let Value::Table(tbl) = value {
if let Some(Value::Boolean(b)) = tbl.get(field_name) {
return Some(*b);
}
}
None
}
pub fn app_dirs() -> ProjectDirs {
ProjectDirs::from("org", "dystroy", "broot")
.expect("Unable to find configuration directories")
}
pub fn dir() -> PathBuf {
app_dirs().config_dir().to_path_buf()
}
impl Conf {
pub fn default_location() -> PathBuf {
dir().join("conf.toml")
}
pub fn from_default_location() -> Result<Conf, ConfError> {
let conf_filepath = Conf::default_location();
if !conf_filepath.exists() {
Conf::write_sample(&conf_filepath)?;
println!(
"New Configuration file written in {}{:?}{}.",
Attribute::Bold,
&conf_filepath,
Attribute::Reset,
);
println!("You should have a look at it.");
}
let mut conf = Conf::default();
match conf.read_file(&conf_filepath) {
Err(e) => {
println!("Failed to read configuration in {:?}.", &conf_filepath);
println!("Please delete or fix this file.");
Err(e)
}
_ => Ok(conf),
}
}
pub fn write_sample(filepath: &Path) -> Result<(), io::Error> {
fs::create_dir_all(filepath.parent().unwrap())?;
fs::write(filepath, DEFAULT_CONF_FILE)?;
Ok(())
}
pub fn read_file(&mut self, filepath: &Path) -> Result<(), ConfError> {
let data = fs::read_to_string(filepath)?;
let root: Value = data.parse::<Value>()?;
if let Some(s) = string_field(&root, "default_flags") {
self.default_flags.push_str(&s);
}
if let Some(Value::Array(verbs_value)) = &root.get("verbs") {
for verb_value in verbs_value.iter() {
let invocation = string_field(verb_value, "invocation")
.unwrap_or("".to_string());
let key = string_field(verb_value, "key")
.map(|s| keys::parse_key(&s))
.transpose()?;
if let Some(key) = key {
if keys::is_reserved(key) {
return Err(ConfError::ReservedKey {
key: keys::key_event_desc(key),
});
}
}
let execution = match string_field(verb_value, "execution") {
Some(s) => s,
None => {
eprintln!("Invalid [[verbs]] entry in configuration");
eprintln!("Missing execution");
continue;
}
};
let from_shell = bool_field(verb_value, "from_shell");
let leave_broot = bool_field(verb_value, "leave_broot");
if leave_broot == Some(false) && from_shell == Some(true) {
eprintln!("Invalid [[verbs]] entry in configuration");
eprintln!(
"You can't simultaneously have leave_broot=false and from_shell=true"
);
continue;
}
self.verbs.push(VerbConf {
invocation,
execution,
key,
shortcut: string_field(verb_value, "shortcut"),
description: string_field(verb_value, "description"),
from_shell,
leave_broot,
confirm: bool_field(verb_value, "confirm"),
});
}
}
if let Some(Value::Table(entries_tbl)) = &root.get("skin") {
for (k, v) in entries_tbl.iter() {
if let Some(s) = v.as_str() {
match skin_conf::parse_object_style(s) {
Ok(ske) => {
self.skin.insert(k.to_string(), ske);
}
Err(e) => {
eprintln!("{}", e);
}
}
}
}
}
Ok(())
}
}
const DEFAULT_CONF_FILE: &str = r#"
###############################################################
# This configuration file lets you
# - define new commands
# - change the shortcut or triggering keys of built-in verbs
# - change the colors
# - set default values for flags
#
# Configuration documentation is available at
# https://dystroy.org/broot
###############################################################
###############################################################
# Default flags
# You can set up flags you want broot to start with by
# default, for example `default_flags="ihp"` if you usually want
# to see hidden and gitignored files and the permissions (then
# if you don't want the hidden files you can launch `br -H`)
# A popular flag is the `g` one which displays git related info.
#
default_flags = ""
###############################################################
# Verbs and shortcuts
# If $EDITOR isn't set on your computer, you should either set it using
# something similar to
# export EDITOR=/usr/bin/nvim
# or just replace it with your editor of choice in the 'execution'
# pattern.
# Example:
# execution = "/usr/bin/nvim {file}"
#
[[verbs]]
invocation = "edit"
key = "F2"
shortcut = "e"
execution = "$EDITOR {file}"
[[verbs]]
key = "ctrl-c"
execution = ":quit"
[[verbs]]
invocation = "create {subpath}"
execution = "$EDITOR {directory}/{subpath}"
[[verbs]]
invocation = "git_diff"
shortcut = "gd"
execution = "git diff {file}"
# If $PAGER isn't set on your computer, you should either set it
# or just replace it with your viewer of choice in the 'execution'
# pattern.
# Example:
# execution = "less {file}"
[[verbs]]
name = "view"
invocation = "view"
execution = "$PAGER {file}"
# If you uncomment the two next shortcuts, the left
# and right arrow keys will be used to go to the parent
# directory or to open a selected one:
#
# [[verbs]]
# key = "left"
# execution = ":parent"
#
# [[verbs]]
# key = "right"
# execution = ":focus"
# Another popular set of shorctuts for going up and down:
#
# [[verbs]]
# key = "ctrl-j"
# execution = ":line_down"
#
# [[verbs]]
# key = "ctrl-k"
# execution = ":line_up"
#
# [[verbs]]
# key = "ctrl-d"
# execution = ":page_down"
#
# [[verbs]]
# key = "ctrl-u"
# execution = ":page_up"
# If you develop using git, you might like to often switch
# to the "git status" filter:
#
# [[verbs]]
# key = "ctrl-g"
# execution = ":toggle_git_status"
###############################################################
# Skin
# If you want to change the colors of broot,
# uncomment the following bloc and start messing
# with the various values.
# Note that some of those colors might not correcly
# render on terminals with low capabilities.
#
# [skin]
# default = "gray(20) gray(1)"
# tree = "rgb(89, 73, 101) none"
# file = "gray(21) none"
# directory = "rgb(255, 152, 0) none bold"
# exe = "rgb(17, 164, 181) none"
# link = "Magenta none"
# pruning = "rgb(89, 73, 101) none Italic"
# perm__ = "gray(5) None"
# perm_r = "ansi(94) None"
# perm_w = "ansi(132) None"
# perm_x = "ansi(65) None"
# owner = "gray(12) none"
# group = "gray(12) none"
# selected_line = "none gray(3)"
# char_match = "yellow none"
# file_error = "Red none"
# flag_label = "gray(16) none"
# flag_value = "rgb(255, 152, 0) none bold"
# input = "White none"
# status_error = "Red gray(2)"
# status_job = "ansi(220) gray(5)"
# status_normal = "gray(20) gray(3)"
# status_italic = "rgb(255, 152, 0) None"
# status_bold = "rgb(255, 152, 0) None bold"
# status_code = "ansi(229) gray(5)"
# status_ellipsis = "gray(19) gray(1)"
# scrollbar_track = "rgb(80, 50, 0) none"
# scrollbar_thumb = "rgb(255, 187, 0) none"
# help_paragraph = "gray(20) none"
# help_bold = "rgb(255, 187, 0) none bold"
# help_italic = "Magenta rgb(30, 30, 40) italic"
# help_code = "gray(21) gray(3)"
# help_headers = "rgb(255, 187, 0) none"
# You may find other skins on
# https://dystroy.org/broot/documentation/configuration/#colors
# for example a skin suitable for white backgrounds
"#;