crax 0.1.8

An interesting CLI for frontend programmer
Documentation
use crate::cli::open::core::{Open, OpenOption};
use crate::log::Log;
use crate::tpl::doc::INIT_CONFIG;
use crate::utils::conf::read;
use anyhow::anyhow;
use dyn_fmt::AsStrFormatExt;
use owo_colors::OwoColorize;
use serde::{Deserialize, Serialize};

use std::fs;
use std::{
    collections::BTreeMap,
    io::{Error, ErrorKind},
    path::{Path, PathBuf},
};

#[derive(Deserialize, Debug, Serialize)]
pub struct Config {
    pub loc: Option<PathBuf>,
    pub map: BTreeMap<String, Open>,
}

impl Config {
    pub fn new(loc: Option<String>) -> anyhow::Result<Self> {
        match loc {
            Some(loc) => {
                let path = Path::new(&loc).to_path_buf();

                if path.is_file() {
                    Ok(read(&path)?)
                } else {
                    Err(anyhow::Error::msg(
                        Log::Warn(&"Can not find config file by {}".format(&[path.display()]))
                            .to_string(),
                    ))
                }
            }
            None => {
                let home = dirs::home_dir();

                if let Some(home) = home {
                    Ok(read(&home.join(".doc.toml"))?)
                } else {
                    Err(anyhow!("Can not find home dir."))
                }
            }
        }
    }

    pub fn new_empty() -> Config {
        Config { loc: None, map: BTreeMap::new() }
    }

    pub fn save(&self) -> anyhow::Result<()> {
        let Config { loc, map } = self;
        let mut content = String::from(INIT_CONFIG);

        if !map.is_empty() {
            content = self.to_string()?
        }

        if let Some(loc) = loc {
            fs::write(loc, content)?
        }

        Ok(())
    }

    pub fn to_string(&self) -> Result<String, Error> {
        toml::to_string(self).map_err(|err| Error::new(ErrorKind::InvalidData, err))
    }

    pub fn open(&self, name: &str, option: &OpenOption) -> anyhow::Result<()> {
        match self.find(name) {
            Some(doc) => {
                doc.open(option)?;
                Ok(())
            }
            None => {
                Log::Warn(
                    &"Can not find any doc by '{}', similar docs found below:".format(&[name]),
                )
                .println();

                self.walk(|n, doc| {
                    if doc.contains(n, &name[0..1]) {
                        println!("{}", doc.get_printed_name(n).green());
                    }
                });
                Ok(())
            }
        }
    }

    pub fn view(&self, name: &str, detail: bool) {
        if let Some(doc) = self.map.get(name) {
            doc.view(name, detail);
        }
    }

    // TODO remove it ??
    pub fn walk<T>(&self, mut call: T)
    where
        T: FnMut(&String, &Open),
    {
        // BTree has already sorted
        for (n, doc) in &self.map {
            call(n, doc)
        }
    }

    pub fn find(&self, name: &str) -> Option<&Open> {
        let mut doc_name = String::new();

        match self.map.get(name) {
            Some(_) => doc_name = name.to_string(),
            None => {
                self.walk(|n, doc| {
                    if let Open { full: Some(full), .. } = doc {
                        if full == name {
                            doc_name = n.to_string()
                        }
                    }
                });
            }
        }
        if doc_name.is_empty() {
            None
        } else {
            Some(self.map.get(&doc_name).unwrap())
        }
    }
}

impl From<(PathBuf, String)> for Config {
    fn from(value: (PathBuf, String)) -> Self {
        let (loc, str) = value;
        Config {
            loc: Some(loc),
            map: if let Ok(config) = toml::from_str(&str) { config } else { BTreeMap::new() },
        }
    }
}