<commit_before>use docopt::Docopt;
use docopt::Error as DocoptError;
use std::error::Error as StdError;
use std::ffi::OsStr;
use std::fmt::{self, Display, Formatter};
use std::fs::File;
use std::io::Error as IoError;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml;

use gobjects;
use version::Version;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkMode {
    Normal,     //generate widgets etc.
    Sys,        //generate -sys with ffi
}

impl Default for WorkMode {
    fn default() -> WorkMode { WorkMode::Normal }
}

impl FromStr for WorkMode {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "normal" => Ok(WorkMode::Normal),
            "sys" => Ok(WorkMode::Sys),
            _ => Err("Wrong work mode".into())
        }
    }
}

#[derive(Debug)]
pub enum Error {
    CommandLine(DocoptError),
    Io(IoError, PathBuf),
    Toml(String, PathBuf),
    Options(String, PathBuf),
}

impl StdError for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            CommandLine(ref e) => e.description(),
            Io(ref e, _) => e.description(),
            Toml(ref s, _) => s,
            Options(ref s, _) => s,
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use self::Error::*;
        match *self {
            CommandLine(ref err) => err.fmt(f),
            Io(ref err, ref filename) => {
                try!(write!(f, "Failed to read config \"{}\": ", filename.display()));
                err.fmt(f)
            }
            Toml(ref err, ref filename) => {
                write!(f, "\"{}\": {}", filename.display(), err)
            }
            Options(ref err, ref filename) => {
                write!(f, "\"{}\": {}", filename.display(), err)
            }
        }
    }
}

impl<'a> From<DocoptError> for Error {
    fn from(e: DocoptError) -> Error {
        Error::CommandLine(e)
    }
}

impl<P: AsRef<OsStr>> From<(IoError, P)> for Error {
    fn from(e: (IoError, P)) -> Error {
        Error::Io(e.0, PathBuf::from(&e.1))
    }
}

impl<'a, P: AsRef<OsStr>> From<(&'a str, P)> for Error {
    fn from(e: (&'a str, P)) -> Error {
        Error::Options(e.0.into(), PathBuf::from(&e.1))
    }
}

impl<P: AsRef<OsStr>> From<(String, P)> for Error {
    fn from(e: (String, P)) -> Error {
        Error::Options(e.0, PathBuf::from(&e.1))
    }
}

trait TomlHelper where Self: Sized {
    fn lookup_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, err: &str, config_file: P) -> Result<&'a str, Error>;
    fn as_result_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a str, Error>;
    fn as_result_slice<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a [Self], Error>;
}

impl TomlHelper for toml::Value {
    fn lookup_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, err: &str, config_file: P) -> Result<&'a str, Error> {
        let value = try!(self.lookup(option).ok_or((err, &config_file)));
        value.as_result_str(option, config_file)
    }
    fn as_result_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a str, Error> {
        self.as_str()
            .ok_or(Error::Options(format!("Invalid `{}` value, expected a string, found {}",
                                          option, self.type_str()),
                                  PathBuf::from(&config_file)))
    }
    fn as_result_slice<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a [Self], Error> {
        self.as_slice()
            .ok_or(Error::Options(format!("Invalid `{}` value, expected a array, found {}",
                                          option, self.type_str()),
                                  PathBuf::from(&config_file)))
    }
}

static USAGE: &'static str = "
Usage: gir [options] [<library> <version>]
       gir --help

Options:
    -h, --help          Show this message.
    -c CONFIG           Config file path (default: Gir.toml)
    -d GIRSPATH         Directory for girs
    -m MODE             Work mode: normal or sys
    -o PATH             Target path
    -b, --make_backup   Make backup before generating
";

#[derive(Debug)]
pub struct Config {
    pub work_mode: WorkMode,
    pub girs_dir: PathBuf,
    pub library_name: String,
    pub library_version: String,
    pub target_path: PathBuf,
    pub external_libraries: Vec<String>,
    pub objects: gobjects::GObjects,
    pub min_cfg_version: Version,
    pub make_backup: bool,
}

impl Config {
    pub fn new() -> Result<Config, Error> {
        let args = try!(Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse()));

        let config_file: PathBuf = match args.get_str("-c") {
            "" => "Gir.toml",
            a => a,
        }.into();

        //TODO: add check file existence when stable std::fs::PathExt
        let toml = try!(read_toml(&config_file));

        let work_mode_str = match args.get_str("-m") {
            "" => try!(toml.lookup_str("options.work_mode", "No options.work_mode in config", &config_file)),
            a => a,
        };
        let work_mode = WorkMode::from_str(work_mode_str)
            .unwrap_or_else(|e| panic!(e));

        let girs_dir: PathBuf = match args.get_str("-d") {
            "" => try!(toml.lookup_str("options.girs_dir", "No options.girs_dir in config", &config_file)),
            a => a
        }.into();

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                try!(toml.lookup_str("options.library", "No options.library in config", &config_file)),
                try!(toml.lookup_str("options.version", "No options.version in config", &config_file))
            ),
            ("", _) | (_, "") => try!(Err(("Library and version can not be specified separately", &config_file))),
            (a, b) => (a, b)
        };

        let target_path: PathBuf = match args.get_str("-o") {
            "" => try!(toml.lookup_str("options.target_path", "No target path specified", &config_file)),
            a => a
        }.into();

        let mut objects = toml.lookup("object").map(|t| gobjects::parse_toml(t))
            .unwrap_or_else(|| Default::default());
        gobjects::parse_status_shorthands(&mut objects, &toml);

        let external_libraries = match toml.lookup("options.external_libraries") {
            Some(a) => {
                try!(a.as_result_slice("options.external_libraries", &config_file))
                    .iter().filter_map(|v| v.as_str().map(String::from))
                    .collect()
            }
            None => Vec::new(),
        };

        let min_cfg_version = match toml.lookup("options.min_cfg_version") {
            Some(v) => {
                try!(
                    try!(v.as_result_str("options.min_cfg_version", &config_file))
                        .parse().map_err(|e: String| (e, config_file)))
            }
            None => Default::default(),
        };

        let make_backup = args.get_bool("-b");

        Ok(Config {
            work_mode: work_mode,
            girs_dir: girs_dir,
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path,
            external_libraries: external_libraries,
            objects: objects,
            min_cfg_version: min_cfg_version,
            make_backup: make_backup,
        })
    }

    pub fn library_full_name(&self) -> String {
        format!("{}-{}", self.library_name, self.library_version)
    }
}

fn read_toml<P: AsRef<OsStr> + AsRef<Path>>(filename: P) -> Result<toml::Value, Error> {
    let mut input = String::new();
    try!(File::open(&filename)
         .and_then(|mut f| f.read_to_string(&mut input))
         .map_err(|e| (e, &filename)));

    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => Ok(toml::Value::Table(toml)),
        None => {
            let err = &parser.errors[parser.errors.len() - 1];
            let (loline, locol) = parser.to_linecol(err.lo);
            let (hiline, hicol) = parser.to_linecol(err.hi);
            let s = format!("{}:{}-{}:{} error: {}", loline, locol, hiline, hicol, err.desc);
            Err(Error::Toml(s, PathBuf::from(&filename)))
        }
    }
}
<commit_msg>Make config paths relative config directory if its read from toml<commit_after>use docopt::Docopt;
use docopt::Error as DocoptError;
use std::error::Error as StdError;
use std::ffi::OsStr;
use std::fmt::{self, Display, Formatter};
use std::fs::File;
use std::io::Error as IoError;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use toml;

use gobjects;
use version::Version;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum WorkMode {
    Normal,     //generate widgets etc.
    Sys,        //generate -sys with ffi
}

impl Default for WorkMode {
    fn default() -> WorkMode { WorkMode::Normal }
}

impl FromStr for WorkMode {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "normal" => Ok(WorkMode::Normal),
            "sys" => Ok(WorkMode::Sys),
            _ => Err("Wrong work mode".into())
        }
    }
}

#[derive(Debug)]
pub enum Error {
    CommandLine(DocoptError),
    Io(IoError, PathBuf),
    Toml(String, PathBuf),
    Options(String, PathBuf),
}

impl StdError for Error {
    fn description(&self) -> &str {
        use self::Error::*;
        match *self {
            CommandLine(ref e) => e.description(),
            Io(ref e, _) => e.description(),
            Toml(ref s, _) => s,
            Options(ref s, _) => s,
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        use self::Error::*;
        match *self {
            CommandLine(ref err) => err.fmt(f),
            Io(ref err, ref filename) => {
                try!(write!(f, "Failed to read config \"{}\": ", filename.display()));
                err.fmt(f)
            }
            Toml(ref err, ref filename) => {
                write!(f, "\"{}\": {}", filename.display(), err)
            }
            Options(ref err, ref filename) => {
                write!(f, "\"{}\": {}", filename.display(), err)
            }
        }
    }
}

impl<'a> From<DocoptError> for Error {
    fn from(e: DocoptError) -> Error {
        Error::CommandLine(e)
    }
}

impl<P: AsRef<OsStr>> From<(IoError, P)> for Error {
    fn from(e: (IoError, P)) -> Error {
        Error::Io(e.0, PathBuf::from(&e.1))
    }
}

impl<'a, P: AsRef<OsStr>> From<(&'a str, P)> for Error {
    fn from(e: (&'a str, P)) -> Error {
        Error::Options(e.0.into(), PathBuf::from(&e.1))
    }
}

impl<P: AsRef<OsStr>> From<(String, P)> for Error {
    fn from(e: (String, P)) -> Error {
        Error::Options(e.0, PathBuf::from(&e.1))
    }
}

trait TomlHelper where Self: Sized {
    fn lookup_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, err: &str, config_file: P) -> Result<&'a str, Error>;
    fn as_result_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a str, Error>;
    fn as_result_slice<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a [Self], Error>;
}

impl TomlHelper for toml::Value {
    fn lookup_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, err: &str, config_file: P) -> Result<&'a str, Error> {
        let value = try!(self.lookup(option).ok_or((err, &config_file)));
        value.as_result_str(option, config_file)
    }
    fn as_result_str<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a str, Error> {
        self.as_str()
            .ok_or(Error::Options(format!("Invalid `{}` value, expected a string, found {}",
                                          option, self.type_str()),
                                  PathBuf::from(&config_file)))
    }
    fn as_result_slice<'a, P: AsRef<OsStr>>(&'a self, option: &'a str, config_file: P) -> Result<&'a [Self], Error> {
        self.as_slice()
            .ok_or(Error::Options(format!("Invalid `{}` value, expected a array, found {}",
                                          option, self.type_str()),
                                  PathBuf::from(&config_file)))
    }
}

