use crossterm::Attribute;
use crossterm::ObjectStyle;
use directories::ProjectDirs;
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::result::Result;
use toml::{self, Value};
use crate::errors::ConfError;
use crate::skin_conf;
#[derive(Debug)]
pub struct VerbConf {
pub shortcut: Option<String>,
pub invocation: String,
pub execution: String,
pub description: Option<String>,
pub from_shell: Option<bool>,
pub leave_broot: Option<bool>,
pub confirm: Option<bool>,
}
pub struct Conf {
pub verbs: Vec<VerbConf>,
pub skin: HashMap<String, ObjectStyle>,
}
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 dir() -> PathBuf {
if let Some(dirs) = ProjectDirs::from("org", "dystroy", "broot") {
dirs.config_dir().to_path_buf()
} else {
panic!("Unable to find configuration directories");
}
}
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.");
}
Ok(Conf::from_file(&conf_filepath)?)
}
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 from_file(filepath: &Path) -> Result<Conf, ConfError> {
let data = fs::read_to_string(filepath)?;
let root: Value = data.parse::<Value>()?;
let mut verbs: Vec<VerbConf> = vec![];
if let Some(Value::Array(verbs_value)) = &root.get("verbs") {
for verb_value in verbs_value.iter() {
let invocation = match string_field(verb_value, "invocation") {
Some(s) => s,
None => {
eprintln!("Invalid [[verbs]] entry in configuration");
eprintln!("Missing invocation");
continue;
}
};
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;
}
verbs.push(VerbConf {
invocation,
execution,
shortcut: string_field(verb_value, "shortcut"),
description: string_field(verb_value, "description"),
from_shell,
leave_broot,
confirm: bool_field(verb_value, "confirm"),
});
}
}
let mut skin = HashMap::new();
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) => {
skin.insert(k.to_string(), ske);
}
Err(e) => {
eprintln!("{}", e);
}
}
}
}
}
Ok(Conf { verbs, skin })
}
}
const DEFAULT_CONF_FILE: &str = r#"
# This configuration file lets you define new commands
# or change the shortcut of built-in verbs.
# You can change the colors of broot too.
#
# Configuration documentation is available at https://dystroy.org/broot
#
###############################
# shortcuts for built-in verbs:
[[verbs]]
invocation = "p"
execution = ":parent"
#####################
# user defined verbs:
# If $EDITOR isn't set on your computer, you should either set it
# or just replace it with your editor of choice in the 'execution'
# pattern.
# Example:
# execution = "/usr/bin/nvim {file}"
[[verbs]]
invocation = "edit"
shortcut = "e"
execution = "$EDITOR {file}"
[[verbs]]
invocation = "create {subpath}"
execution = "$EDITOR {directory}/{subpath}"
[[verbs]]
name = "view"
invocation = "view"
execution = "less {file}"
#####################
# Skin
# If you want to change the colors of broot,
# uncomment the following bloc and start messing
# with the various values
#
# [skin]
# 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"
# permissions = "gray(12) none "
# selected_line = "none gray(3)"
# size_bar = "black rgb(255, 152, 0)"
# size_no_bar = "gray(15) gray(2)"
# char_match = "yellow none"
# file_error = "Red none"
# flag_label = "gray(16) none"
# flag_value = "rgb(255, 152, 0) none bold"
# input = "White none"
# spinner = "gray(10) gray(2)"
# status_error = "Red gray(2)"
# status_normal = "gray(20) gray(2)"
# 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"
# help_table_border = "rgb(170, 136, 0) none"
#
"#;