wearte_config 0.0.2

Config file parser of wearte
Documentation
//!
//! `wearte_config` is the crate in charge of parsing the configure file, `wearte.toml`,
//! to a rust object that will later be treated in the rest of the crates.
//! Right now a wearte configuration file can have the following:
//!
//! - **`main`** (general configuration - optional): with attribute
//!   - **`dir`**: name of template directory. If no value is given, a default directory
//! **`templates`** will be used. If the defined directory is not found, an error
//! will prompt.
//!   - **`debug`**: type of output of debug mode. The code and/or  ast generated by  wearte
//! can be visualize, to do so, at most one of three possible values has to be given:
//! `code`, `ast`, or `all`.
//!
//! - **`partials`** (partials aliasing - optional): each entry must be of the type
//! `name_alias = "./alias/path/"`, where `./` makes reference to `dir` value. Path
//! must exist, or error will be prompt. If the tag `partials` doesn't exist no aliasing
//! will be possible.
//!
//! - **`debug`** (debugging configuration - optional): in order to visualize clearly generated code
//! in a debugging environment wearte gives it a tabulated format, and the possibility
//! to see the number line use a color theme. Options are the following:
//!
//!   - **`number_line`** (default:  `false`): Boolean, if set to `true` number lines will appear
//! in debug-mode.
//!   - **`theme`** (default: `zenburn`): String, color theme used in debugging environment.
//! Possible values are:
//!     - `DarkNeon`,
//!     - `GitHub`,
//!     - `Monokai Extended`,
//!     - `Monokai Extended Bright`,
//!     - `Monokai Extended Light`,
//!     - `Monokai Extended Origin`,
//!     - `OneHalfDark`,
//!     - `OneHalfLight`,
//!     - `Sublime Snazzy`,
//!     - `TwoDark`,
//!     - `zenburn`
//!
//! In future versions, more features will be added to the configuration file.
//!
//! ### Example of a config file
//! ```toml
//! [main]
//! dir = "templates"
//! debug = "all"
//!
//! [partials]
//! alias = "./deep/more/deep"
//!
//! [debug]
//! theme = "zenburn"
//! number_line = true
//! ```
//!
//! With this configuration, the user can call `alias` in a partial instance with
//! `{{> alias context}}` or `{{> alias}}` if the current context is well defined.
//!
#[macro_use]
extern crate serde_derive;

use std::collections::BTreeMap;
use std::{
    env, fs,
    path::{Path, PathBuf},
};

#[derive(Debug)]
pub struct Dir(PathBuf);

impl Dir {
    pub fn get_template(&self, path: PathBuf) -> PathBuf {
        if path.exists() {
            return path;
        }

        let template = self.0.join(path);
        if template.exists() {
            template
        } else {
            panic!("template not found in directory {:?}", self.0)
        }
    }
}

impl From<Option<&str>> for Dir {
    fn from(p: Option<&str>) -> Self {
        let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        Dir(p.map_or_else(|| root.join(DEFAULT_DIR), |v| root.join(v)))
    }
}

#[derive(Debug, PartialEq)]
pub enum PrintConfig {
    All,
    Ast,
    Code,
    None,
}

impl From<Option<&str>> for PrintConfig {
    fn from(s: Option<&str>) -> Self {
        match s {
            Some("all") => PrintConfig::All,
            Some("ast") => PrintConfig::Ast,
            Some("code") => PrintConfig::Code,
            _ => PrintConfig::None,
        }
    }
}

#[derive(Debug)]
pub struct Config<'a> {
    dir: Dir,
    alias: BTreeMap<&'a str, &'a str>,
    pub print_override: PrintConfig,
    pub debug: PrintOption<'a>,
}

impl<'a> Config<'a> {
    pub fn new(s: &str) -> Config {
        let raw: RawConfig =
            toml::from_str(&s).expect(&format!("invalid TOML in {}", CONFIG_FILE_NAME));
        let (dir, print) = raw.main.map(|x| (x.dir, x.debug)).unwrap_or((None, None));

        Config {
            dir: Dir::from(dir),
            print_override: PrintConfig::from(print),
            debug: raw.debug.unwrap_or_default(),
            alias: raw.partials.unwrap_or(BTreeMap::new()),
        }
    }

    pub fn get_dir(&self) -> &PathBuf {
        &self.dir.0
    }

    pub fn get_template(&self, ident: &str) -> (PathBuf, String) {
        let path = self.dir.get_template(PathBuf::from(ident));
        let src = get_source(path.as_path());
        (path, src)
    }

    pub fn get_partial(&self, parent: &Path, ident: &str) -> (PathBuf, String) {
        let path = self.resolve_partial(parent, ident);
        let src = get_source(path.as_path());
        (path, src)
    }

    pub fn resolve_partial(&self, parent: &Path, ident: &str) -> PathBuf {
        let mut name = None;
        for (k, v) in &self.alias {
            if ident.starts_with(k) {
                name = Some(PathBuf::from([v, &ident[k.len()..]].join("")));
                break;
            }
        }

        let (mut buf, is_alias) = name.map_or((PathBuf::from(ident), false), |s| (s, true));

        if buf.extension().is_none() {
            if let Some(ext) = parent.extension() {
                buf = buf.with_extension(ext);
            }
        };

        if is_alias {
            self.dir
                .get_template(buf)
                .canonicalize()
                .expect("valid path to partial")
        } else {
            let mut parent = parent.to_owned();
            parent.pop();
            parent.push(buf);
            parent.canonicalize().expect("valid path to partial")
        }
    }
}

#[derive(Deserialize)]
struct RawConfig<'a> {
    #[serde(borrow)]
    main: Option<Main<'a>>,
    #[serde(borrow)]
    debug: Option<PrintOption<'a>>,
    #[serde(borrow)]
    partials: Option<BTreeMap<&'a str, &'a str>>,
}

#[derive(Deserialize)]
struct Main<'a> {
    #[serde(borrow)]
    dir: Option<&'a str>,
    #[serde(borrow)]
    debug: Option<&'a str>,
}

#[derive(Debug, Deserialize)]
pub struct PrintOption<'a> {
    #[serde(borrow)]
    pub theme: Option<&'a str>,
    pub number_line: Option<bool>,
}

impl<'a> Default for PrintOption<'a> {
    fn default() -> Self {
        Self {
            theme: None,
            number_line: None,
        }
    }
}

pub fn read_config_file() -> String {
    let filename = config_file_path();
    if filename.exists() {
        fs::read_to_string(&filename)
            .expect(&format!("unable to read {}", filename.to_str().unwrap()))
    } else {
        "".to_string()
    }
}

#[inline]
pub fn config_file_path() -> PathBuf {
    PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join(CONFIG_FILE_NAME)
}

fn get_source(path: &Path) -> String {
    match fs::read_to_string(path) {
        Err(_) => panic!("unable to open template file '{:?}'", path),
        Ok(mut source) => match source
            .as_bytes()
            .iter()
            .enumerate()
            .rev()
            .find_map(|(j, x)| {
                if x.is_ascii_whitespace() {
                    None
                } else {
                    Some(j)
                }
            }) {
            Some(j) => {
                source.drain(j + 1..);
                source
            }
            None => source,
        },
    }
}

static CONFIG_FILE_NAME: &str = "wearte.toml";
static DEFAULT_DIR: &str = "templates";