use clap::ArgMatches;
use dirs;
use serde_derive::{Serialize, Deserialize};
use std::default::Default;
use std::path::{Path, PathBuf};
use toml;
struct ConfigRef {
pub path: PathBuf,
}
impl Default for ConfigRef {
fn default() -> Self {
let path =
dirs::config_dir().expect("cannot find config dir")
.join("my-journal")
.join("config");
ConfigRef { path }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigOpt {
pub global_vault_root: Option<PathBuf>,
pub view_delimiter: Option<String>,
pub input_delimiter: Option<String>,
pub task_not_done_prefix: Option<String>,
pub task_not_done_display_prefix: Option<String>,
pub task_in_progress_prefix: Option<String>,
pub task_in_progress_display_prefix: Option<String>,
pub task_done_prefix: Option<String>,
pub task_done_display_prefix: Option<String>,
}
impl ConfigOpt {
fn deserialize(value: &str) -> Option<Self> {
toml::from_str(value).ok()
}
pub fn load(path: PathBuf) -> Option<Self> {
if path.exists() {
let toml = std::fs::read_to_string(&path).expect("cannot read config file");
Some(ConfigOpt::deserialize(toml.as_str()).expect("cannot parse config file"))
} else {
None
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub global_vault_root: PathBuf,
pub view_delimiter: String,
pub input_delimiter: String,
pub task_not_done_prefix: String,
pub task_not_done_display_prefix: String,
pub task_in_progress_prefix: String,
pub task_in_progress_display_prefix: String,
pub task_done_prefix: String,
pub task_done_display_prefix: String,
}
impl Config {
fn merge_field<T>(input: &Option<T>, output: &mut T, is_new: &mut bool)
where
T: PartialEq,
T: Clone,
{
if input.is_none() {
*is_new = true;
}
if let Some(value) = input {
if *output != *value {
*output = value.to_owned();
}
}
}
pub fn merge(base: &Config, opt: &ConfigOpt) -> (Self, bool) {
let mut result: Config = base.clone();
let mut is_new = false;
Config::merge_field(&opt.global_vault_root, &mut result.global_vault_root, &mut is_new);
Config::merge_field(&opt.view_delimiter, &mut result.view_delimiter, &mut is_new);
Config::merge_field(&opt.input_delimiter, &mut result.input_delimiter, &mut is_new);
Config::merge_field(&opt.task_not_done_prefix, &mut result.task_not_done_prefix, &mut is_new);
Config::merge_field(&opt.task_not_done_display_prefix, &mut result.task_not_done_display_prefix, &mut is_new);
Config::merge_field(&opt.task_in_progress_prefix, &mut result.task_in_progress_prefix, &mut is_new);
Config::merge_field(&opt.task_in_progress_display_prefix, &mut result.task_in_progress_display_prefix, &mut is_new);
Config::merge_field(&opt.task_done_prefix, &mut result.task_done_prefix, &mut is_new);
Config::merge_field(&opt.task_done_display_prefix, &mut result.task_done_display_prefix, &mut is_new);
(result, is_new)
}
pub fn serialize(&self) -> Option<String> {
toml::to_string_pretty(self).ok()
}
#[allow(dead_code)] pub fn deserialize(value: &str) -> Option<Self> {
toml::from_str(value).ok()
}
pub fn clone(&self) -> Self {
Config {
global_vault_root: self.global_vault_root.to_owned(),
view_delimiter: self.view_delimiter.to_owned(),
input_delimiter: self.input_delimiter.to_owned(),
task_not_done_prefix: self.task_not_done_prefix.to_owned(),
task_not_done_display_prefix: self.task_not_done_display_prefix.to_owned(),
task_in_progress_prefix: self.task_in_progress_prefix.to_owned(),
task_in_progress_display_prefix: self.task_in_progress_display_prefix.to_owned(),
task_done_prefix: self.task_done_prefix.to_owned(),
task_done_display_prefix: self.task_done_display_prefix.to_owned(),
}
}
}
impl Default for Config {
fn default() -> Self {
let global_vault_root = dirs::data_local_dir()
.expect("cannot find data local dir")
.join("my-journal");
let view_delimiter = String::from("¶");
let input_delimiter = String::from("::");
let task_not_done_prefix = String::from("-");
let task_not_done_display_prefix = String::from("☐");
let task_in_progress_prefix = String::from("*");
let task_in_progress_display_prefix = String::from("⟳");
let task_done_prefix = String::from("+");
let task_done_display_prefix = String::from("☑");
Config {
global_vault_root,
view_delimiter,
input_delimiter,
task_not_done_prefix,
task_not_done_display_prefix,
task_in_progress_prefix,
task_in_progress_display_prefix,
task_done_prefix,
task_done_display_prefix,
}
}
}
pub fn prep(matches: &ArgMatches) -> Config {
let ConfigRef { path } = match_args(matches);
let (config, write_to_disk) =
if path.exists() {
let toml = std::fs::read_to_string(&path).expect("cannot read config file");
let opt = ConfigOpt::deserialize(toml.as_str()).expect("cannot parse config file");
Config::merge(&Default::default(), &opt)
} else {
let config: Config = Default::default();
(config, true)
};
if write_to_disk {
let toml = config.serialize().expect("cannot serialize default configuration");
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("cannot create dir for configuration files");
}
std::fs::write(&path, toml.as_bytes()).expect("cannot write default config file");
}
config
}
fn match_args(matches: &ArgMatches) -> ConfigRef {
match matches.value_of("config") {
Some(value) => {
let path = Path::new(value).to_path_buf();
ConfigRef { path }
},
None => Default::default(),
}
}