static USAGE: &'static str = "
Usage: gir [options] [<library> <version>]
       gir --help

Options:
    -h, --help          Show this message.
    -c CONFIG           Config file path (default: Gir.toml)
    -d GIRSPATH         Directory for girs
    -m MODE             Work mode: normal or sys
    -o PATH             Target path
    -b, --make_backup   Make backup before generating
";

#[derive(Debug)]
pub struct Config {
    pub work_mode: WorkMode,
    pub girs_dir: PathBuf,
    pub library_name: String,
    pub library_version: String,
    pub target_path: PathBuf,
    pub external_libraries: Vec<String>,
    pub objects: gobjects::GObjects,
    pub min_cfg_version: Version,
    pub make_backup: bool,
}

impl Config {
    pub fn new() -> Result<Config, Error> {
        let args = try!(Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse()));

        let config_file: PathBuf = match args.get_str("-c") {
            "" => "Gir.toml",
            a => a,
        }.into();

        let config_dir = match config_file.parent() {
            Some(path) => path.into(),
            None => PathBuf::new(),
        };

        //TODO: add check file existence when stable std::fs::PathExt
        let toml = try!(read_toml(&config_file));

        let work_mode_str = match args.get_str("-m") {
            "" => try!(toml.lookup_str("options.work_mode",
               "No options.work_mode in config", &config_file)),
            a => a,
        };
        let work_mode = WorkMode::from_str(work_mode_str)
            .unwrap_or_else(|e| panic!(e));

        let girs_dir: PathBuf = match args.get_str("-d") {
            "" => {
                let path = try!(toml.lookup_str("options.girs_dir",
                    "No options.girs_dir in config", &config_file));
                config_dir.join(path)
            }
            a => a.into(),
        };

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                try!(toml.lookup_str("options.library", "No options.library in config", &config_file)),
                try!(toml.lookup_str("options.version", "No options.version in config", &config_file))
            ),
            ("", _) | (_, "") => try!(Err(("Library and version can not be specified separately",
                                           &config_file))),
            (a, b) => (a, b)
        };

        let target_path: PathBuf = match args.get_str("-o") {
            "" => {
                let path = try!(toml.lookup_str("options.target_path",
                    "No target path specified", &config_file));
                config_dir.join(path)
            }
            a => a.into()
        };

        let mut objects = toml.lookup("object").map(|t| gobjects::parse_toml(t))
            .unwrap_or_else(|| Default::default());
        gobjects::parse_status_shorthands(&mut objects, &toml);

        let external_libraries = match toml.lookup("options.external_libraries") {
            Some(a) => {
                try!(a.as_result_slice("options.external_libraries", &config_file))
                    .iter().filter_map(|v| v.as_str().map(String::from))
                    .collect()
            }
            None => Vec::new(),
        };

        let min_cfg_version = match toml.lookup("options.min_cfg_version") {
            Some(v) => {
                try!(
                    try!(v.as_result_str("options.min_cfg_version", &config_file))
                        .parse().map_err(|e: String| (e, config_file)))
            }
            None => Default::default(),
        };

        let make_backup = args.get_bool("-b");

        Ok(Config {
            work_mode: work_mode,
            girs_dir: girs_dir,
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path,
            external_libraries: external_libraries,
            objects: objects,
            min_cfg_version: min_cfg_version,
            make_backup: make_backup,
        })
    }

    pub fn library_full_name(&self) -> String {
        format!("{}-{}", self.library_name, self.library_version)
    }
}

fn read_toml<P: AsRef<OsStr> + AsRef<Path>>(filename: P) -> Result<toml::Value, Error> {
    let mut input = String::new();
    try!(File::open(&filename)
         .and_then(|mut f| f.read_to_string(&mut input))
         .map_err(|e| (e, &filename)));

    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => Ok(toml::Value::Table(toml)),
        None => {
            let err = &parser.errors[parser.errors.len() - 1];
            let (loline, locol) = parser.to_linecol(err.lo);
            let (hiline, hicol) = parser.to_linecol(err.hi);
            let s = format!("{}:{}-{}:{} error: {}", loline, locol, hiline, hicol, err.desc);
            Err(Error::Toml(s, PathBuf::from(&filename)))
        }
    }
}
<|endoftext|>


<commit_before>use etcd;
use std::thread;
use std::sync::atomic::{Ordering, AtomicBool};
use std::fmt;
use std::sync::Arc;
use std::collections::BTreeMap;
use time::{SteadyTime, Duration};
use serde_json as json;
use serde::ser::Serialize;
use serde::de::Deserialize;

include!(concat!(env!("OUT_DIR"), "/config_data.rs"));

impl ConfigSequencer {
    fn update_from_keys(&mut self, current_keys: Vec<String>) {
        self.keys = current_keys;
        self.epoch = self.epoch.succ();
    }
}

pub struct ConfigClient<T> {
    client: Arc<InnerClient<T>>,
    lease_mgr: thread::JoinHandle<()>,
    watcher: thread::JoinHandle<()>,
}

#[derive(Clone,Debug, Default)]
pub struct ConfigurationView<T> {
    pub epoch: Epoch,
    ord: usize,
    next: Option<T>,
}

struct InnerClient<T> {
    etcd: etcd::Client,
    data: T,
    lease_time: Duration,
    callback: Box<Fn(ConfigurationView<T>) + Send + Sync + 'static>,
    on_exit: Box<Fn() + Send + Sync + 'static>,
    lease_key: Option<String>,
    lease_alive: AtomicBool,
    watcher_alive: AtomicBool,
}

impl Epoch {
    fn succ(&self) -> Epoch {
        Epoch(self.0 + 1)
    }
}

struct DeathWatch<'a, F>(&'a AtomicBool, F) where F: Fn();

impl<'a, F> DeathWatch<'a, F>
    where F: Fn()
{
    fn new(ctr: &'a AtomicBool, f: F) -> DeathWatch<'a, F> {
        ctr.store(true, Ordering::SeqCst);
        DeathWatch(ctr, f)
    }
    fn is_alive(&self) -> bool {
        self.0.load(Ordering::Relaxed)
    }
}
impl<'a, F> Drop for DeathWatch<'a, F>
    where F: Fn()
{
    fn drop(&mut self) {
        warn!("Exiting: {:?}", self);
        let &mut DeathWatch(ref alivep, ref mut cb) = self;
        alivep.store(false, Ordering::Relaxed);
        (cb)();
    }
}

impl<'a, F> fmt::Debug for DeathWatch<'a, F>
    where F: Fn()
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("DeathWatch").field("alive", &self.is_alive()).finish()
    }
}

impl<T: 'static + Deserialize + Serialize + fmt::Debug + Eq + Clone + Send + Sync> ConfigClient<T> {
    pub fn new<F: Fn(ConfigurationView<T>) + Send + Sync + 'static, S: Fn() + Send + Sync + 'static>
        (addr: &str,
         data: T,
         lease_time: Duration,
         callback: F,
         on_exit: S)
         -> Result<ConfigClient<T>, ()> {
        let etcd = etcd::Client::new(addr).expect("etcd client");
        let mut client = InnerClient {
            etcd: etcd,
            data: data,
            lease_time: lease_time,
            callback: Box::new(callback),
            on_exit: Box::new(on_exit),
            lease_key: None,
            lease_alive: AtomicBool::new(true),
            watcher_alive: AtomicBool::new(true),
        };
        let lease = client.setup_lease();

        let client = Arc::new(client);
        let lease_mgr = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd config".to_string())
                .spawn(move || client.run_lease(lease))
                .expect("etcd thread")
        };
        let watcher = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd watcher".to_string())
                .spawn(move || client.run_watch())
                .expect("etcd watcher")
        };
        Ok(ConfigClient {
            client: client,
            lease_mgr: lease_mgr,
            watcher: watcher,
        })
    }

    pub fn is_running(&self) -> bool {
        self.client.is_running()
    }
}

const MEMBERS: &'static str = "/chain/members";
const SEQUENCER: &'static str = "/chain/config_seq";
const KEY_EXISTS: u64 = 105;
const KEY_NOT_EXISTS: u64 = 100;
const COMPARE_FAILED: u64 = 101;

