use rustc_serialize::Decodable;
use toml::{Parser, Value, Table, Decoder};
use dev_prefix::*;
use super::types::*;
use super::utils;
use core::link;
use core::locs;
lazy_static!{
pub static ref REPO_DIR: PathBuf = PathBuf::from(".art");
pub static ref SETTINGS_PATH: PathBuf = REPO_DIR.join("settings.toml");
}
pub fn parse_toml(toml: &str) -> Result<Table> {
let mut parser = Parser::new(toml);
match parser.parse() {
Some(table) => Ok(table),
None => {
let mut locs = String::new();
for e in &parser.errors {
let (line, col) = parser.to_linecol(e.lo);
write!(locs, "[{}:{}] {}, ", line, col, e.desc).unwrap();
}
Err(ErrorKind::TomlParse(locs).into())
}
}
}
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,
}
}
}
impl ProjectText {
pub fn load(&mut self, load_dir: &Path, loaded_dirs: &mut HashSet<PathBuf>) -> Result<()> {
loaded_dirs.insert(load_dir.to_path_buf());
let mut dirs_to_load: Vec<PathBuf> = Vec::new();
let dir_entries =
fs::read_dir(load_dir).chain_err(|| format!(
"could not get dir: {}", load_dir.display()))?;
for entry in dir_entries.filter_map(|e| e.ok()) {
let fpath = entry.path();
let ftype = entry.file_type()
.chain_err(|| format!("error reading type: {}", fpath.display()))?;
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;
}
let mut text = String::new();
let mut fp = fs::File::open(&fpath).chain_err(|| format!(
"error opening: {}", fpath.display()))?;
fp.read_to_string(&mut text)
.chain_err(|| format!("Error loading path {}", fpath.display()))?;
self.files.insert(fpath.to_path_buf(), text);
}
}
for dir in dirs_to_load {
if !loaded_dirs.contains(dir.as_path()) {
self.load(dir.as_path(), loaded_dirs)?;
}
}
Ok(())
}
}
impl Project {
pub fn extend_text(&mut self, project_text: &ProjectText) -> Result<u64> {
let mut count = 0;
for (path, text) in &project_text.files {
count += load_toml(path, text, self)?;
}
Ok(count)
}
}
impl Settings {
pub fn from_table(tbl: &Table) -> Result<(RawSettings, Settings)> {
let value = Value::Table(tbl.clone());
let mut decoder = Decoder::new(value);
let raw = RawSettings::decode(&mut decoder).chain_err(|| "invalid settings")?;
if let Some(invalid) = decoder.toml {
return Err(ErrorKind::InvalidSettings(format!("{:?}", invalid)).into());
}
fn to_paths(paths: &Option<Vec<String>>) -> VecDeque<PathBuf> {
match *paths {
Some(ref p) => {
p.iter()
.map(|p| PathBuf::from(utils::convert_path_str(p)))
.collect()
}
None => VecDeque::new(),
}
}
let mut paths = to_paths(&raw.artifact_paths);
let artifact_paths: HashSet<PathBuf> = paths.drain(0..).collect();
let settings = Settings {
artifact_paths: artifact_paths,
code_paths: to_paths(&raw.code_paths),
exclude_code_paths: to_paths(&raw.exclude_code_paths),
additional_repos: to_paths(&raw.additional_repos),
};
Ok((raw, settings))
}
}
impl Artifact {
#[allow(should_implement_trait)]
pub fn from_str(toml: &str) -> Result<(ArtNameRc, Artifact)> {
let table = try!(parse_toml(toml));
if table.len() != 1 {
return Err(ErrorKind::Load("must contain a single table".to_string()).into());
}
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(ErrorKind::Load("must contain a single table".to_string()).into()),
};
let artifact = try!(Artifact::from_table(&name, &Path::new("from_str"), value));
Ok((Arc::new(name), artifact))
}
fn from_table(name: &ArtName, path: &Path, tbl: &Table) -> Result<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(ErrorKind::InvalidArtifact(name.to_string(), e.to_string()).into())
}
};
if let Some(invalid) = decoder.toml {
return Err(ErrorKind::InvalidArtifact(name.to_string(),
format!("invalid attrs: {}", invalid))
.into());
}
let done = match raw.done {
Some(s) => Done::Defined(s),
None => Done::NotDone,
};
Ok(Artifact {
path: path.to_path_buf(),
text: raw.text.unwrap_or_default(),
partof: ArtNames::from_str(&raw.partof.unwrap_or_default())?,
done: done,
parts: HashSet::new(),
completed: -1.0,
tested: -1.0,
})
}
}
pub fn load_toml(path: &Path, text: &str, project: &mut Project) -> Result<u64> {
let table = parse_toml(text)?;
let mut num_loaded: u64 = 0;
project.files.insert(path.to_path_buf());
for (name, value) in &table {
let aname = ArtName::from_str(name)?;
let art_tbl: &Table = match *value {
Value::Table(ref t) => t,
_ => {
let msg = format!("All top-level values must be a table: {}", name);
return Err(ErrorKind::Load(msg).into());
}
};
if let Some(overlap) = project.artifacts.get(&aname) {
let msg = format!("Overlapping key found <{}> other key at: {}",
name,
overlap.path.display());
return Err(ErrorKind::Load(msg).into());
}
let artifact = Artifact::from_table(&aname, path, art_tbl)?;
project.artifacts.insert(Arc::new(aname), artifact);
num_loaded += 1;
}
Ok(num_loaded)
}
pub fn resolve_settings_paths(repo: &Path, settings: &mut Settings) -> Result<()> {
let mut vars: HashMap<String, String> = HashMap::new();
let settings_path = repo.join(SETTINGS_PATH.as_path());
{
let cwd = repo.join(REPO_DIR.as_path());
let cwd_str = utils::get_path_str(&cwd)?;
vars.insert(CWD_VAR.to_string(), cwd_str.to_string());
vars.insert(REPO_VAR.to_string(), utils::get_path_str(repo)?.to_string());
}
{
let mut paths = HashSet::new();
for p in &settings.artifact_paths {
let p = utils::do_strfmt(utils::get_path_str(p)?, &vars, &settings_path)?;
let p = utils::canonicalize(Path::new(&p)).chain_err(
|| format!("could not find artifact_path: {}", p))?;
paths.insert(p);
}
settings.artifact_paths = paths;
}
{
let mut paths = VecDeque::new();
for p in &settings.code_paths {
let p = try!(utils::do_strfmt(utils::get_path_str(p)?, &vars, &settings_path));
let p = utils::canonicalize(Path::new(&p)).chain_err(
|| format!("could not find code_path: {}", p))?;
paths.push_back(p);
}
settings.code_paths = paths;
}
{
let mut paths = VecDeque::new();
for p in &settings.exclude_code_paths {
let p = try!(utils::do_strfmt(utils::get_path_str(p)?, &vars, &settings_path));
let p = match utils::canonicalize(Path::new(&p)) {
Ok(p) => p,
Err(_) => {
info!("could not find exclude path: {}", p);
continue;
}
};
paths.push_back(p);
}
settings.exclude_code_paths = paths;
}
Ok(())
}
pub fn load_settings(repo: &Path) -> Result<Settings> {
let settings_path = repo.join(SETTINGS_PATH.as_path());
let mut text = String::new();
let mut f = fs::File::open(&settings_path).chain_err(|| format!(
"error opening settings: {}", settings_path.display()))?;
f.read_to_string(&mut text)
.chain_err(|| format!("error reading settings: {}", settings_path.display()))?;
let tbl = parse_toml(&text).chain_err(|| format!(
"error parsing settings: {}", settings_path.display()))?;
let (_, mut settings) = Settings::from_table(&tbl)?;
resolve_settings_paths(repo, &mut settings)?;
Ok(settings)
}
pub fn process_project(project: &mut Project) -> Result<()> {
let locs = locs::find_locs(&project.settings)?;
project.dne_locs = locs::attach_locs(&mut project.artifacts, locs)?;
link::do_links(&mut project.artifacts)?;
Ok(())
}
pub fn load_project(repo: &Path) -> Result<Project> {
let settings = load_settings(repo)?;
let mut project_text = ProjectText::default();
let mut loaded_dirs: HashSet<PathBuf> = HashSet::new(); loaded_dirs.insert(repo.join(REPO_DIR.as_path()));
for dir in &settings.artifact_paths {
if loaded_dirs.contains(dir) {
warn!("artifact_paths tried to load a directory twice: {}",
dir.display());
continue;
}
loaded_dirs.insert(dir.to_path_buf());
project_text.load(dir.as_path(), &mut loaded_dirs)?;
}
let mut project = Project::default();
project.settings = settings.clone();
project.extend_text(&project_text)?;
process_project(&mut project)?;
project.origin = repo.to_path_buf();
Ok(project)
}