sailfish/sailfish-compiler/src/config.rs

228 lines
6.8 KiB
Rust
Raw Normal View History

2020-06-14 16:36:39 -04:00
use crate::error::*;
use std::fs;
2020-06-05 22:58:14 -04:00
use std::path::{Path, PathBuf};
2020-06-14 16:36:39 -04:00
use yaml_rust::yaml::{Yaml, YamlLoader};
2020-06-05 22:58:14 -04:00
#[derive(Clone, Debug)]
pub struct Config {
pub delimiter: char,
pub escape: bool,
2020-06-06 18:01:15 -04:00
pub rm_whitespace: bool,
2020-06-14 16:36:39 -04:00
pub template_dirs: Vec<PathBuf>,
#[doc(hidden)]
pub cache_dir: PathBuf,
2020-06-05 22:58:14 -04:00
#[doc(hidden)]
pub _non_exhaustive: (),
2020-06-05 22:58:14 -04:00
}
impl Default for Config {
fn default() -> Self {
Self {
2020-06-14 16:36:39 -04:00
template_dirs: Vec::new(),
2020-06-05 22:58:14 -04:00
delimiter: '%',
escape: true,
cache_dir: Path::new(env!("OUT_DIR")).join("cache"),
2020-06-06 18:01:15 -04:00
rm_whitespace: false,
_non_exhaustive: (),
2020-06-05 22:58:14 -04:00
}
}
}
2020-06-14 16:36:39 -04:00
impl Config {
pub fn search_file_and_read(base: &Path) -> Result<Config, Error> {
// search config file
let mut path = PathBuf::new();
let mut config = Config::default();
for component in base.iter() {
path.push(component);
path.push("sailfish.yml");
if path.is_file() {
let config_file =
ConfigFile::read_from_file(&*path).map_err(|mut e| {
e.source_file = Some(path.to_owned());
e
})?;
if let Some(template_dirs) = config_file.template_dirs {
for template_dir in template_dirs.into_iter().rev() {
if template_dir.is_absolute() {
config.template_dirs.push(template_dir);
} else {
config
.template_dirs
.push(path.parent().unwrap().join(template_dir));
}
}
}
if let Some(delimiter) = config_file.delimiter {
config.delimiter = delimiter;
}
if let Some(escape) = config_file.escape {
config.escape = escape;
}
if let Some(rm_whitespace) = config_file.rm_whitespace {
config.rm_whitespace = rm_whitespace;
}
}
path.pop();
}
Ok(config)
}
}
#[derive(Default)]
struct ConfigFile {
template_dirs: Option<Vec<PathBuf>>,
delimiter: Option<char>,
escape: Option<bool>,
rm_whitespace: Option<bool>,
}
impl ConfigFile {
fn read_from_file(path: &Path) -> Result<Self, Error> {
let mut config = Self::default();
let content = fs::read_to_string(path)
.chain_err(|| format!("Failed to read configuration file {:?}", path))?;
let entries = YamlLoader::load_from_str(&*content)
.map_err(|e| ErrorKind::ConfigError(e.to_string()))?;
drop(content);
for entry in entries {
config.visit_global(entry)?
}
Ok(config)
}
fn visit_global(&mut self, entry: Yaml) -> Result<(), Error> {
let hash = entry.into_hash().ok_or_else(|| {
ErrorKind::ConfigError("Invalid configuration format".to_owned())
})?;
for (k, v) in hash {
match k {
Yaml::String(ref s) => match &**s {
"template_dir" => self.visit_template_dir(v)?,
"delimiter" => self.visit_delimiter(v)?,
"escape" => self.visit_escape(v)?,
"optimization" => self.visit_optimization(v)?,
_ => return Err(Self::error(format!("Unknown key ({})", s))),
},
_ => {
return Err(Self::error("Invalid configuration format"));
}
}
}
Ok(())
}
fn visit_template_dir(&mut self, value: Yaml) -> Result<(), Error> {
if self.template_dirs.is_some() {
return Err(Self::error("Duplicate key (template_dir)"));
}
match value {
Yaml::String(s) => self.template_dirs = Some(vec![PathBuf::from(s)]),
Yaml::Array(v) => {
let mut template_dirs = Vec::new();
for e in v {
if let Yaml::String(s) = e {
template_dirs.push(PathBuf::from(s));
} else {
return Err(Self::error(
"Arguments of `template_dir` must be string",
));
}
}
self.template_dirs = Some(template_dirs);
}
_ => {
return Err(Self::error("Arguments of `template_dir` must be string"));
}
}
Ok(())
}
fn visit_delimiter(&mut self, value: Yaml) -> Result<(), Error> {
if self.delimiter.is_some() {
return Err(Self::error("Duplicate key (delimiter)"));
}
if let Yaml::String(s) = value {
if s.chars().count() == 1 {
self.delimiter = Some(s.chars().next().unwrap());
Ok(())
} else {
Err(Self::error("`escape` must be single character"))
}
} else {
Err(Self::error("`escape` must be single character"))
}
}
fn visit_escape(&mut self, value: Yaml) -> Result<(), Error> {
if self.escape.is_some() {
return Err(Self::error("Duplicate key (escape)"));
}
if let Yaml::Boolean(b) = value {
self.escape = Some(b);
Ok(())
} else {
Err(Self::error("`escape` must be boolean"))
}
}
fn visit_optimization(&mut self, entry: Yaml) -> Result<(), Error> {
let hash = entry.into_hash().ok_or_else(|| {
ErrorKind::ConfigError("Invalid configuration format".to_owned())
})?;
for (k, v) in hash {
match k {
Yaml::String(ref s) => match &**s {
"rm_whitespace" => self.visit_rm_whitespace(v)?,
_ => {
return Err(Self::error(format!(
"Unknown key (optimization.{})",
s
)));
}
},
_ => {
return Err(Self::error("Invalid configuration format"));
}
}
}
Ok(())
}
fn visit_rm_whitespace(&mut self, value: Yaml) -> Result<(), Error> {
if self.rm_whitespace.is_some() {
return Err(Self::error("Duplicate key (rm_whitespace)"));
}
if let Yaml::Boolean(b) = value {
self.rm_whitespace = Some(b);
Ok(())
} else {
Err(Self::error("`rm_whitespace` must be boolean"))
}
}
fn error<T: Into<String>>(msg: T) -> Error {
make_error!(ErrorKind::ConfigError(msg.into()))
}
}