use std::{collections::HashMap, fs, io::Write, path::PathBuf};
use anyhow::{Context, Result};
use chrono::Local;
use toml::{self, Value};
#[allow(dead_code)]
pub trait DefaultSettings {
const FOLDER_OUT: &'static str = "target/";
const FOLDER_ROOT: &'static str = "./";
const FOLDER_MAIN: &'static str = ".x-do/";
const FOLDER_SETTINGS: &'static str = "./.x-do/";
const FOLDER_TASKSJOB: &'static str = "jobs/";
const FOLDER_TEMPORAL: &'static str = "temp/";
fn ensure_out_exist(name_out_folder: &str) {
let mut path = PathBuf::from(Self::FOLDER_ROOT);
path.push(Self::FOLDER_OUT);
path.push(name_out_folder);
if !path.exists() {
fs::create_dir_all(&path).expect("Cannot create directory");
}
}
fn build_path_set(file: &str) -> PathBuf {
let mut path = PathBuf::from(Self::FOLDER_ROOT);
path.push(Self::FOLDER_MAIN);
path.push(Self::FOLDER_TASKSJOB);
path.push(file);
path
}
fn build_path_tmp(file: &str) -> PathBuf {
let mut path = PathBuf::from(Self::FOLDER_ROOT);
path.push(Self::FOLDER_MAIN);
path.push(Self::FOLDER_TEMPORAL);
let timestamp = Local::now().format("%Y%m%d%H%M%S%3f"); let prefix_file = format!("{}_{}", timestamp, file);
path.push(prefix_file);
path
}
fn init_if_not_exist(file: &str, template: &str) {
let path = Self::build_path_set(file);
println!("init_if_not_exist: {:?}", path);
if !path.exists() {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("Cannot create directories");
}
let mut f = fs::File::create(&path).expect("Cannot create file");
f.write_all(template.as_bytes()).expect("Cannot write template");
println!("created: {:?}", path);
} else {
println!("exists (skipped): {:?}", path);
}
}
fn init_clear_safest(file: &str, template: &str) {
let path = Self::build_path_set(file);
let temp = Self::build_path_tmp(file);
println!("init_clear_safest: {:?}", path);
if path.exists() {
if let Some(parent) = temp.parent() {
fs::create_dir_all(parent).expect("Cannot create temp directories");
}
fs::rename(&path, &temp).expect("Cannot move existing file to temp");
println!("moved existing file to temp: {:?}", temp);
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("Cannot create directories");
}
let mut f = fs::File::create(&path).expect("Cannot create file");
f.write_all(template.as_bytes()).expect("Cannot write template");
println!("fresh init: {:?}", path);
}
fn init_clear_forced(file: &str, template: &str) {
let path = Self::build_path_set(file);
println!("init_clear_forced: {:?}", path);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("Cannot create directories");
}
let mut f = fs::File::create(&path).expect("Cannot overwrite file");
f.write_all(template.as_bytes()).expect("Cannot write template");
println!("overwritten: {:?}", path);
}
fn get_one_by_key(file: &str, key: &str) -> Result<Value> {
let path = Self::build_path_set(file);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
if let Some(val) = value.get(key) {
Ok(val.clone())
} else {
anyhow::bail!("Key '{}' not found in {:?}", key, path);
}
}
fn get_all_by_all_key(file: &str, keys: &[&str]) -> Result<Vec<(String, Value)>> {
let path = Self::build_path_set(file);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
let mut results = Vec::new();
for &key in keys {
if let Some(val) = value.get(key) {
results.push((key.to_string(), val.clone()));
} else {
anyhow::bail!("Key '{}' not found in {:?}", key, path);
}
}
Ok(results)
}
fn get_one_by_anchor_and_key(
file: &str,
anchor: &str,
key_value: &str, key_is_id: bool, ) -> Result<Value> {
let path = Self::build_path_set(file);
println!("get_one_get_one_get_one_by_anchor_and_key: {:?}", path);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
let anchor_key = anchor.trim_matches(|c| c == '[' || c == ']');
if let Some(items) = value.get(anchor_key)
&& let Value::Array(arr) = items
{
let field_to_search = if key_is_id { "id" } else { "name" };
for item in arr {
if let Value::Table(table) = item
&& let Some(Value::String(s)) = table.get(field_to_search)
&& s == key_value
{
return Ok(item.clone());
}
}
}
anyhow::bail!(
"No anchor {} with {}={} found in {:?}",
anchor,
if key_is_id { "id" } else { "name" },
key_value,
path
);
}
fn get_all_by_anchor(file: &str, anchor: &str) -> Result<Vec<Value>> {
let path = Self::build_path_set(file);
println!("get_all_by_anchor: {:?}", path);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
let anchor_key = anchor.trim_matches(|c| c == '[' || c == ']');
if let Some(items) = value.get(anchor_key)
&& let Value::Array(arr) = items
{
return Ok(arr.clone());
}
anyhow::bail!("No anchor {} found in {:?}", anchor, path);
}
fn get_all_by_all_anchor(file: &str, anchors: &[&str]) -> Result<Vec<Value>> {
let path = Self::build_path_set(file);
println!("get_all_by_all_anchor: {:?}", path);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
let mut results = Vec::new();
for &anchor in anchors {
let anchor_key = anchor.trim_matches(|c| c == '[' || c == ']');
if let Some(items) = value.get(anchor_key)
&& let Value::Array(arr) = items
{
results.extend(arr.clone()); }
}
if results.is_empty() {
anyhow::bail!("No anchors {:?} found in {:?}", anchors, path);
}
Ok(results)
}
fn load_anchors_list(
file: &str,
anchors: &[&str],
keys_by_anchors: &[&str],
) -> Result<Vec<HashMap<String, String>>> {
let path = Self::build_path_set(file);
println!("load_anchors_list: {:?}", path);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: toml::Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
let mut results = Vec::new();
for &anchor in anchors {
let key = anchor.trim_matches(|c| c == '[' || c == ']');
if let Some(items) = value.get(key)
&& let toml::Value::Array(arr) = items
{
for item in arr {
let mut map = HashMap::new();
if let toml::Value::Table(table) = item {
for &k in keys_by_anchors {
if let Some(v) = table.get(k) {
let s = match v {
toml::Value::String(s) => s.clone(),
toml::Value::Integer(i) => i.to_string(),
toml::Value::Float(f) => f.to_string(),
toml::Value::Boolean(b) => b.to_string(),
_ => continue, };
map.insert(k.to_string(), s);
}
}
}
results.push(map);
}
}
}
Ok(results)
}
fn plot_anchors_list(file: &str, anchors: &[&str], keys_by_anchors: &[&str]) -> Result<()> {
let path = Self::build_path_set(file);
println!("plot_anchors_list: {:?}", path);
let content =
fs::read_to_string(&path).with_context(|| format!("Cannot read file {:?}", path))?;
let value: Value = toml::from_str(&content)
.with_context(|| format!("Cannot parse TOML from {:?}", path))?;
for &anchor in anchors {
let key = anchor.trim_matches(|c| c == '[' || c == ']');
if let Some(items) = value.get(key)
&& let Value::Array(arr) = items
{
for item in arr {
if let Value::Table(table) = item {
let mut map = HashMap::new();
for &k in keys_by_anchors {
if let Some(v) = table.get(k) {
let s = match v {
Value::String(s) => s.clone(),
Value::Integer(i) => i.to_string(),
Value::Float(f) => f.to_string(),
Value::Boolean(b) => b.to_string(),
_ => continue,
};
map.insert(k.to_string(), s);
}
}
println!("{:?}", map); }
}
}
}
Ok(())
}
}