use std::iter::Iterator;
use rustc_serialize::{Decodable};
use super::types::*;
use super::vars;
use super::utils;
use toml::{Parser, Value, Table, Decoder};
#[derive(Debug, RustcEncodable, RustcDecodable)]
pub struct RawArtifact {
pub partof: Option<String>,
pub text: Option<String>,
}
#[derive(Debug, RustcEncodable, RustcDecodable)]
pub struct RawSettings {
pub disabled: Option<bool>,
pub artifact_paths: Option<Vec<String>>,
pub code_paths: Option<Vec<String>>,
pub exclude_code_paths: Option<Vec<String>>,
pub color: Option<bool>,
}
lazy_static!{
pub static ref ARTIFACT_ATTRS: HashSet<String> = HashSet::from_iter(
["disabled", "text", "partof"].iter().map(|s| s.to_string()));
pub static ref SETTINGS_ATTRS: HashSet<String> = HashSet::from_iter(
["disabled", "artifact_paths",
"code_paths", "exclude_code_paths"].iter().map(|s| s.to_string()));
}
macro_rules! get_attr {
($tbl: expr, $attr: expr, $default: expr, $ty: ident) => {
match $tbl.get($attr) {
Some(&Value::$ty(ref v)) => Some(v.clone()),
None => Some($default.clone()),
_ => None,
}
}
}
pub fn get_vecstr(tbl: &Table, attr: &str, default: &[String]) -> Option<Vec<String>> {
match tbl.get(attr) {
Some(&Value::Array(ref a)) => {
let mut out: Vec<String> = Vec::with_capacity(a.len());
for v in a {
match *v {
Value::String(ref s) => out.push(s.clone()),
_ => return None, }
}
Some(out)
}
None => Some(Vec::from(default)), _ => None, }
}
macro_rules! check_type {
($value: expr, $attr: expr, $name: expr) => {
match $value {
Some(v) => v,
None => {
let mut msg = Vec::new();
write!(&mut msg, "{} has invalid attribute: {}", $name, $attr).unwrap();
return Err(LoadError::new(String::from_utf8(msg).unwrap()));
}
}
}
}
#[cfg(not(windows))]
fn get_color(raw: &RawSettings) -> bool {
raw.color.unwrap_or(true)
}
#[cfg(windows)]
fn get_color(raw: &RawSettings) -> bool {
false
}
impl Settings {
pub fn from_table(tbl: &Table) -> LoadResult<Settings> {
let value = Value::Table(tbl.clone());
let mut decoder = Decoder::new(value);
let raw = match RawSettings::decode(&mut decoder) {
Ok(v) => v,
Err(e) => return Err(LoadError::new(format!(
"error loading settings: {}", e))),
};
if let Some(invalid) = decoder.toml {
let msg = format!("Invalid attributes in settings: {:?}", invalid);
return Err(LoadError::new(msg));
}
fn to_paths(paths: &Option<Vec<String>>) -> VecDeque<PathBuf> {
match *paths {
Some(ref p) => p.iter().map(PathBuf::from).collect(),
None => VecDeque::new(),
}
}
Ok(Settings {
disabled: raw.disabled.unwrap_or_default(),
paths: to_paths(&raw.artifact_paths),
code_paths: to_paths(&raw.code_paths),
exclude_code_paths: to_paths(&raw.exclude_code_paths),
color: get_color(&raw),
})
}
}
fn parse_toml(toml: &str) -> LoadResult<Table> {
let mut parser = Parser::new(toml);
match parser.parse() {
Some(table) => Ok(table),
None => {
let mut msg = String::new();
for e in &parser.errors {
let (line, col) = parser.to_linecol(e.lo);
write!(msg, "[{}:{}] {}, ", line, col, e.desc).unwrap();
}
Err(LoadError::new(msg))
}
}
}
impl Artifact {
pub fn from_str(toml: &str) -> LoadResult<(ArtNameRc, Artifact)> {
let table = try!(parse_toml(toml));
if table.len() != 1 {
return Err(LoadError::new("must contain a single table".to_string()));
}
let (name, value) = table.iter().next().unwrap();
let name = try!(ArtName::from_str(name));
let value = match *value {
Value::Table(ref t) => t,
_ => return Err(LoadError::new("must contain a single table".to_string())),
};
let artifact = try!(Artifact::from_table(&name, &Path::new("from_str"), value));
Ok((Rc::new(name), artifact))
}
fn from_table(name: &ArtName, path: &Path, tbl: &Table) -> LoadResult<Artifact> {
let value = Value::Table(tbl.clone());
let mut decoder = Decoder::new(value);
let raw = match RawArtifact::decode(&mut decoder) {
Ok(v) => v,
Err(e) => return Err(LoadError::new(format!(
"{} has invalid attribute type: {}", name, e))),
};
if let Some(invalid) = decoder.toml {
return Err(LoadError::new(format!(
"{} has invalid attributes: {:?}", name, invalid)));
}
Ok(Artifact {
path: path.to_path_buf(),
text: raw.text.unwrap_or_default(),
partof: try!(ArtNames::from_str(
&raw.partof.unwrap_or_default())),
loc: None,
parts: HashSet::new(),
completed: -1.0,
tested: -1.0,
})
}
}
pub fn load_file_table(file_table: &mut Table,
path: &Path,
artifacts: &mut Artifacts,
settings: &mut Vec<(PathBuf, Settings)>,
variables: &mut Vec<(PathBuf, Variables)>)
-> LoadResult<u64> {
let mut msg: Vec<u8> = Vec::new();
let mut num_loaded: u64 = 0;
match file_table.remove("settings") {
Some(Value::Table(t)) => {
let lset = try!(Settings::from_table(&t));
if lset.disabled {
return Ok(0);
}
settings.push((path.to_path_buf(), lset));
}
None => {}
_ => return Err(LoadError::new("settings must be a Table".to_string())),
}
match file_table.remove("globals") {
Some(Value::Table(t)) => {
let mut lvars = Variables::new();
for (k, v) in t {
if vars::DEFAULT_GLOBALS.contains(k.as_str()) {
return Err(LoadError::new("cannot use variables: repo, cwd".to_string()));
}
lvars.insert(k.clone(),
match v {
Value::String(s) => s.to_string(),
_ => {
return Err(LoadError::new(k.to_string() +
" global var must be of type str"))
}
});
}
variables.push((path.to_path_buf(), lvars));
}
None => {}
_ => return Err(LoadError::new("globals must be a Table".to_string())),
}
for (name, value) in file_table.iter() {
let aname = try!(ArtName::from_str(name));
let art_tbl: &Table = match *value {
Value::Table(ref t) => t,
_ => {
write!(&mut msg, "All top-level values must be a table: {}", name).unwrap();
return Err(LoadError::new(String::from_utf8(msg).unwrap()));
}
};
if let Some(overlap) = artifacts.get(&aname) {
write!(&mut msg,
"Overlapping key found <{}> other key at: {}",
name,
overlap.path.display())
.unwrap();
return Err(LoadError::new(String::from_utf8(msg).unwrap()));
}
if check_type!(get_attr!(art_tbl, "disabled", false, Boolean),
"disabled",
name) {
continue;
}
let artifact = try!(Artifact::from_table(&aname, path, art_tbl));
artifacts.insert(Rc::new(aname), artifact);
num_loaded += 1;
}
Ok(num_loaded)
}
pub fn load_toml_simple(text: &str) -> Artifacts {
let mut artifacts = Artifacts::new();
let mut settings: Vec<(PathBuf, Settings)> = Vec::new();
let mut variables: Vec<(PathBuf, Variables)> = Vec::new();
let path = PathBuf::from("test");
load_toml(&path, text, &mut artifacts, &mut settings, &mut variables).unwrap();
artifacts
}
pub fn load_toml(path: &Path,
text: &str,
artifacts: &mut Artifacts,
settings: &mut Vec<(PathBuf, Settings)>,
variables: &mut Vec<(PathBuf, Variables)>)
-> LoadResult<u64> {
let mut table = try!(parse_toml(text));
load_file_table(&mut table, path, artifacts, settings, variables)
}
pub fn load_file(path: &Path,
artifacts: &mut Artifacts,
settings: &mut Vec<(PathBuf, Settings)>,
variables: &mut Vec<(PathBuf, Variables)>)
-> LoadResult<u64> {
let mut text = String::new();
let mut fp = fs::File::open(path).unwrap();
try!(fp.read_to_string(&mut text).or_else(|err| {
let mut msg = String::new();
write!(msg, "Error loading path {:?}: {}", path, err).unwrap();
Err(LoadError::new(msg))
}));
load_toml(path, &text, artifacts, settings, variables)
}
pub fn load_dir(path: &Path,
loaded_dirs: &mut HashSet<PathBuf>,
artifacts: &mut Artifacts,
settings: &mut Vec<(PathBuf, Settings)>,
variables: &mut Vec<(PathBuf, Variables)>)
-> LoadResult<u64> {
loaded_dirs.insert(path.to_path_buf());
let mut num_loaded: u64 = 0;
let mut error = false;
let mut dirs_to_load: Vec<PathBuf> = Vec::new();
let read_dir = match fs::read_dir(path) {
Ok(d) => d,
Err(err) => return Err(LoadError::new("E001: ".to_string() + &err.to_string())),
};
for entry in read_dir.filter_map(|e| e.ok()) {
let fpath = entry.path();
let ftype = match entry.file_type() {
Ok(f) => f,
Err(err) => {
error!("while loading from <{}>: {}", fpath.display(), err);
error = true;
continue;
}
};
if ftype.is_dir() {
dirs_to_load.push(fpath.clone());
} else if ftype.is_file() {
let ext = match fpath.extension() {
None => continue,
Some(ext) => ext,
};
if ext != "toml" {
continue;
}
match load_file(fpath.as_path(), artifacts, settings, variables) {
Ok(n) => num_loaded += n,
Err(err) => {
error!("while loading from <{}>: {}", fpath.display(), err);
error = true;
}
};
}
}
if num_loaded > 0 {
for dir in dirs_to_load {
if loaded_dirs.contains(dir.as_path()) {
continue;
}
match load_dir(dir.as_path(), loaded_dirs, artifacts, settings, variables) {
Ok(n) => num_loaded += n,
Err(_) => error = true,
}
}
}
if error {
Err(LoadError::new("ERROR: some files failed to load".to_string()))
} else {
Ok(num_loaded)
}
}
pub fn resolve_settings(settings: &mut Settings,
repo_map: &mut HashMap<PathBuf, PathBuf>,
loaded_settings: &[(PathBuf, Settings)])
-> LoadResult<()> {
let mut vars: HashMap<String, String> = HashMap::new();
for ps in loaded_settings.iter() {
let settings_item: &Settings = &ps.1;
let fpath = ps.0.clone();
let cwd = fpath.parent().unwrap();
let cwd_str = try!(utils::get_path_str(cwd));
vars.insert("cwd".to_string(), cwd_str.to_string());
try!(utils::find_and_insert_repo(cwd, repo_map));
let repo = repo_map.get(cwd).unwrap();
vars.insert("repo".to_string(),
try!(utils::get_path_str(repo.as_path())).to_string());
for p in &settings_item.paths {
let p = try!(utils::do_strfmt(p.to_str().unwrap(), &vars, &fpath));
settings.paths.push_back(PathBuf::from(p));
}
for p in &settings_item.code_paths {
let p = try!(utils::do_strfmt(p.to_str().unwrap(), &vars, &fpath));
settings.code_paths.push_back(PathBuf::from(p));
}
for p in &settings_item.exclude_code_paths {
let p = try!(utils::do_strfmt(p.to_str().unwrap(), &vars, &fpath));
settings.exclude_code_paths.push_back(PathBuf::from(p));
}
}
Ok(())
}
#[allow(type_complexity)] pub fn load_raw(path: &Path)
-> LoadResult<(Artifacts,
Settings,
Vec<(PathBuf, Variables)>,
HashMap<PathBuf, PathBuf>)> {
let mut artifacts = Artifacts::new();
let mut settings = Settings::new();
let mut loaded_dirs: HashSet<PathBuf> = HashSet::new(); let mut loaded_settings: Vec<(PathBuf, Settings)> = Vec::new();
let mut loaded_vars: Vec<(PathBuf, Variables)> = Vec::new();
let mut repo_map: HashMap<PathBuf, PathBuf> = HashMap::new();
let mut msg = String::new();
info!("Loading artifact files:");
if path.is_file() {
try!(load_file(path, &mut artifacts, &mut loaded_settings, &mut loaded_vars));
try!(resolve_settings(&mut settings, &mut repo_map, &loaded_settings));
} else if path.is_dir() {
settings.paths.push_back(path.to_path_buf());
} else {
return Err(LoadError::new("File is not valid type: ".to_string() +
path.to_string_lossy().as_ref()));
}
while !settings.paths.is_empty() {
let dir = settings.paths.pop_front().unwrap(); if loaded_dirs.contains(&dir) {
continue;
}
debug!("Loading artifacts: {:?}", dir);
loaded_settings.clear();
loaded_dirs.insert(dir.to_path_buf());
match load_dir(dir.as_path(),
&mut loaded_dirs,
&mut artifacts,
&mut loaded_settings,
&mut loaded_vars) {
Ok(n) => n,
Err(err) => {
write!(msg,
"Error loading <{}>: {}",
dir.to_string_lossy().as_ref(),
err)
.unwrap();
return Err(LoadError::new(msg));
}
};
try!(resolve_settings(&mut settings, &mut repo_map, &loaded_settings));
}
Ok((artifacts, settings, loaded_vars, repo_map))
}