All ProjectsHome
jobrunner
jobrunner/src/job.rs
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})
    }
}