impl<T: Deserialize + Serialize + fmt::Debug + Eq + Clone> InnerClient<T> {
    fn setup_lease(&mut self) -> u64 {
        match self.etcd.create_dir(MEMBERS, None) {
            Ok(res) => info!("Created dir: {}: {:?}: ", MEMBERS, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Dir exists: {:?}: ", MEMBERS)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", MEMBERS, e),
        }

        let seq: ConfigSequencer = Default::default();
        match self.etcd.create(SEQUENCER,
                               &json::to_string(&seq).expect("encode epoch"),
                               None) {
            Ok(res) => info!("Created seq: {}: {:?}: ", SEQUENCER, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Sequencer exists: {:?}: ", SEQUENCER)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
        }

        let me = self.etcd
                     .create_in_order(MEMBERS,
                                      &self.data_json(),
                                      Some(self.lease_time.num_seconds() as u64))
                     .expect("Create unique node");
        info!("My node! {:?}", me);
        self.lease_key = Some(me.node.key.expect("Key name"));
        me.node.modified_index.expect("Lease node version")
    }

    fn data_json(&self) -> String {
        json::to_string(&self.data).expect("json encode")
    }

    fn run_lease(&self, mut lease_index: u64) {
        let watch = DeathWatch::new(&self.lease_alive, || (*self.on_exit)());
        let update_interval = self.lease_time / 2;
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        loop {
            trace!("touch node: {:?}={:?}", lease_key, self.data);
            let start_time = SteadyTime::now();
            let next_wakeup = start_time + update_interval;
            let res = match self.etcd
                                .compare_and_swap(&lease_key,
                                                  &self.data_json(),
                                                  Some(self.lease_time.num_seconds() as u64),
                                                  None,
                                                  Some(lease_index)) {
                Ok(res) => {
                    trace!("refreshed lease:: {}: {:?}: ",
                           lease_key,
                           res.node.modified_index);
                    res
                }
                Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_NOT_EXISTS => {
                    panic!("Could not update lease: expired");
                }
                Err(e) => panic!("Unexpected error updating lease {}: {:?}", lease_key, e),
            };
            lease_index = res.node.modified_index.expect("lease version");
            let end_time = SteadyTime::now();
            trace!("Updated in {}: {:?}", end_time - start_time, res);
            let pausetime = next_wakeup - end_time;
            trace!("Pause for {}", pausetime);
            if pausetime > Duration::zero() {
                thread::sleep_ms(pausetime.num_milliseconds() as u32);
            }
        }
    }

    fn list_members(&self) -> BTreeMap<String, T> {
        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        trace!("Listing: {:?}", current_listing);
        current_listing.node
                       .nodes
                       .unwrap_or_else(|| Vec::new())
                       .into_iter()
                       .filter_map(|n| {
                           if let (Some(k), Some(v)) = (n.key, n.value) {
                               Some((k, json::from_str(&v).expect("decode json")))
                           } else {
                               None
                           }
                       })
                       .collect::<BTreeMap<String, T>>()
    }

    fn run_watch(&self) {
        let watch = DeathWatch::new(&self.watcher_alive, || (*self.on_exit)());
        info!("Starting etcd watcher");
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        debug!("Listing: {:?}", current_listing);
        let mut last_observed_index = current_listing.node
                                                     .nodes
                                                     .unwrap_or_else(|| Vec::new())
                                                     .into_iter()
                                                     .filter_map(|x| x.modified_index)
                                                     .max();

        let mut curr_members = BTreeMap::new();
        loop {
            trace!("Awaiting for {} from etcd index {:?}", MEMBERS, last_observed_index);
            let res = self.etcd.watch(MEMBERS, last_observed_index, true).expect("watch");
            trace!("Watch: {:?}", res);
            last_observed_index = res.node.modified_index.map(|x| x + 1);

            let members = self.list_members();
            let seq = self.verify_epoch(&members);

            trace!("Members: {:?}; seq: {:?}", members, seq);
            if curr_members != members {
                curr_members = members;
                info!("Membership change! {:?}", curr_members);
                if let Some(view) = ConfigurationView::of_membership(seq.epoch,
                                                                     lease_key,
                                                                     curr_members.clone()) {
                    (self.callback)(view)
                } else {
                    warn!("I seem to have died: {:?}", lease_key);
                    (self.on_exit)();
                    break;
                }
            }
        }

    }

    fn verify_epoch(&self, members: &BTreeMap<String, T>) -> ConfigSequencer {
        loop {
            let (seq_index, mut seq): (u64, ConfigSequencer) = match self.etcd.get(SEQUENCER,
                                                                                false,
                                                                                false,
                                                                                false) {
                Ok(etcd::KeySpaceInfo {
                    node: etcd::keys::Node {
                        modified_index: Some(modified_index),
                        value: Some(value),
                        ..
                    } ,
                    ..
                }) => {
                    (modified_index,
                     json::from_str(&value).expect("decode epoch"))
                }
                Ok(e) => panic!("Unexpected response reading {}: {:?}", SEQUENCER, e),
                Err(e) => panic!("Unexpected error reading {}: {:?}", SEQUENCER, e),
            };
            trace!("Sequencer: {}/{:?}", seq_index, seq);
            let current_keys = members.keys().cloned().collect::<Vec<_>>();
            if current_keys != seq.keys {
                debug!("verify_epoch: Stale! {:?}", seq);

                seq.update_from_keys(current_keys);

                match self.etcd.compare_and_swap(SEQUENCER,
                                                 &json::to_string(&seq).expect("encode epoch"),
                                                 None,
                                                 None,
                                                 Some(seq_index)) {
                    Ok(res) => {
                        debug!("verify_epoch: Updated seq: {}: {:?}: ", SEQUENCER, seq);
                        return seq;
                    }
                    Err(etcd::Error::Etcd(ref e)) if e.error_code == COMPARE_FAILED => {
                        debug!("verify_epoch: Raced out; retry")
                    }
                    Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
                }
            } else {
                debug!("verify_epoch: Fresh! {:?}", seq);
                return seq;
            }
        }
    }

    fn is_running(&self) -> bool {
        self.lease_alive.load(Ordering::Relaxed) && self.watcher_alive.load(Ordering::Relaxed)
    }
}

impl<T: Clone> ConfigurationView<T> {
    fn of_membership(epoch: Epoch,
                     this_node: &str,
                     members: BTreeMap<String, T>)
                     -> Option<ConfigurationView<T>> {
        let ordp = members.keys().position(|x| *x == this_node);
        ordp.map(|ord| {
            let next = members.values().nth(ord + 1).map(|v| v.clone());
            ConfigurationView {
                epoch: epoch,
                ord: ord,
                next: next,
            }
        })
    }

    pub fn is_head(&self) -> bool {
        self.ord == 0
    }

    pub fn should_listen_for_clients(&self) -> bool {
        self.is_head()
    }
    pub fn should_listen_for_upstream(&self) -> bool {
        !self.is_head()
    }

    pub fn should_connect_downstream<'a>(&'a self) -> Option<&'a T> {
        self.next.as_ref()
    }
}
<commit_msg>Rename threads so perf dumps are easier to parse.<commit_after>use etcd;
use std::thread;
use std::sync::atomic::{Ordering, AtomicBool};
use std::fmt;
use std::sync::Arc;
use std::collections::BTreeMap;
use time::{SteadyTime, Duration};
use serde_json as json;
use serde::ser::Serialize;
use serde::de::Deserialize;

include!(concat!(env!("OUT_DIR"), "/config_data.rs"));

impl ConfigSequencer {
    fn update_from_keys(&mut self, current_keys: Vec<String>) {
        self.keys = current_keys;
        self.epoch = self.epoch.succ();
    }
}

pub struct ConfigClient<T> {
    client: Arc<InnerClient<T>>,
    lease_mgr: thread::JoinHandle<()>,
    watcher: thread::JoinHandle<()>,
}

#[derive(Clone,Debug, Default)]
pub struct ConfigurationView<T> {
    pub epoch: Epoch,
    ord: usize,
    next: Option<T>,
}

struct InnerClient<T> {
    etcd: etcd::Client,
    data: T,
    lease_time: Duration,
    callback: Box<Fn(ConfigurationView<T>) + Send + Sync + 'static>,
    on_exit: Box<Fn() + Send + Sync + 'static>,
    lease_key: Option<String>,
    lease_alive: AtomicBool,
    watcher_alive: AtomicBool,
}

impl Epoch {
    fn succ(&self) -> Epoch {
        Epoch(self.0 + 1)
    }
}

struct DeathWatch<'a, F>(&'a AtomicBool, F) where F: Fn();

impl<'a, F> DeathWatch<'a, F>
    where F: Fn()
{
    fn new(ctr: &'a AtomicBool, f: F) -> DeathWatch<'a, F> {
        ctr.store(true, Ordering::SeqCst);
        DeathWatch(ctr, f)
    }
    fn is_alive(&self) -> bool {
        self.0.load(Ordering::Relaxed)
    }
}
impl<'a, F> Drop for DeathWatch<'a, F>
    where F: Fn()
{
    fn drop(&mut self) {
        warn!("Exiting: {:?}", self);
        let &mut DeathWatch(ref alivep, ref mut cb) = self;
        alivep.store(false, Ordering::Relaxed);
        (cb)();
    }
}

impl<'a, F> fmt::Debug for DeathWatch<'a, F>
    where F: Fn()
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_struct("DeathWatch").field("alive", &self.is_alive()).finish()
    }
}

impl<T: 'static + Deserialize + Serialize + fmt::Debug + Eq + Clone + Send + Sync> ConfigClient<T> {
    pub fn new<F: Fn(ConfigurationView<T>) + Send + Sync + 'static, S: Fn() + Send + Sync + 'static>
        (addr: &str,
         data: T,
         lease_time: Duration,
         callback: F,
         on_exit: S)
         -> Result<ConfigClient<T>, ()> {
        let etcd = etcd::Client::new(addr).expect("etcd client");
        let mut client = InnerClient {
            etcd: etcd,
            data: data,
            lease_time: lease_time,
            callback: Box::new(callback),
            on_exit: Box::new(on_exit),
            lease_key: None,
            lease_alive: AtomicBool::new(true),
            watcher_alive: AtomicBool::new(true),
        };
        let lease = client.setup_lease();

        let client = Arc::new(client);
        let lease_mgr = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd-config".to_string())
                .spawn(move || client.run_lease(lease))
                .expect("etcd thread")
        };
        let watcher = {
            let client = client.clone();
            thread::Builder::new()
                .name("etcd-watcher".to_string())
                .spawn(move || client.run_watch())
                .expect("etcd watcher")
        };
        Ok(ConfigClient {
            client: client,
            lease_mgr: lease_mgr,
            watcher: watcher,
        })
    }

    pub fn is_running(&self) -> bool {
        self.client.is_running()
    }
}

const MEMBERS: &'static str = "/chain/members";
const SEQUENCER: &'static str = "/chain/config_seq";
const KEY_EXISTS: u64 = 105;
const KEY_NOT_EXISTS: u64 = 100;
const COMPARE_FAILED: u64 = 101;

impl<T: Deserialize + Serialize + fmt::Debug + Eq + Clone> InnerClient<T> {
    fn setup_lease(&mut self) -> u64 {
        match self.etcd.create_dir(MEMBERS, None) {
            Ok(res) => info!("Created dir: {}: {:?}: ", MEMBERS, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Dir exists: {:?}: ", MEMBERS)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", MEMBERS, e),
        }

        let seq: ConfigSequencer = Default::default();
        match self.etcd.create(SEQUENCER,
                               &json::to_string(&seq).expect("encode epoch"),
                               None) {
            Ok(res) => info!("Created seq: {}: {:?}: ", SEQUENCER, res),
            Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_EXISTS => {
                info!("Sequencer exists: {:?}: ", SEQUENCER)
            }
            Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
        }

        let me = self.etcd
                     .create_in_order(MEMBERS,
                                      &self.data_json(),
                                      Some(self.lease_time.num_seconds() as u64))
                     .expect("Create unique node");
        info!("My node! {:?}", me);
        self.lease_key = Some(me.node.key.expect("Key name"));
        me.node.modified_index.expect("Lease node version")
    }

