toddi 0.2.2

A TODO focuser built on top of todo.txt
Documentation
use std::path::PathBuf;

use anyhow::{anyhow, Context, Result};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct Config {
    pub todo_file: PathBuf,
    pub done_file: PathBuf,
}

static TODO_FILE: &str = "todo.txt";
static DONE_FILE: &str = "done.txt";

impl Config {
    fn new() -> Result<Self> {
        let mut todo_txt_path = std::path::PathBuf::new();
        let mut done_txt_path = std::path::PathBuf::new();
        set_default_path(&mut todo_txt_path, TODO_FILE)?;
        set_default_path(&mut done_txt_path, DONE_FILE)?;
        Ok(Config {
            todo_file: todo_txt_path,
            done_file: done_txt_path,
        })
    }

    pub fn load(config_file_path: Option<PathBuf>) -> Result<Config> {
        let mut file_path: PathBuf;
        if let Some(file) = config_file_path.as_deref() {
            file_path = file.to_path_buf();
        } else {
            file_path = get_configdir()?;
            file_path.push("toddi");
            file_path.push("config.toml");

            match std::fs::exists(&file_path) {
                Ok(value) => {
                    if !value {
                        println!("Default configuration file does not exist.");
                        match Config::new() {
                            Ok(config) => {
                                match toml::to_string(&config) {
                                    Ok(conf_str) => {
                                        if let Some(parent_dir) = file_path.parent() {
                                            if !parent_dir.exists() {
                                                std::fs::create_dir_all(parent_dir)?;
                                            }
                                        }
                                        println!(
                                            "Writing default configuration file: {}",
                                            file_path.display()
                                        );
                                        std::fs::write(&file_path, &conf_str).with_context(
                                            || {
                                                format!(
                                                    "Could not write file `{}`\nwith config: {}",
                                                    file_path.display(),
                                                    &conf_str
                                                )
                                            },
                                        )?;
                                    }
                                    Err(err) => {
                                        return Err(anyhow!(
                                            "Could not serialize default configuration!\nError: {:?}",
                                            err
                                        ));
                                    }
                                };
                            }
                            Err(err) => {
                                return Err(anyhow!(
                                    "Could not load configuration!\nError: {:?}",
                                    err
                                ))
                            }
                        };
                    }
                }
                Err(err) => {
                    return Err(anyhow!(
                        "Could not assess whether configuration file exists or not!\nError: {:?}",
                        err
                    ))
                }
            }
        }

        let config_file = std::fs::read_to_string(&file_path)
            .with_context(|| format!("could not read file `{}`", file_path.display()))?;
        match toml::from_str(&config_file) {
            Ok(parsed_config) => {
                let parsed_config: Config = parsed_config;
                Ok(parsed_config)
            }
            Err(err) => Err(anyhow!("Could not parse configuration!\nError: {:?}", err)),
        }
    }
}

fn set_default_path(path_buffer: &mut PathBuf, filename: &str) -> Result<()> {
    let home_folder = get_homedir()?;
    let default_folder = ".todo-txt";
    path_buffer.push(&home_folder);
    path_buffer.push(default_folder);
    path_buffer.push(filename);
    Ok(())
}

fn get_homedir() -> Result<PathBuf> {
    if let Some(dir) = dirs::home_dir() {
        Ok(dir)
    } else {
        Err(anyhow!(
            "Issue with `dirs` crate: could not fetch home directory."
        ))
    }
}

fn get_configdir() -> Result<PathBuf> {
    if let Some(dir) = dirs::config_dir() {
        Ok(dir)
    } else {
        Err(anyhow!(
            "Issue with `dirs` crate: could not fetch configuration directory."
        ))
    }
}