job.rs
Raw
extern crate toml;
use std::collections::BTreeMap;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
#[derive(Debug,Clone,Deserialize,Serialize)]
pub struct Job {
pub name: String,
pub display: String,
pub location: PathBuf,
pub command: String,
pub artifacts: Vec<PathBuf>,
//NOTE: This must come last, due to weirdness with toml/serde interaction
// Oddly, the recommended solution (toml::ser::tables_last) seems to do nothing
pub environment: BTreeMap<String, String>,
}
impl fmt::Display for Job {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({}) - \"{}\" from {:?}", self.display, self.name, self.command, self.location)
}
}
fn get_essential(t: &toml::value::Table, key: &str) -> io::Result<String> {
match t.get(key) {
Some(&toml::Value::String(ref s)) => Ok(s.clone()),
None => Err(io::Error::new(io::ErrorKind::InvalidData, format!("Missing critical option \"{}\"", key))),
_ => Err(io::Error::new(io::ErrorKind::InvalidData, format!("Invalid type for option \"{}\"", key))),
}
}
fn get_artifact_list(t: &toml::value::Table) -> io::Result<Vec<PathBuf>> {
if !t.contains_key("artifacts") { return Ok(Vec::new()); }
let a_v = try!(t.get("artifacts").ok_or(io::Error::new(io::ErrorKind::InvalidData, "Missing artifacts option after check, should be impossible")));
let a_s = try!(a_v.as_array().ok_or(io::Error::new(io::ErrorKind::InvalidData, format!("Incorrect type for 'artifacts': {}, should be array of strings", a_v.type_str()))));
let mut artifacts: Vec<PathBuf> = Vec::new();
for value in a_s {
let v = try!(value.as_str().ok_or(io::Error::new(io::ErrorKind::InvalidData, format!("Invalid type for 'artifacts': array of {}, should be array of strings", value.type_str()))));
artifacts.push(PathBuf::from(v));
}
Ok(artifacts)
}
impl Job {
pub fn from_dir(dir: &Path) -> io::Result<Job> {
let mut f = try!(File::open(dir.join("config.toml")));
let mut buf = String::new();
try!(f.read_to_string(&mut buf));
let v = &buf.parse::<toml::Value>().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidData, "No valid configuration file")
})?;
let table = v.as_table().ok_or(io::Error::new(io::ErrorKind::InvalidData, "Badly formed configuration file"))?;
let name = dir.file_name().unwrap().to_str().unwrap().to_string(); //This is ugly as hell and should be rethought -- but is safe as currently used because it would have exploded earlier
let display = try!(get_essential(&table, "displayname"));
let command = try!(get_essential(&table, "command"));
let env = match table.get("environment") {
Some(&toml::Value::Table(ref tb)) => tb,
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid or missing environment data")),
};
let mut environment: BTreeMap<String, String> = BTreeMap::new();
for (key, value) in env.iter() {
let v = match value {
&toml::Value::String(ref s) => s.clone(),
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, format!("Invalid type for environment variable \"{}\", must be string", key))),
};
environment.insert(key.clone(), v);
}
let artifacts = try!(get_artifact_list(&table));
Ok(Job{name: name, display: display, location: dir.to_path_buf(), command: command, environment: environment, artifacts: artifacts})
}
}