    fn data_json(&self) -> String {
        json::to_string(&self.data).expect("json encode")
    }

    fn run_lease(&self, mut lease_index: u64) {
        let watch = DeathWatch::new(&self.lease_alive, || (*self.on_exit)());
        let update_interval = self.lease_time / 2;
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        loop {
            trace!("touch node: {:?}={:?}", lease_key, self.data);
            let start_time = SteadyTime::now();
            let next_wakeup = start_time + update_interval;
            let res = match self.etcd
                                .compare_and_swap(&lease_key,
                                                  &self.data_json(),
                                                  Some(self.lease_time.num_seconds() as u64),
                                                  None,
                                                  Some(lease_index)) {
                Ok(res) => {
                    trace!("refreshed lease:: {}: {:?}: ",
                           lease_key,
                           res.node.modified_index);
                    res
                }
                Err(etcd::Error::Etcd(ref e)) if e.error_code == KEY_NOT_EXISTS => {
                    panic!("Could not update lease: expired");
                }
                Err(e) => panic!("Unexpected error updating lease {}: {:?}", lease_key, e),
            };
            lease_index = res.node.modified_index.expect("lease version");
            let end_time = SteadyTime::now();
            trace!("Updated in {}: {:?}", end_time - start_time, res);
            let pausetime = next_wakeup - end_time;
            trace!("Pause for {}", pausetime);
            if pausetime > Duration::zero() {
                thread::sleep_ms(pausetime.num_milliseconds() as u32);
            }
        }
    }

    fn list_members(&self) -> BTreeMap<String, T> {
        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        trace!("Listing: {:?}", current_listing);
        current_listing.node
                       .nodes
                       .unwrap_or_else(|| Vec::new())
                       .into_iter()
                       .filter_map(|n| {
                           if let (Some(k), Some(v)) = (n.key, n.value) {
                               Some((k, json::from_str(&v).expect("decode json")))
                           } else {
                               None
                           }
                       })
                       .collect::<BTreeMap<String, T>>()
    }

    fn run_watch(&self) {
        let watch = DeathWatch::new(&self.watcher_alive, || (*self.on_exit)());
        info!("Starting etcd watcher");
        let lease_key = self.lease_key.as_ref().expect("Should have created lease node");

        let current_listing = self.etcd.get(MEMBERS, true, true, true).expect("List members");
        debug!("Listing: {:?}", current_listing);
        let mut last_observed_index = current_listing.node
                                                     .nodes
                                                     .unwrap_or_else(|| Vec::new())
                                                     .into_iter()
                                                     .filter_map(|x| x.modified_index)
                                                     .max();

        let mut curr_members = BTreeMap::new();
        loop {
            trace!("Awaiting for {} from etcd index {:?}", MEMBERS, last_observed_index);
            let res = self.etcd.watch(MEMBERS, last_observed_index, true).expect("watch");
            trace!("Watch: {:?}", res);
            last_observed_index = res.node.modified_index.map(|x| x + 1);

            let members = self.list_members();
            let seq = self.verify_epoch(&members);

            trace!("Members: {:?}; seq: {:?}", members, seq);
            if curr_members != members {
                curr_members = members;
                info!("Membership change! {:?}", curr_members);
                if let Some(view) = ConfigurationView::of_membership(seq.epoch,
                                                                     lease_key,
                                                                     curr_members.clone()) {
                    (self.callback)(view)
                } else {
                    warn!("I seem to have died: {:?}", lease_key);
                    (self.on_exit)();
                    break;
                }
            }
        }

    }

    fn verify_epoch(&self, members: &BTreeMap<String, T>) -> ConfigSequencer {
        loop {
            let (seq_index, mut seq): (u64, ConfigSequencer) = match self.etcd.get(SEQUENCER,
                                                                                false,
                                                                                false,
                                                                                false) {
                Ok(etcd::KeySpaceInfo {
                    node: etcd::keys::Node {
                        modified_index: Some(modified_index),
                        value: Some(value),
                        ..
                    } ,
                    ..
                }) => {
                    (modified_index,
                     json::from_str(&value).expect("decode epoch"))
                }
                Ok(e) => panic!("Unexpected response reading {}: {:?}", SEQUENCER, e),
                Err(e) => panic!("Unexpected error reading {}: {:?}", SEQUENCER, e),
            };
            trace!("Sequencer: {}/{:?}", seq_index, seq);
            let current_keys = members.keys().cloned().collect::<Vec<_>>();
            if current_keys != seq.keys {
                debug!("verify_epoch: Stale! {:?}", seq);

                seq.update_from_keys(current_keys);

                match self.etcd.compare_and_swap(SEQUENCER,
                                                 &json::to_string(&seq).expect("encode epoch"),
                                                 None,
                                                 None,
                                                 Some(seq_index)) {
                    Ok(res) => {
                        debug!("verify_epoch: Updated seq: {}: {:?}: ", SEQUENCER, seq);
                        return seq;
                    }
                    Err(etcd::Error::Etcd(ref e)) if e.error_code == COMPARE_FAILED => {
                        debug!("verify_epoch: Raced out; retry")
                    }
                    Err(e) => panic!("Unexpected error creating {}: {:?}", SEQUENCER, e),
                }
            } else {
                debug!("verify_epoch: Fresh! {:?}", seq);
                return seq;
            }
        }
    }

    fn is_running(&self) -> bool {
        self.lease_alive.load(Ordering::Relaxed) && self.watcher_alive.load(Ordering::Relaxed)
    }
}

impl<T: Clone> ConfigurationView<T> {
    fn of_membership(epoch: Epoch,
                     this_node: &str,
                     members: BTreeMap<String, T>)
                     -> Option<ConfigurationView<T>> {
        let ordp = members.keys().position(|x| *x == this_node);
        ordp.map(|ord| {
            let next = members.values().nth(ord + 1).map(|v| v.clone());
            ConfigurationView {
                epoch: epoch,
                ord: ord,
                next: next,
            }
        })
    }

    pub fn is_head(&self) -> bool {
        self.ord == 0
    }

    pub fn should_listen_for_clients(&self) -> bool {
        self.is_head()
    }
    pub fn should_listen_for_upstream(&self) -> bool {
        !self.is_head()
    }

    pub fn should_connect_downstream<'a>(&'a self) -> Option<&'a T> {
        self.next.as_ref()
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use regex;
use regex::{Regex, RegexBuilder};
use serde_yaml;

error_chain! {
    foreign_links {
        ParseError(::serde_yaml::Error);
        IOError(::std::io::Error);
    }

    errors {
        NoHomeError {
            description("could not locate home directory")
        }

        BadProfileError(browser: String, profile: String) {
            description("bad configuration")
            display("bad configuration: no such browser '{}' defined for profile '{}'",
                    browser,
                    profile)
        }
    }
}

#[doc(hidden)]
/// A regex that is equivalent to the "*." wildcard pattern.
///
/// This regular expression matches any subdomains (and subdomains of subdomains, etc.). It replaces
/// the "*." in entries such as "*.example.com"
static WC_REPLACEMENT: &'static str = r"(?:.+\.)?";

#[doc(hidden)]
/// The prefix for the generated regular expression.
///
/// This prefix will make the regular expression match both HTTP and HTTPs URLs.
static RE_PREFIX: &'static str = r"^https?://(?:";

#[doc(hidden)]
/// The suffix for the generated regular expression.
///
/// This is the corresponding closing parenthesis for the opening parenthesis in `RE_PREFIX`.
static RE_SUFFIX: char = ')';

#[doc(hidden)]
/// Transform a string into a regular expression that will match that URL.
///
/// URLs of the form "*.example.com" will match any subdomains of "example.com" in addition to
/// "example.com".
fn escape_url(url: &str) -> String {
    if url.starts_with("*.") {
        let rest = url.split_at(2).1;
        let mut escaped = String::with_capacity(WC_REPLACEMENT.len() + url.len() - 2);

        escaped.push_str(WC_REPLACEMENT);
        escaped.push_str(&regex::escape(&rest));
        escaped
    } else {
        regex::escape(&url)
    }
}

#[doc(hidden)]
/// Transform a list of URLs to a regular expression that matches them.
///
/// URLs should not include the scheme: the resulting regular expression will match both HTTP and
/// HTTPs URLs.
///
/// If the list is empty, no regular expression is returned.
fn urls_to_regex(urls: &Vec<String>) -> Option<Regex> {
    if urls.is_empty() {
        None
    } else {

        let re_body = urls.iter()
            .map(|url| escape_url(url))
            .collect::<Vec<_>>()
            .join("|");

        let mut pattern = String::with_capacity(RE_PREFIX.len() + re_body.len() + 1);
        pattern.push_str(RE_PREFIX);
        pattern.push_str(&re_body);
        pattern.push(RE_SUFFIX);

        // build() returns a ::std::result::Result<regex::Regex, regex::Error>, but panicing is not
        // possible because we escape all user input (and we therefore cannot generate an invalid
        // regular expression).
        Some(RegexBuilder::new(&pattern)
            .case_insensitive(true)
            .build()
            .unwrap())
    }
}

#[derive(Clone, Debug, Deserialize)]
/// A default browser and profile combination.
pub struct DefaultProfile {
    /// The default browser.
    pub browser: String,
    /// The default profile to use for the browser.
    pub profile: String,
}

#[derive(Debug)]
/// A browser profile.
///
/// A browser profile describes a browser and profile pair and the corresponding regex. All URLs
/// that match the regular expression will launch in the given browser.
pub struct BrowserProfile {
    /// The browser to use.
    ///
    /// This must be defined in the list of browsers in the parent [`Config`](struct.Config.html)
    /// struct.
    pub browser: String,
    /// The profile to use.
    pub profile: String,
    /// The regular expression to match against.
    ///
    /// If a URL matches this regular expression, the corresponding browser will be launched with the
    /// corresponding profile.
    pub regex: Regex,
}

#[derive(Debug)]
/// Metabrowser configuration.
///
/// The configuration specifies the set of browsers and how they can be launched, as well as the
pub struct Config {
    /// A mapping of browser names to their command line parameters for launching with a specified
    /// profile.
    pub browsers: HashMap<String, Vec<String>>,
    /// The default browser and profile to use for URLs that do not match any regular expressions in
    /// the list of profiles.
    pub default: DefaultProfile,
    /// The specified browser-profile pairs and their corresponding regular expressions.
    pub profiles: Vec<BrowserProfile>,
}

#[derive(Debug, Deserialize)]
#[doc(hidden)]
/// A parsed browser profile.
struct ParsedBrowserProfile {
    browser: String,
    profile: String,
    urls: Vec<String>,
}

#[derive(Debug, Deserialize)]
#[doc(hidden)]
/// The parsed configuration.
///
/// The configuration must be processed into a Config object before it is usable.
struct ParsedConfig {
    browsers: HashMap<String, Vec<String>>,
    default: DefaultProfile,
    profiles: Vec<ParsedBrowserProfile>,
}

impl ParsedConfig {
    #[doc(hidden)]
    /// Read the configuration from a file.
    ///
    /// The configuration must be further procsed
    fn from_file(p: &Path) -> Result<Self> {
        let mut buf = String::new();
        let mut f = try!(File::open(p));
        try!(f.read_to_string(&mut buf));
        try!(serde_yaml::from_str::<Self>(&buf))
    }

    #[doc(hidden)]
    /// Validate a parsed configuration.
    fn validate(self) -> Result<Self> {

        for profile in &self.profiles {
            if !self.browsers.contains_key(&profile.browser) {
                return Err(Error::from(ErrorKind::BadProfileError(profile.browser,
                                                                  profile.profile)));
            }
        }
        Ok(self)
    }
}

impl Into<Config> for ParsedConfig {
    #[doc(hidden)]
    /// Transform a ParsedConfig into a usable Config.
    ///
    /// Transformation of a ParsedConfig into a Config involves transforming the list of URLs into
    /// a regular expression that matches all of those URLs. This process cannot fail.
    fn into(self) -> Config {
        let profiles = self.profiles
            .into_iter()
            .filter_map(|p| urls_to_regex(&p.urls).map(|re| (re, p)))
            .map(|(re, p)| {
                BrowserProfile {
                    browser: p.browser,
                    profile: p.profile,
                    regex: re,
                }
            })
            .collect::<Vec<_>>();

        Config {
            browsers: self.browsers,
            default: self.default,
            profiles: profiles,
        }
    }
}

impl Config {
    /// Read the configuration from the file.
    pub fn from_file(p: &Path) -> Result<Config> {
        ParsedConfig::from_file(p)
            .and_then(ParsedConfig::validate)
            .map(::std::convert::Into::into)
    }
}
<commit_msg>Clean up formatting<commit_after>use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use regex;
use regex::{Regex, RegexBuilder};
use serde_yaml;

error_chain! {
    foreign_links {
        ParseError(::serde_yaml::Error);
        IOError(::std::io::Error);
    }

    errors {
        NoHomeError {
            description("could not locate home directory")
        }

        BadProfileError(browser: String, profile: String) {
            description("bad configuration")
            display("bad configuration: no such browser '{}' defined for profile '{}'",
                    browser,
                    profile)
        }
    }
}

#[doc(hidden)]
/// A regex that is equivalent to the "*." wildcard pattern.
///
/// This regular expression matches any subdomains (and subdomains of subdomains, etc.). It replaces
/// the "*." in entries such as "*.example.com"
static WC_REPLACEMENT: &'static str = r"(?:.+\.)?";

#[doc(hidden)]
/// The prefix for the generated regular expression.
///
/// This prefix will make the regular expression match both HTTP and HTTPs URLs.
static RE_PREFIX: &'static str = r"^https?://(?:";

#[doc(hidden)]
/// The suffix for the generated regular expression.
///
/// This is the corresponding closing parenthesis for the opening parenthesis in `RE_PREFIX`.
static RE_SUFFIX: char = ')';

#[doc(hidden)]
/// Transform a string into a regular expression that will match that URL.
///
/// URLs of the form "*.example.com" will match any subdomains of "example.com" in addition to
/// "example.com".
fn escape_url(url: &str) -> String {
    if url.starts_with("*.") {
        let rest = url.split_at(2).1;
        let mut escaped = String::with_capacity(WC_REPLACEMENT.len() + url.len() - 2);

        escaped.push_str(WC_REPLACEMENT);
        escaped.push_str(&regex::escape(&rest));
        escaped
    } else {
        regex::escape(&url)
    }
}

#[doc(hidden)]
/// Transform a list of URLs to a regular expression that matches them.
///
/// URLs should not include the scheme: the resulting regular expression will match both HTTP and
/// HTTPs URLs.
///
/// If the list is empty, no regular expression is returned.
fn urls_to_regex(urls: &Vec<String>) -> Option<Regex> {
    if urls.is_empty() {
        None
    } else {
        let re_body = urls.iter()
            .map(|url| escape_url(url))
            .collect::<Vec<_>>()
            .join("|");

        let mut pattern = String::with_capacity(RE_PREFIX.len() + re_body.len() + 1);
        pattern.push_str(RE_PREFIX);
        pattern.push_str(&re_body);
        pattern.push(RE_SUFFIX);

        // build() returns a ::std::result::Result<regex::Regex, regex::Error>, but panicing is not
        // possible because we escape all user input (and we therefore cannot generate an invalid
        // regular expression).
        Some(RegexBuilder::new(&pattern)
            .case_insensitive(true)
            .build()
            .unwrap())
    }
}

#[derive(Clone, Debug, Deserialize)]
/// A default browser and profile combination.
pub struct DefaultProfile {
    /// The default browser.
    pub browser: String,

    /// The default profile to use for the browser.
    pub profile: String,
}

#[derive(Debug)]
/// A browser profile.
///
/// A browser profile describes a browser and profile pair and the corresponding regex. All URLs
/// that match the regular expression will launch in the given browser.
pub struct BrowserProfile {
    /// The browser to use.
    ///
    /// This must be defined in the list of browsers in the parent [`Config`](struct.Config.html)
    /// struct.
    pub browser: String,

    /// The profile to use.
    pub profile: String,

    /// The regular expression to match against.
    ///
    /// If a URL matches this regular expression, the corresponding browser will be launched with
    /// the corresponding profile.
    pub regex: Regex,
}

#[derive(Debug)]
/// Metabrowser configuration.
///
/// The configuration specifies the set of browsers and how they can be launched, as well as the
pub struct Config {
    /// A mapping of browser names to their command line parameters for launching with a specified
    /// profile.
    pub browsers: HashMap<String, Vec<String>>,

    /// The default browser and profile to use for URLs that do not match any regular expressions in
    /// the list of profiles.
    pub default: DefaultProfile,

    /// The specified browser-profile pairs and their corresponding regular expressions.
    pub profiles: Vec<BrowserProfile>,
}

#[derive(Debug, Deserialize)]
#[doc(hidden)]
/// A parsed browser profile.
struct ParsedBrowserProfile {
    browser: String,
    profile: String,
    urls: Vec<String>,
}

#[derive(Debug, Deserialize)]
#[doc(hidden)]
/// The parsed configuration.
///
/// The configuration must be processed into a Config object before it is usable.
struct ParsedConfig {
    browsers: HashMap<String, Vec<String>>,
    default: DefaultProfile,
    profiles: Vec<ParsedBrowserProfile>,
}

impl ParsedConfig {
    #[doc(hidden)]
    /// Read the configuration from a file.
    ///
    /// The configuration must be further procsed
    fn from_file(p: &Path) -> Result<Self> {
        let mut buf = String::new();
        let mut f = try!(File::open(p));
        try!(f.read_to_string(&mut buf));
        try!(serde_yaml::from_str::<Self>(&buf))
    }

    #[doc(hidden)]
    /// Validate a parsed configuration.
    fn validate(self) -> Result<Self> {

        for profile in &self.profiles {
            if !self.browsers.contains_key(&profile.browser) {
                return Err(Error::from(ErrorKind::BadProfileError(profile.browser,
                                                                  profile.profile)));
            }
        }
        Ok(self)
    }
}

impl Into<Config> for ParsedConfig {
    #[doc(hidden)]
    /// Transform a ParsedConfig into a usable Config.
    ///
    /// Transformation of a ParsedConfig into a Config involves transforming the list of URLs into a
    /// regular expression that matches all of those URLs. This process cannot fail.
    fn into(self) -> Config {
        let profiles = self.profiles
            .into_iter()
            .filter_map(|p| urls_to_regex(&p.urls).map(|re| (re, p)))
            .map(|(re, p)| {
                BrowserProfile {
                    browser: p.browser,
                    profile: p.profile,
                    regex: re,
                }
            })
            .collect::<Vec<_>>();

        Config {
            browsers: self.browsers,
            default: self.default,
            profiles: profiles,
        }
    }
}

impl Config {
    /// Read the configuration from the file.
    pub fn from_file(p: &Path) -> Result<Config> {
        ParsedConfig::from_file(p)
            .and_then(ParsedConfig::validate)
            .map(::std::convert::Into::into)
    }
}
<|endoftext|>


<commit_before>use crate::cli::Cli;
use crate::status;
use crate::Result;

use config::{Config, Environment, File, FileFormat};
use std::{env, sync};

static ERROR_CONFIG_WRITE: &str = "Unable to gain write lock on global app config";
static ERROR_CONFIG_READ: &str = "Unable to gain read lock on global app config";

lazy_static! {
    pub static ref CONF: sync::RwLock<Config> = sync::RwLock::new(Config::default());
}

impl CONF {
    pub fn defaults(&self) -> Result<()> {
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        config
            .set_default("debug", false)?
            .set_default("quiet", false)?
            .set_default("verbose", false)?
            .set_default("language", crate::DEFAULT_LOCALE)?
            .set_default("path", "./")?;
        Ok(())
    }

    pub fn merge_env(&self) -> Result<()> {
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        if let Some(lang) = env::var_os("LANG") {
            if lang.len() > 0 && env::var_os("CASILE_LANGUAGELANG").is_none() {
                env::set_var("CASILE_LANGUAGE", lang)
            }
        }
        config.merge(Environment::with_prefix("casile"))?;
        Ok(())
    }

    pub fn merge_files(&self) -> Result<()> {
        let confs = status::get_confs()?;
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        for (_, conf) in confs.iter().enumerate() {
            let f = File::new(conf.to_str().unwrap(), FileFormat::Yaml);
            config.merge(f).unwrap();
        }
        Ok(())
    }

    pub fn merge_args(&self, args: &Cli) -> Result<()> {
        if args.debug {
            self.set_bool("debug", true)?;
            self.set_bool("verbose", true)?;
        } else if args.verbose {
            self.set_bool("verbose", true)?;
        } else if args.quiet {
            self.set_bool("quiet", true)?;
        }
        if let Some(path) = &args.path.to_str() {
            self.set_str("path", path)?;
        }
        if let Some(language) = &args.language {
            self.set_str("language", language)?;
        };
        Ok(())
    }

    pub fn set_bool(&self, key: &str, val: bool) -> Result<()> {
        self.write().expect(ERROR_CONFIG_WRITE).set(key, val)?;
        Ok(())
    }

    pub fn get_bool(&self, key: &str) -> Result<bool> {
        Ok(self.read().expect(ERROR_CONFIG_READ).get_bool(key)?)
    }

    pub fn set_str(&self, key: &str, val: &str) -> Result<()> {
        self.write().expect(ERROR_CONFIG_WRITE).set(key, val)?;
        Ok(())
    }

    pub fn get_string(&self, key: &str) -> Result<String> {
        Ok(self.read().expect(ERROR_CONFIG_READ).get_str(key)?)
    }
}
<commit_msg>refactor(cli): Deal with lint warnings, bubble error instead of suppressing<commit_after>use crate::cli::Cli;
use crate::status;
use crate::Result;

use config::{Config, Environment, File, FileFormat};
use std::{env, sync};

static ERROR_CONFIG_WRITE: &str = "Unable to gain write lock on global app config";
static ERROR_CONFIG_READ: &str = "Unable to gain read lock on global app config";

lazy_static! {
    pub static ref CONF: sync::RwLock<Config> = sync::RwLock::new(Config::default());
}

impl CONF {
    pub fn defaults(&self) -> Result<()> {
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        config
            .set_default("debug", false)?
            .set_default("quiet", false)?
            .set_default("verbose", false)?
            .set_default("language", crate::DEFAULT_LOCALE)?
            .set_default("path", "./")?;
        Ok(())
    }

    pub fn merge_env(&self) -> Result<()> {
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        if let Some(lang) = env::var_os("LANG") {
            if lang.len() > 0 && env::var_os("CASILE_LANGUAGELANG").is_none() {
                env::set_var("CASILE_LANGUAGE", lang)
            }
        }
        config.merge(Environment::with_prefix("casile"))?;
        Ok(())
    }

    pub fn merge_files(&self) -> Result<()> {
        let confs = status::get_confs()?;
        let mut config = self.write().expect(ERROR_CONFIG_WRITE);
        for (_, conf) in confs.iter().enumerate() {
            let f = File::new(conf.to_str().unwrap(), FileFormat::Yaml);
            config.merge(f)?;
        }
        Ok(())
    }

    pub fn merge_args(&self, args: &Cli) -> Result<()> {
        if args.debug {
            self.set_bool("debug", true)?;
            self.set_bool("verbose", true)?;
        } else if args.verbose {
            self.set_bool("verbose", true)?;
        } else if args.quiet {
            self.set_bool("quiet", true)?;
        }
        if let Some(path) = &args.path.to_str() {
            self.set_str("path", path)?;
        }
        if let Some(language) = &args.language {
            self.set_str("language", language)?;
        };
        Ok(())
    }

    pub fn set_bool(&self, key: &str, val: bool) -> Result<()> {
        self.write().expect(ERROR_CONFIG_WRITE).set(key, val)?;
        Ok(())
    }

    pub fn get_bool(&self, key: &str) -> Result<bool> {
        Ok(self.read().expect(ERROR_CONFIG_READ).get_bool(key)?)
    }

    pub fn set_str(&self, key: &str, val: &str) -> Result<()> {
        self.write().expect(ERROR_CONFIG_WRITE).set(key, val)?;
        Ok(())
    }

    pub fn get_string(&self, key: &str) -> Result<String> {
        Ok(self.read().expect(ERROR_CONFIG_READ).get_str(key)?)
    }
}
<|endoftext|>


<commit_before>// In heavy WIP
use std::fs::File;
use std::io::Read;
use std::path::Path;
use toml;

use error::PackageError;
use repository::RepositoryList;

/// Represents the config file.
#[derive(Deserialize)]
pub struct Config {
    pub buffer_size: Option<u64>,
    pub config_path: Option<String>,
    pub download_path: Option<String>,
    pub repository: Option<RepositoryList>,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            buffer_size: Some(65536),
            config_path: Some(String::from("~/.wiz/config/")),
            download_path: Some(String::from("~/.wiz/downloads/")),
            repository: Some(RepositoryList(Vec::new())),
        }
    }
}

/// Internal macro, to ease the implementation of `fill_with_default`.
macro_rules! set_on_none {
    ($dest:ident, $key:ident, $value:expr) => (
        match $dest {
            Self { $key: ref mut x, .. } => {
                match x {
                    &mut Some(_) => {},
                    &mut None => *x = $value,
                }
            }
        }

        // Wait for RFC-2086 to be implemented first.
        /*
            [allow(irrefutable_let_pattern)]
            if let Config { $key: mut x, .. } = $dest {
                if let None = x { x = $value }
            }
        */
    );
}

impl Config {
    /// Read the config file from the config path specified in `path`.
    /// If the config file is read & parsed properly, it should return
    /// a `Config`.
    pub fn read_from(path: &Path) -> Result<Self, PackageError> {
        // Read the config file into a string
        let mut config = File::open(path)?;
        let mut content = String::new();
        config.read_to_string(&mut content)?;

        // Try parsing the string, and convert it into a `Config`.
        let config = content.parse::<toml::Value>()?;
        let config = config.try_into::<Self>()?;
        Ok(config)
    }

    /// Read the config file from the default config path.
    /// If the config file is read & parsed properly, it should return
    /// a `Config`.
    pub fn read_from_default() -> Result<Self, PackageError> {
        if let Self { config_path: Some(path), .. } = Self::default() {
            Self::read_from(Path::new(&path))
        } else {
            // This should never, ever, happen.
            Err(PackageError::Parsing(
                String::from("Unable to get config_path from the default cfg.")
            ))
        }
    }

    pub fn fill_with_default(mut self) -> Self {
        let Self {
            buffer_size: buf_size,
            config_path: conf_path,
            download_path: dl_path,
            repository: repo,
        } = Self::default();

        set_on_none!(self, buffer_size, buf_size);
        set_on_none!(self, config_path, conf_path);
        set_on_none!(self, download_path, dl_path);
        set_on_none!(self, repository, repo);

        self
    }
}
<commit_msg>Adding configurable unpacking paths<commit_after>// In heavy WIP
use std::fs::File;
use std::io::Read;
use std::path::Path;
use toml;

use error::PackageError;
use repository::RepositoryUrlList;

/// Represents the config file.
#[derive(Deserialize)]
pub struct Config {
    pub buffer_size: Option<u64>,
    pub config_path: Option<String>,
    pub download_path: Option<String>,
    pub unpack_path: Option<String>,
    pub repository: Option<RepositoryUrlList>,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            buffer_size: Some(65536),
            config_path: Some(String::from("~/.wiz/config/")),
            download_path: Some(String::from("~/.wiz/downloads/")),
            unpack_path: Some(String::from("~/.wiz/downloads/unpacked/")),
            repository: Some(RepositoryUrlList(Vec::new())),
        }
    }
}

/// Internal macro, to ease the implementation of `fill_with_default`.
macro_rules! set_on_none {
    ($dest:ident, $key:ident, $value:expr) => (
        match $dest {
            Self { $key: ref mut x, .. } => {
                match x {
                    &mut Some(_) => {},
                    &mut None => *x = $value,
                }
            }
        }

        // Wait for RFC-2086 to be implemented first.
        /*
            [allow(irrefutable_let_pattern)]
            if let Config { $key: mut x, .. } = $dest {
                if let None = x { x = $value }
            }
        */
    );
}

impl Config {
    /// Read the config file from the config path specified in `path`.
    /// If the config file is read & parsed properly, it should return
    /// a `Config`.
    pub fn read_from(path: &Path) -> Result<Self, PackageError> {
        // Read the config file into a string
        let mut config = File::open(path)?;
        let mut content = String::new();
        config.read_to_string(&mut content)?;

        // Try parsing the string, and convert it into a `Config`.
        let config = content.parse::<toml::Value>()?;
        let config = config.try_into::<Self>()?;
        Ok(config)
    }

    /// Read the config file from the default config path.
    /// If the config file is read & parsed properly, it should return
    /// a `Config`.
    pub fn read_from_default() -> Result<Self, PackageError> {
        if let Self { config_path: Some(path), .. } = Self::default() {
            Self::read_from(Path::new(&path))
        } else {
            // This should never, ever, happen.
            Err(PackageError::Parsing(
                String::from("Unable to get config_path from the default cfg.")
            ))
        }
    }

    /// This function sets the None(s) in the config, to the default values.
    pub fn fill_with_default(mut self) -> Self {
        // Destructuring the default configs into individual variables.
        let Self {
            buffer_size: buf_size,
            config_path: conf_path,
            download_path: dl_path,
            unpack_path: unpk_path,
            repository: repo,
        } = Self::default();

        // If there are None(s), set them to the default value.
        set_on_none!(self, buffer_size, buf_size);
        set_on_none!(self, config_path, conf_path);
        set_on_none!(self, download_path, dl_path);
        set_on_none!(self, unpack_path, unpk_path);
        set_on_none!(self, repository, repo);

        self
    }
}
<|endoftext|>


<commit_before>//! defines configuration

use std::borrow::{Borrow, Cow};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;
use shellexpand;
use errors::Result;

#[cfg_attr(rustfmt, rustfmt_skip)]
const CANDIDATES: &'static [&'static str] =
  &[
    "~/.config/rhq/config"
  , "~/.rhqconfig"
  ];

/// configuration load from config files
#[derive(Debug)]
pub struct Config {
  pub root: PathBuf,
  pub supplements: Vec<PathBuf>,
}

impl Config {
  pub fn roots(&self) -> Vec<&Path> {
    let mut roots = vec![self.root.as_path()];
    roots.extend(self.supplements.iter().map(|ref s| s.as_path()));
    roots
  }
}

fn make_path_buf<'a>(s: Cow<'a, str>) -> Result<PathBuf> {
  shellexpand::full(s.borrow() as &str)
    .map(|s| PathBuf::from(s.borrow() as &str))
    .map_err(Into::into)
}

fn read_toml_table<P: AsRef<Path>>(path: P) -> Result<toml::value::Table> {
  let mut content = String::new();
  File::open(path)?.read_to_string(&mut content)?;
  toml::de::from_str(&content).map_err(Into::into)
}

pub fn read_all_config() -> Result<Config> {
  let mut root = None;
  let mut supplements = Vec::new();

  for path in CANDIDATES.iter()
    .map(|&path| make_path_buf(path.into()).unwrap())
    .filter(|ref path| path.is_file()) {
    let config = read_toml_table(path)?;

    if let Some(r) = config.get("root") {
      let r = r.as_str().ok_or("config.root is not a string")?;
      root = Some(r.to_owned());
    }

    if let Some(supp) = config.get("supplements") {
      let supp = supp.as_array().ok_or("config.supplements is not an array")?;
      for s in supp {
        let s = s.as_str().ok_or("config.supplements contains an invalid element")?;
        let s = make_path_buf(s.into())?;
        supplements.push(s);
      }
    }
  }

  let root = root.map(|r| Cow::Owned(r)).unwrap_or("~/.rhq".into());
  let root = make_path_buf(root)?;

  Ok(Config {
    root: root,
    supplements: supplements,
  })
}

impl ::std::fmt::Display for Config {
  fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
    write!(f, "{:?}", self)
  }
}
<commit_msg>change default root directory<commit_after>//! defines configuration

use std::borrow::{Borrow, Cow};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use toml;
use shellexpand;
use errors::Result;

#[cfg_attr(rustfmt, rustfmt_skip)]
const CANDIDATES: &'static [&'static str] =
  &[
    "~/.config/rhq/config"
  , "~/.config/rhq/config.toml"
  , "~/.rhqconfig"
  ];

/// configuration load from config files
#[derive(Debug)]
pub struct Config {
  pub root: PathBuf,
  pub supplements: Vec<PathBuf>,
}

impl Config {
  pub fn roots(&self) -> Vec<&Path> {
    let mut roots = vec![self.root.as_path()];
    roots.extend(self.supplements.iter().map(|ref s| s.as_path()));
    roots
  }
}

fn make_path_buf<'a>(s: Cow<'a, str>) -> Result<PathBuf> {
  shellexpand::full(s.borrow() as &str)
    .map(|s| PathBuf::from(s.borrow() as &str))
    .map_err(Into::into)
}

fn read_toml_table<P: AsRef<Path>>(path: P) -> Result<toml::value::Table> {
  let mut content = String::new();
  File::open(path)?.read_to_string(&mut content)?;
  toml::de::from_str(&content).map_err(Into::into)
}

pub fn read_all_config() -> Result<Config> {
  let mut root = None;
  let mut supplements = Vec::new();

  for path in CANDIDATES.iter()
    .map(|&path| make_path_buf(path.into()).unwrap())
    .filter(|ref path| path.is_file()) {
    let config = read_toml_table(path)?;

    if let Some(r) = config.get("root") {
      let r = r.as_str().ok_or("config.root is not a string")?;
      root = Some(r.to_owned());
    }

    if let Some(supp) = config.get("supplements") {
      let supp = supp.as_array().ok_or("config.supplements is not an array")?;
      for s in supp {
        let s = s.as_str().ok_or("config.supplements contains an invalid element")?;
        let s = make_path_buf(s.into())?;
        supplements.push(s);
      }
    }
  }

  let root = root.map(|r| Cow::Owned(r)).unwrap_or("~/rhq".into());
  let root = make_path_buf(root)?;

  Ok(Config {
    root: root,
    supplements: supplements,
  })
}

impl ::std::fmt::Display for Config {
  fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
    write!(f, "{:?}", self)
  }
}
<|endoftext|>


<commit_before>use std::cmp::{max, min};
use std::collections::HashMap;

use cassowary::{Constraint, Expression, Solver, Variable};
use cassowary::WeightedRelation::*;
use cassowary::strength::{REQUIRED, WEAK};

use terminal::Terminal;
use backend::Backend;

#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
    Horizontal,
    Vertical,
}

/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
/// area they are supposed to render to.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Rect {
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
}

impl Default for Rect {
    fn default() -> Rect {
        Rect {
            x: 0,
            y: 0,
            width: 0,
            height: 0,
        }
    }
}

impl Rect {
    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
        Rect {
            x: x,
            y: y,
            width: width,
            height: height,
        }
    }

    pub fn area(&self) -> u16 {
        self.width * self.height
    }

    pub fn left(&self) -> u16 {
        self.x
    }

    pub fn right(&self) -> u16 {
        self.x + self.width
    }

    pub fn top(&self) -> u16 {
        self.y
    }

    pub fn bottom(&self) -> u16 {
        self.y + self.height
    }

    pub fn inner(&self, margin: u16) -> Rect {
        if self.width < 2 * margin || self.height < 2 * margin {
            Rect::default()
        } else {
            Rect {
                x: self.x + margin,
                y: self.y + margin,
                width: self.width - 2 * margin,
                height: self.height - 2 * margin,
            }
        }
    }

    pub fn union(&self, other: &Rect) -> Rect {
        let x1 = min(self.x, other.x);
        let y1 = min(self.y, other.y);
        let x2 = max(self.x + self.width, other.x + other.width);
        let y2 = max(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

    pub fn intersection(&self, other: &Rect) -> Rect {
        let x1 = max(self.x, other.x);
        let y1 = max(self.y, other.y);
        let x2 = min(self.x + self.width, other.x + other.width);
        let y2 = min(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

    pub fn intersects(&self, other: &Rect) -> bool {
        self.x < other.x + other.width && self.x + self.width > other.x
            && self.y < other.y + other.height && self.y + self.height > other.y
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Size {
    Fixed(u16),
    Percent(u16),
    Max(u16),
    Min(u16),
}

/// Wrapper function around the cassowary-rs solver to be able to split a given
/// area into smaller ones based on the preferred widths or heights and the direction.
///
/// # Examples
/// ```
/// # extern crate tui;
/// # use tui::layout::{Rect, Size, Direction, split};
///
/// # fn main() {
///     let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
///                        &Direction::Vertical,
///                        0,
///                        &[Size::Fixed(5), Size::Min(0)]);
///     assert_eq!(chunks, vec![Rect{x:2, y: 2, width: 10, height: 5},
///                             Rect{x: 2, y: 7, width: 10, height: 5}])
/// # }
///
/// ```
pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
    let mut solver = Solver::new();
    let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
    let elements = sizes
        .iter()
        .map(|_| Element::new())
        .collect::<Vec<Element>>();
    let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
    let dest_area = area.inner(margin);
    for (i, e) in elements.iter().enumerate() {
        vars.insert(e.x, (i, 0));
        vars.insert(e.y, (i, 1));
        vars.insert(e.width, (i, 2));
        vars.insert(e.height, (i, 3));
    }
    let mut constraints: Vec<Constraint> = Vec::with_capacity(elements.len() * 4 + sizes.len() * 6);
    for elt in &elements {
        constraints.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
        constraints.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
        constraints.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
        constraints.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
    }
    if let Some(first) = elements.first() {
        constraints.push(match *dir {
            Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
            Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
        });
    }
    if let Some(last) = elements.last() {
        constraints.push(match *dir {
            Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
            Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
        });
    }
    match *dir {
        Direction::Horizontal => {
            for pair in elements.windows(2) {
                constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
            }
            for (i, size) in sizes.iter().enumerate() {
                constraints.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
                constraints.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
                constraints.push(match *size {
                    Size::Fixed(v) => elements[i].width | EQ(WEAK) | f64::from(v),
                    Size::Percent(v) => {
                        elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
                    }
                    Size::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
                    Size::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
                });
            }
        }
        Direction::Vertical => {
            for pair in elements.windows(2) {
                constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
            }
            for (i, size) in sizes.iter().enumerate() {
                constraints.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
                constraints.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
                constraints.push(match *size {
                    Size::Fixed(v) => elements[i].height | EQ(WEAK) | f64::from(v),
                    Size::Percent(v) => {
                        elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
                    }
                    Size::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
                    Size::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
                });
            }
        }
    }
    solver.add_constraints(&constraints).unwrap();
    for &(var, value) in solver.fetch_changes() {
        let (index, attr) = vars[&var];
        let value = value as u16;
        match attr {
            0 => {
                results[index].x = value;
            }
            1 => {
                results[index].y = value;
            }
            2 => {
                results[index].width = value;
            }
            3 => {
                results[index].height = value;
            }
            _ => {}
        }
    }
    // Fix imprecision by extending the last item a bit if necessary
    if let Some(last) = results.last_mut() {
        match *dir {
            Direction::Vertical => {
                last.height = dest_area.bottom() - last.y;
            }
            Direction::Horizontal => {
                last.width = dest_area.right() - last.x;
            }
        }
    }
    results
}

/// A container used by the solver inside split
struct Element {
    x: Variable,
    y: Variable,
    width: Variable,
    height: Variable,
}

impl Element {
    fn new() -> Element {
        Element {
            x: Variable::new(),
            y: Variable::new(),
            width: Variable::new(),
            height: Variable::new(),
        }
    }

    fn left(&self) -> Variable {
        self.x
    }

    fn top(&self) -> Variable {
        self.y
    }

    fn right(&self) -> Expression {
        self.x + self.width
    }

    fn bottom(&self) -> Expression {
        self.y + self.height
    }
}

/// Describes a layout and may be used to group widgets in a specific area of the terminal
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// use tui::layout::{Group, Direction, Size};
/// # fn main() {
///     Group::default()
///         .direction(Direction::Vertical)
///         .margin(0)
///         .sizes(&[Size::Percent(50), Size::Percent(50)]);
/// # }
/// ```
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct Group {
    pub direction: Direction,
    pub margin: u16,
    pub sizes: Vec<Size>,
}

impl Default for Group {
    fn default() -> Group {
        Group {
            direction: Direction::Horizontal,
            margin: 0,
            sizes: Vec::new(),
        }
    }
}

impl Group {
    pub fn direction(&mut self, direction: Direction) -> &mut Group {
        self.direction = direction;
        self
    }

    pub fn margin(&mut self, margin: u16) -> &mut Group {
        self.margin = margin;
        self
    }

    pub fn sizes(&mut self, sizes: &[Size]) -> &mut Group {
        self.sizes = Vec::from(sizes);
        self
    }
    pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, mut f: F)
    where
        B: Backend,
        F: FnMut(&mut Terminal<B>, &[Rect]),
    {
        let chunks = t.compute_layout(self, area);
        f(t, &chunks);
    }
}
<commit_msg>[lib] Fix conversion from cassowary-rs results to internal layouts<commit_after>use std::cmp::{max, min};
use std::collections::HashMap;

use cassowary::{Constraint, Expression, Solver, Variable};
use cassowary::WeightedRelation::*;
use cassowary::strength::{REQUIRED, WEAK};

use terminal::Terminal;
use backend::Backend;

#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
    Horizontal,
    Vertical,
}

/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
/// area they are supposed to render to.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Rect {
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
}

impl Default for Rect {
    fn default() -> Rect {
        Rect {
            x: 0,
            y: 0,
            width: 0,
            height: 0,
        }
    }
}

impl Rect {
    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
        Rect {
            x: x,
            y: y,
            width: width,
            height: height,
        }
    }

    pub fn area(&self) -> u16 {
        self.width * self.height
    }

    pub fn left(&self) -> u16 {
        self.x
    }

    pub fn right(&self) -> u16 {
        self.x + self.width
    }

    pub fn top(&self) -> u16 {
        self.y
    }

    pub fn bottom(&self) -> u16 {
        self.y + self.height
    }

    pub fn inner(&self, margin: u16) -> Rect {
        if self.width < 2 * margin || self.height < 2 * margin {
            Rect::default()
        } else {
            Rect {
                x: self.x + margin,
                y: self.y + margin,
                width: self.width - 2 * margin,
                height: self.height - 2 * margin,
            }
        }
    }

    pub fn union(&self, other: &Rect) -> Rect {
        let x1 = min(self.x, other.x);
        let y1 = min(self.y, other.y);
        let x2 = max(self.x + self.width, other.x + other.width);
        let y2 = max(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

    pub fn intersection(&self, other: &Rect) -> Rect {
        let x1 = max(self.x, other.x);
        let y1 = max(self.y, other.y);
        let x2 = min(self.x + self.width, other.x + other.width);
        let y2 = min(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

    pub fn intersects(&self, other: &Rect) -> bool {
        self.x < other.x + other.width && self.x + self.width > other.x
            && self.y < other.y + other.height && self.y + self.height > other.y
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Size {
    Fixed(u16),
    Percent(u16),
    Max(u16),
    Min(u16),
}

/// Wrapper function around the cassowary-rs solver to be able to split a given
/// area into smaller ones based on the preferred widths or heights and the direction.
///
/// # Examples
/// ```
/// # extern crate tui;
/// # use tui::layout::{Rect, Size, Direction, split};
///
/// # fn main() {
///     let chunks = split(&Rect{x: 2, y: 2, width: 10, height: 10},
///                        &Direction::Vertical,
///                        0,
///                        &[Size::Fixed(5), Size::Min(0)]);
///     assert_eq!(chunks, vec![Rect{x:2, y: 2, width: 10, height: 5},
///                             Rect{x: 2, y: 7, width: 10, height: 5}])
/// # }
///
/// ```
pub fn split(area: &Rect, dir: &Direction, margin: u16, sizes: &[Size]) -> Vec<Rect> {
    let mut solver = Solver::new();
    let mut vars: HashMap<Variable, (usize, usize)> = HashMap::new();
    let elements = sizes
        .iter()
        .map(|_| Element::new())
        .collect::<Vec<Element>>();
    let mut results = sizes.iter().map(|_| Rect::default()).collect::<Vec<Rect>>();
    let dest_area = area.inner(margin);
    for (i, e) in elements.iter().enumerate() {
        vars.insert(e.x, (i, 0));
        vars.insert(e.y, (i, 1));
        vars.insert(e.width, (i, 2));
        vars.insert(e.height, (i, 3));
    }
    let mut constraints: Vec<Constraint> = Vec::with_capacity(elements.len() * 4 + sizes.len() * 6);
    for elt in &elements {
        constraints.push(elt.left() | GE(REQUIRED) | f64::from(dest_area.left()));
        constraints.push(elt.top() | GE(REQUIRED) | f64::from(dest_area.top()));
        constraints.push(elt.right() | LE(REQUIRED) | f64::from(dest_area.right()));
        constraints.push(elt.bottom() | LE(REQUIRED) | f64::from(dest_area.bottom()));
    }
    if let Some(first) = elements.first() {
        constraints.push(match *dir {
            Direction::Horizontal => first.left() | EQ(REQUIRED) | f64::from(dest_area.left()),
            Direction::Vertical => first.top() | EQ(REQUIRED) | f64::from(dest_area.top()),
        });
    }
    if let Some(last) = elements.last() {
        constraints.push(match *dir {
            Direction::Horizontal => last.right() | EQ(REQUIRED) | f64::from(dest_area.right()),
            Direction::Vertical => last.bottom() | EQ(REQUIRED) | f64::from(dest_area.bottom()),
        });
    }
    match *dir {
        Direction::Horizontal => {
            for pair in elements.windows(2) {
                constraints.push((pair[0].x + pair[0].width) | EQ(REQUIRED) | pair[1].x);
            }
            for (i, size) in sizes.iter().enumerate() {
                constraints.push(elements[i].y | EQ(REQUIRED) | f64::from(dest_area.y));
                constraints.push(elements[i].height | EQ(REQUIRED) | f64::from(dest_area.height));
                constraints.push(match *size {
                    Size::Fixed(v) => elements[i].width | EQ(WEAK) | f64::from(v),
                    Size::Percent(v) => {
                        elements[i].width | EQ(WEAK) | (f64::from(v * dest_area.width) / 100.0)
                    }
                    Size::Min(v) => elements[i].width | GE(WEAK) | f64::from(v),
                    Size::Max(v) => elements[i].width | LE(WEAK) | f64::from(v),
                });
            }
        }
        Direction::Vertical => {
            for pair in elements.windows(2) {
                constraints.push((pair[0].y + pair[0].height) | EQ(REQUIRED) | pair[1].y);
            }
            for (i, size) in sizes.iter().enumerate() {
                constraints.push(elements[i].x | EQ(REQUIRED) | f64::from(dest_area.x));
                constraints.push(elements[i].width | EQ(REQUIRED) | f64::from(dest_area.width));
                constraints.push(match *size {
                    Size::Fixed(v) => elements[i].height | EQ(WEAK) | f64::from(v),
                    Size::Percent(v) => {
                        elements[i].height | EQ(WEAK) | (f64::from(v * dest_area.height) / 100.0)
                    }
                    Size::Min(v) => elements[i].height | GE(WEAK) | f64::from(v),
                    Size::Max(v) => elements[i].height | LE(WEAK) | f64::from(v),
                });
            }
        }
    }
    solver.add_constraints(&constraints).unwrap();
    for &(var, value) in solver.fetch_changes() {
        let (index, attr) = vars[&var];
        let value = if value.is_sign_negative() {
            0
        } else {
            value as u16
        };
        match attr {
            0 => {
                results[index].x = value;
            }
            1 => {
                results[index].y = value;
            }
            2 => {
                results[index].width = value;
            }
            3 => {
                results[index].height = value;
            }
            _ => {}
        }
    }

    // Fix imprecision by extending the last item a bit if necessary
    if let Some(last) = results.last_mut() {
        match *dir {
            Direction::Vertical => {
                last.height = dest_area.bottom() - last.y;
            }
            Direction::Horizontal => {
                last.width = dest_area.right() - last.x;
            }
        }
    }
    results
}

/// A container used by the solver inside split
struct Element {
    x: Variable,
    y: Variable,
    width: Variable,
    height: Variable,
}

impl Element {
    fn new() -> Element {
        Element {
            x: Variable::new(),
            y: Variable::new(),
            width: Variable::new(),
            height: Variable::new(),
        }
    }

    fn left(&self) -> Variable {
        self.x
    }

    fn top(&self) -> Variable {
        self.y
    }

    fn right(&self) -> Expression {
        self.x + self.width
    }

    fn bottom(&self) -> Expression {
        self.y + self.height
    }
}

/// Describes a layout and may be used to group widgets in a specific area of the terminal
///
/// # Examples
///
/// ```
/// # extern crate tui;
/// use tui::layout::{Group, Direction, Size};
/// # fn main() {
///     Group::default()
///         .direction(Direction::Vertical)
///         .margin(0)
///         .sizes(&[Size::Percent(50), Size::Percent(50)]);
/// # }
/// ```
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct Group {
    pub direction: Direction,
    pub margin: u16,
    pub sizes: Vec<Size>,
}

impl Default for Group {
    fn default() -> Group {
        Group {
            direction: Direction::Horizontal,
            margin: 0,
            sizes: Vec::new(),
        }
    }
}

impl Group {
    pub fn direction(&mut self, direction: Direction) -> &mut Group {
        self.direction = direction;
        self
    }

    pub fn margin(&mut self, margin: u16) -> &mut Group {
        self.margin = margin;
        self
    }

    pub fn sizes(&mut self, sizes: &[Size]) -> &mut Group {
        self.sizes = Vec::from(sizes);
        self
    }
    pub fn render<F, B>(&self, t: &mut Terminal<B>, area: &Rect, mut f: F)
    where
        B: Backend,
        F: FnMut(&mut Terminal<B>, &[Rect]),
    {
        let chunks = t.compute_layout(self, area);
        f(t, &chunks);
    }
}
<|endoftext|>