<commit_before>use std::collections::HashSet;
use time::Timespec;

use conduit::{Request, Response};
use conduit_router::RequestParams;
use pg::GenericConnection;
use pg::rows::Row;

use {Model, Crate};
use db::RequestTransaction;
use util::{RequestUtils, CargoResult, ChainError};
use util::errors::NotFound;

#[derive(Clone)]
pub struct Category {
    pub id: i32,
    pub category: String,
    pub created_at: Timespec,
    pub crates_cnt: i32,
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct EncodableCategory {
    pub id: String,
    pub category: String,
    pub created_at: String,
    pub crates_cnt: i32,
}

impl Category {
    pub fn find_by_category(conn: &GenericConnection, name: &str)
                            -> CargoResult<Option<Category>> {
        let stmt = try!(conn.prepare("SELECT * FROM categories \
                                      WHERE category = $1"));
        let rows = try!(stmt.query(&[&name]));
        Ok(rows.iter().next().map(|r| Model::from_row(&r)))
    }

    pub fn find_all_by_category(conn: &GenericConnection, names: &[String])
                                  -> CargoResult<Vec<Category>> {
        let stmt = try!(conn.prepare("SELECT * FROM categories \
                                      WHERE category = ANY($1)"));
        let rows = try!(stmt.query(&[&names]));
        Ok(rows.iter().map(|r| Model::from_row(&r)).collect())
    }

    pub fn encodable(self) -> EncodableCategory {
        let Category { id: _, crates_cnt, category, created_at } = self;
        EncodableCategory {
            id: category.clone(),
            created_at: ::encode_time(created_at),
            crates_cnt: crates_cnt,
            category: category,
        }
    }

    pub fn update_crate(conn: &GenericConnection,
                        krate: &Crate,
                        categories: &[String]) -> CargoResult<()> {
        let old_categories = try!(krate.categories(conn));
        let old_categories_ids: HashSet<_> = old_categories.iter().map(|cat| {
            cat.id
        }).collect();
        // If a new category specified is not in the database, filter
        // it out and don't add it.
        let new_categories = try!(
            Category::find_all_by_category(conn, categories)
        );
        let new_categories_ids: HashSet<_> = new_categories.iter().map(|cat| {
            cat.id
        }).collect();

        let to_rm: Vec<_> = old_categories_ids
                                .difference(&new_categories_ids)
                                .cloned()
                                .collect();
        let to_add: Vec<_> = new_categories_ids
                                .difference(&old_categories_ids)
                                .cloned()
                                .collect();

        if !to_rm.is_empty() {
            try!(conn.execute("DELETE FROM crates_categories \
                                WHERE category_id = ANY($1) \
                                  AND crate_id = $2",
                              &[&to_rm, &krate.id]));
        }

        if !to_add.is_empty() {
            let insert: Vec<_> = to_add.into_iter().map(|id| {
                format!("({}, {})", krate.id, id)
            }).collect();
            let insert = insert.join(", ");
            try!(conn.execute(&format!("INSERT INTO crates_categories \
                                        (crate_id, category_id) VALUES {}",
                                       insert),
                              &[]));
        }

        Ok(())
    }
}

impl Model for Category {
    fn from_row(row: &Row) -> Category {
        Category {
            id: row.get("id"),
            created_at: row.get("created_at"),
            crates_cnt: row.get("crates_cnt"),
            category: row.get("category"),
        }
    }
    fn table_name(_: Option<Category>) -> &'static str { "categories" }
}

/// Handles the `GET /categories` route.
pub fn index(req: &mut Request) -> CargoResult<Response> {
    let conn = try!(req.tx());
    let (offset, limit) = try!(req.pagination(10, 100));
    let query = req.query();
    let sort = query.get("sort").map_or("alpha", String::as_str);
    let sort_sql = match sort {
        "crates" => "ORDER BY crates_cnt DESC",
        _ => "ORDER BY category ASC",
    };

    // Collect all the categories
    let stmt = try!(conn.prepare(&format!("SELECT * FROM categories {} \
                                           LIMIT $1 OFFSET $2",
                                          sort_sql)));

    let categories: Vec<_> = try!(stmt.query(&[&limit, &offset]))
        .iter()
        .map(|row| {
            let category: Category = Model::from_row(&row);
            category.encodable()
        })
        .collect();

    // Query for the total count of categories
    let total = try!(Category::count(conn));

    #[derive(RustcEncodable)]
    struct R { categories: Vec<EncodableCategory>, meta: Meta }
    #[derive(RustcEncodable)]
    struct Meta { total: i64 }

    Ok(req.json(&R {
        categories: categories,
        meta: Meta { total: total },
    }))
}

/// Handles the `GET /categories/:category_id` route.
pub fn show(req: &mut Request) -> CargoResult<Response> {
    let name = &req.params()["category_id"];
    let conn = try!(req.tx());
    let cat = try!(Category::find_by_category(&*conn, &name));
    let cat = try!(cat.chain_error(|| NotFound));

    #[derive(RustcEncodable)]
    struct R { category: EncodableCategory }
    Ok(req.json(&R { category: cat.encodable() }))
}
<commit_msg>Move chain_error NotFound into find_by_category<commit_after>use std::collections::HashSet;
use time::Timespec;

use conduit::{Request, Response};
use conduit_router::RequestParams;
use pg::GenericConnection;
use pg::rows::Row;

use {Model, Crate};
use db::RequestTransaction;
use util::{RequestUtils, CargoResult, ChainError};
use util::errors::NotFound;

#[derive(Clone)]
pub struct Category {
    pub id: i32,
    pub category: String,
    pub created_at: Timespec,
    pub crates_cnt: i32,
}

#[derive(RustcEncodable, RustcDecodable)]
pub struct EncodableCategory {
    pub id: String,
    pub category: String,
    pub created_at: String,
    pub crates_cnt: i32,
}

impl Category {
    pub fn find_by_category(conn: &GenericConnection, name: &str)
                            -> CargoResult<Category> {
        let stmt = try!(conn.prepare("SELECT * FROM categories \
                                      WHERE category = $1"));
        let rows = try!(stmt.query(&[&name]));
        rows.iter().next()
                   .chain_error(|| NotFound)
                   .map(|row| Model::from_row(&row))
    }

    pub fn find_all_by_category(conn: &GenericConnection, names: &[String])
                                  -> CargoResult<Vec<Category>> {
        let stmt = try!(conn.prepare("SELECT * FROM categories \
                                      WHERE category = ANY($1)"));
        let rows = try!(stmt.query(&[&names]));
        Ok(rows.iter().map(|r| Model::from_row(&r)).collect())
    }

    pub fn encodable(self) -> EncodableCategory {
        let Category { id: _, crates_cnt, category, created_at } = self;
        EncodableCategory {
            id: category.clone(),
            created_at: ::encode_time(created_at),
            crates_cnt: crates_cnt,
            category: category,
        }
    }

    pub fn update_crate(conn: &GenericConnection,
                        krate: &Crate,
                        categories: &[String]) -> CargoResult<()> {
        let old_categories = try!(krate.categories(conn));
        let old_categories_ids: HashSet<_> = old_categories.iter().map(|cat| {
            cat.id
        }).collect();
        // If a new category specified is not in the database, filter
        // it out and don't add it.
        let new_categories = try!(
            Category::find_all_by_category(conn, categories)
        );
        let new_categories_ids: HashSet<_> = new_categories.iter().map(|cat| {
            cat.id
        }).collect();

        let to_rm: Vec<_> = old_categories_ids
                                .difference(&new_categories_ids)
                                .cloned()
                                .collect();
        let to_add: Vec<_> = new_categories_ids
                                .difference(&old_categories_ids)
                                .cloned()
                                .collect();

        if !to_rm.is_empty() {
            try!(conn.execute("DELETE FROM crates_categories \
                                WHERE category_id = ANY($1) \
                                  AND crate_id = $2",
                              &[&to_rm, &krate.id]));
        }

        if !to_add.is_empty() {
            let insert: Vec<_> = to_add.into_iter().map(|id| {
                format!("({}, {})", krate.id, id)
            }).collect();
            let insert = insert.join(", ");
            try!(conn.execute(&format!("INSERT INTO crates_categories \
                                        (crate_id, category_id) VALUES {}",
                                       insert),
                              &[]));
        }

        Ok(())
    }
}

impl Model for Category {
    fn from_row(row: &Row) -> Category {
        Category {
            id: row.get("id"),
            created_at: row.get("created_at"),
            crates_cnt: row.get("crates_cnt"),
            category: row.get("category"),
        }
    }
    fn table_name(_: Option<Category>) -> &'static str { "categories" }
}

/// Handles the `GET /categories` route.
pub fn index(req: &mut Request) -> CargoResult<Response> {
    let conn = try!(req.tx());
    let (offset, limit) = try!(req.pagination(10, 100));
    let query = req.query();
    let sort = query.get("sort").map_or("alpha", String::as_str);
    let sort_sql = match sort {
        "crates" => "ORDER BY crates_cnt DESC",
        _ => "ORDER BY category ASC",
    };

    // Collect all the categories
    let stmt = try!(conn.prepare(&format!("SELECT * FROM categories {} \
                                           LIMIT $1 OFFSET $2",
                                          sort_sql)));

    let categories: Vec<_> = try!(stmt.query(&[&limit, &offset]))
        .iter()
        .map(|row| {
            let category: Category = Model::from_row(&row);
            category.encodable()
        })
        .collect();

    // Query for the total count of categories
    let total = try!(Category::count(conn));

    #[derive(RustcEncodable)]
    struct R { categories: Vec<EncodableCategory>, meta: Meta }
    #[derive(RustcEncodable)]
    struct Meta { total: i64 }

    Ok(req.json(&R {
        categories: categories,
        meta: Meta { total: total },
    }))
}

/// Handles the `GET /categories/:category_id` route.
pub fn show(req: &mut Request) -> CargoResult<Response> {
    let name = &req.params()["category_id"];
    let conn = try!(req.tx());
    let cat = try!(Category::find_by_category(&*conn, &name));

    #[derive(RustcEncodable)]
    struct R { category: EncodableCategory }
    Ok(req.json(&R { category: cat.encodable() }))
}
<|endoftext|>


<commit_before>use toml::Value;
use hook::position::HookPosition;

/// Check whether the configuration is valid for the store
///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
/// fail.
///
/// It checks whether the configuration looks like the store wants it to be:
///
/// ```toml
/// [store]
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
///
/// [[aspects.misc]]
/// parallel = true
/// [[aspects.encryption]]
/// parallel = false
/// [[aspects.version-control]]
/// parallel = false
///
/// [[hooks.gnupg]]
/// aspect = "encryption"
/// key = "0x123456789"
///
/// [[hooks.git]]
/// aspect = "version-control"
/// ```
///
/// It checks:
///  * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
///  maps)
///  * Whether each aspect configuration has a "parallel = <Boolean>" setting
///  * Whether each hook congfiguration has a "aspect = <String>" setting
///
/// It does NOT check:
///  * Whether all aspects which are used in the hook configuration are also configured
///
pub fn config_is_valid(config: &Value) -> bool {
    use std::collections::BTreeMap;

    fn has_key_with_map(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key).map(|t| match t { &Value::Table(_) => true, _ => false }).unwrap_or(false)
    }

    fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key)
            .map(|t| match t {
                &Value::Array(ref a) => a.iter().all(|elem| {
                    match elem {
                        &Value::String(_) => true,
                        _ => false,
                    }
                }),
                _ => false
            }).unwrap_or(false)
    }

    /// Check that
    /// * the top-level configuration
    /// * is a table
    /// * where all entries of a key `section` (eg. "hooks" or "aspects")
    ///     * Are maps
    ///     * where each has a key `key` (eg. "aspect" or "parallel")
    ///     * which fullfills constraint `f` (typecheck)
    fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
                                             section: &str,
                                             key: &str,
                                             f: F)
        -> bool
        where F: Fn(&Value) -> bool
    {
        store_config.get(section) // The store config has the section `section`
            .map(|section_table| {
                match section_table { // which is
                    &Value::Table(ref section_table) => // a table
                        section_table
                            .values() // which has values,
                            .all(|cfg| { // and all of these values
                                match cfg {
                                    &Value::Table(ref hook_config) => { // are tables
                                        hook_config.get(key) // with a key
                                            // fullfilling this constraint
                                            .map(|hook_aspect| f(&hook_aspect))
                                            .unwrap_or(false)
                                    },
                                    _ => false,
                                }
                            }),
                    _ => false,
                }
            })
            .unwrap_or(false)
    }

    match config {
        &Value::Table(ref t) => {
            has_key_with_string_ary(t, "pre-read-hook-aspects")        &&
            has_key_with_string_ary(t, "post-read-hook-aspects")       &&
            has_key_with_string_ary(t, "pre-create-hook-aspects")      &&
            has_key_with_string_ary(t, "post-create-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-retrieve-hook-aspects")    &&
            has_key_with_string_ary(t, "post-retrieve-hook-aspects")   &&
            has_key_with_string_ary(t, "pre-update-hook-aspects")      &&
            has_key_with_string_ary(t, "post-update-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-delete-hook-aspects")      &&
            has_key_with_string_ary(t, "post-delete-hook-aspects")     &&

            // The section "hooks" has maps which have a key "aspect" which has a value of type
            // String
            check_all_inner_maps_have_key_with(t, "hooks", "aspect", |asp| {
                match asp { &Value::String(_) => true, _ => false }
            }) &&

            // The section "aspects" has maps which have a key "parllel" which has a value of type
            // Boolean
            check_all_inner_maps_have_key_with(t, "aspects", "parallel", |asp| {
                match asp { &Value::Boolean(_) => true, _ => false, }
            })
        }
        _ => false,
    }
}

pub fn get_pre_read_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PreRead, value)
}

pub fn get_post_read_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PostRead, value)
}

pub fn get_pre_create_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PreCreate, value)
}

pub fn get_post_create_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PostCreate, value)
}

pub fn get_pre_retrieve_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PreRetrieve, value)
}

pub fn get_post_retrieve_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PostRetrieve, value)
}

pub fn get_pre_update_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PreUpdate, value)
}

pub fn get_post_update_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PostUpdate, value)
}

pub fn get_pre_delete_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PreDelete, value)
}

pub fn get_post_delete_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position(HookPosition::PostDelete, value)
}

fn get_aspect_names_for_aspect_position(position: HookPosition, value: &Value) -> Vec<String> {
    unimplemented!()
}
<commit_msg>Implement getter for aspect names<commit_after>use toml::Value;
use hook::position::HookPosition;

/// Check whether the configuration is valid for the store
///
/// The passed `Value` _must be_ the `[store]` sub-tree of the configuration. Otherwise this will
/// fail.
///
/// It checks whether the configuration looks like the store wants it to be:
///
/// ```toml
/// [store]
/// pre-create-hook-aspects = [ "misc", "encryption", "version-control"]
///
/// [[aspects.misc]]
/// parallel = true
/// [[aspects.encryption]]
/// parallel = false
/// [[aspects.version-control]]
/// parallel = false
///
/// [[hooks.gnupg]]
/// aspect = "encryption"
/// key = "0x123456789"
///
/// [[hooks.git]]
/// aspect = "version-control"
/// ```
///
/// It checks:
///  * Whether all the maps are there (whether store, store.aspects, store.aspects.example are all
///  maps)
///  * Whether each aspect configuration has a "parallel = <Boolean>" setting
///  * Whether each hook congfiguration has a "aspect = <String>" setting
///
/// It does NOT check:
///  * Whether all aspects which are used in the hook configuration are also configured
///
pub fn config_is_valid(config: &Value) -> bool {
    use std::collections::BTreeMap;

    fn has_key_with_map(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key).map(|t| match t { &Value::Table(_) => true, _ => false }).unwrap_or(false)
    }

    fn has_key_with_string_ary(v: &BTreeMap<String, Value>, key: &str) -> bool {
        v.get(key)
            .map(|t| match t {
                &Value::Array(ref a) => a.iter().all(|elem| {
                    match elem {
                        &Value::String(_) => true,
                        _ => false,
                    }
                }),
                _ => false
            }).unwrap_or(false)
    }

    /// Check that
    /// * the top-level configuration
    /// * is a table
    /// * where all entries of a key `section` (eg. "hooks" or "aspects")
    ///     * Are maps
    ///     * where each has a key `key` (eg. "aspect" or "parallel")
    ///     * which fullfills constraint `f` (typecheck)
    fn check_all_inner_maps_have_key_with<F>(store_config: &BTreeMap<String, Value>,
                                             section: &str,
                                             key: &str,
                                             f: F)
        -> bool
        where F: Fn(&Value) -> bool
    {
        store_config.get(section) // The store config has the section `section`
            .map(|section_table| {
                match section_table { // which is
                    &Value::Table(ref section_table) => // a table
                        section_table
                            .values() // which has values,
                            .all(|cfg| { // and all of these values
                                match cfg {
                                    &Value::Table(ref hook_config) => { // are tables
                                        hook_config.get(key) // with a key
                                            // fullfilling this constraint
                                            .map(|hook_aspect| f(&hook_aspect))
                                            .unwrap_or(false)
                                    },
                                    _ => false,
                                }
                            }),
                    _ => false,
                }
            })
            .unwrap_or(false)
    }

    match config {
        &Value::Table(ref t) => {
            has_key_with_string_ary(t, "pre-read-hook-aspects")        &&
            has_key_with_string_ary(t, "post-read-hook-aspects")       &&
            has_key_with_string_ary(t, "pre-create-hook-aspects")      &&
            has_key_with_string_ary(t, "post-create-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-retrieve-hook-aspects")    &&
            has_key_with_string_ary(t, "post-retrieve-hook-aspects")   &&
            has_key_with_string_ary(t, "pre-update-hook-aspects")      &&
            has_key_with_string_ary(t, "post-update-hook-aspects")     &&
            has_key_with_string_ary(t, "pre-delete-hook-aspects")      &&
            has_key_with_string_ary(t, "post-delete-hook-aspects")     &&

            // The section "hooks" has maps which have a key "aspect" which has a value of type
            // String
            check_all_inner_maps_have_key_with(t, "hooks", "aspect", |asp| {
                match asp { &Value::String(_) => true, _ => false }
            }) &&

            // The section "aspects" has maps which have a key "parllel" which has a value of type
            // Boolean
            check_all_inner_maps_have_key_with(t, "aspects", "parallel", |asp| {
                match asp { &Value::Boolean(_) => true, _ => false, }
            })
        }
        _ => false,
    }
}

pub fn get_pre_read_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-read-hook-aspects", value)
}

pub fn get_post_read_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-read-hook-aspects", value)
}

pub fn get_pre_create_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-create-hook-aspects", value)
}

pub fn get_post_create_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-create-hook-aspects", value)
}

pub fn get_pre_retrieve_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-retrieve-hook-aspects", value)
}

pub fn get_post_retrieve_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-retrieve-hook-aspects", value)
}

pub fn get_pre_update_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-update-hook-aspects", value)
}

pub fn get_post_update_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-update-hook-aspects", value)
}

pub fn get_pre_delete_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("pre-delete-hook-aspects", value)
}

pub fn get_post_delete_aspect_names(value: &Value) -> Vec<String> {
    get_aspect_names_for_aspect_position("post-delete-hook-aspects", value)
}

fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Value) -> Vec<String> {
    let mut v = vec![];

    match value {
        &Value::Table(ref t) => {
            match t.get(config_name) {
                Some(&Value::Array(ref a)) => {
                    for elem in a {
                        match elem {
                            &Value::String(ref s) => v.push(s.clone()),
                            _ => warn!("Non-String in configuration, inside '{}'", config_name),
                        }
                    }
                },
                _ => warn!("'{}' configuration key should contain Array, does not", config_name),
            };
        },
        _ => warn!("Configuration is not a table"),
    }
    v
}

<|endoftext|>


<commit_before>use rustfft::FFTnum;
use rustfft::num_traits::FloatConst;

/// Generic floating point number, implemented for f32 and f64
pub trait DCTnum: FFTnum + FloatConst {
	fn half() -> Self;
	fn two() -> Self;
}
// impl DCTnum for f32 {
// 	fn half() -> Self {
// 		0.5
// 	}
// 	fn two() -> Self {
// 		2.0
// 	}
// }
// impl DCTnum for f64 {
// 	fn half() -> Self {
// 		0.5
// 	}
// 	fn two() -> Self {
// 		2.0
// 	}
// }

impl<T: FFTnum + FloatConst> DCTnum for T {
	fn two() -> Self {
		Self::from_f64(2.0).unwrap()
	}
	fn half() -> Self {
		Self::from_f64(0.5).unwrap()
	}
}

#[inline(always)]
pub fn verify_length<T>(input: &[T], output: &[T], expected: usize) {
	assert_eq!(input.len(), expected, "Input is the wrong length. Expected {}, got {}", expected, input.len());
	assert_eq!(output.len(), expected, "Output is the wrong length. Expected {}, got {}", expected, output.len());
}

#[allow(unused)]
#[inline(always)]
pub fn verify_length_divisible<T>(input: &[T], output: &[T], expected: usize) {
	assert_eq!(input.len() % expected, 0, "Input is the wrong length. Expected multiple of {}, got {}", expected, input.len());
	assert_eq!(input.len(), output.len(), "Input and output must have the same length. Expected {}, got {}", input.len(), output.len());
}<commit_msg>Remove comment<commit_after>use rustfft::FFTnum;
use rustfft::num_traits::FloatConst;

/// Generic floating point number
pub trait DCTnum: FFTnum + FloatConst {
	fn half() -> Self;
	fn two() -> Self;
}

impl<T: FFTnum + FloatConst> DCTnum for T {
	fn half() -> Self {
		Self::from_f64(0.5).unwrap()
	}
	fn two() -> Self {
		Self::from_f64(2.0).unwrap()
	}
}

#[inline(always)]
pub fn verify_length<T>(input: &[T], output: &[T], expected: usize) {
	assert_eq!(input.len(), expected, "Input is the wrong length. Expected {}, got {}", expected, input.len());
	assert_eq!(output.len(), expected, "Output is the wrong length. Expected {}, got {}", expected, output.len());
}

#[allow(unused)]
#[inline(always)]
pub fn verify_length_divisible<T>(input: &[T], output: &[T], expected: usize) {
	assert_eq!(input.len() % expected, 0, "Input is the wrong length. Expected multiple of {}, got {}", expected, input.len());
	assert_eq!(input.len(), output.len(), "Input and output must have the same length. Expected {}, got {}", input.len(), output.len());
}
<|endoftext|>


<commit_before>use command::Command;
use vm::VM;

pub struct Program {
    instructions : Vec<Command>,
    instruction_pointer: Option<usize>,
    pub is_seeking: bool,
    current_depth: u64,
    goal_depth: Option<u64>,
}

impl Program {
    pub fn new () -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            is_seeking: false,
            current_depth: 0,
            goal_depth: None,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() { self.instruction_pointer = Some(0); }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match self.instruction_pointer {
            None => {},
            Some(mut index) => {
                if self.is_seeking {
                    let (new_index, still_seeking) = self.seek_forward(index);
                    self.is_seeking = still_seeking;
                    index = new_index;
                }
                while index < self.instructions.len() {
                    let command = self.instructions[index];
                    if command == Command::JumpForward {
                        index = self.start_jump_forward(vm, index);
                    } else if command == Command::JumpBackward {
                        self.current_depth = self.current_depth - 1
                    } else {
                        vm.apply(command);
                        index = index + 1;
                    }
                }
                self.instruction_pointer = Some(index);
            }
        }
    }

    fn jump_backward (&mut self, vm: &VM, starting_index: usize) -> usize {
        match vm.cells[vm.data_pointer] {
            0 => {
                self.current_depth = self.current_depth - 1;
                starting_index + 1
            },
            _ => {
                let goal_depth = self.current_depth;
                for index in (0..starting_index).rev() {
                    match self.instructions[index] {
                        Command::JumpBackward => {
                            self.current_depth = self.current_depth + 1;
                        },
                        Command::JumpForward => {
                            if self.current_depth == goal_depth { return index + 1 }
                            self.current_depth = self.current_depth - 1;
                        },
                        _ => {},
                    }
                }
                panic!("No starting brace found!");
            },
        }
    }

    fn seek_forward(&mut self, starting_index: usize) -> (usize, bool) {
        let goal_depth = self.goal_depth.unwrap();
        for index in starting_index..self.instructions.len() {
            match self.instructions[index] {
                Command::JumpForward => self.current_depth = self.current_depth + 1,
                Command::JumpBackward => self.current_depth = self.current_depth - 1,
                _ => {},
            }
            if self.current_depth == goal_depth { return (index + 1, false) }
        }
        (self.instructions.len(), true)
    }

    fn start_jump_forward(&mut self, vm: &VM, index: usize) -> usize {
        if vm.cells[vm.data_pointer] == 0 {
            self.goal_depth = Some(self.current_depth);
            let (index, still_seeking) = self.seek_forward(index);
            self.is_seeking = still_seeking;
            return index;
        } else {
            self.current_depth = self.current_depth + 1;
            return index + 1;
        }
    }
}
<commit_msg>Call jump backward and store index result for ']'<commit_after>use command::Command;
use vm::VM;

pub struct Program {
    instructions : Vec<Command>,
    instruction_pointer: Option<usize>,
    pub is_seeking: bool,
    current_depth: u64,
    goal_depth: Option<u64>,
}

impl Program {
    pub fn new () -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            is_seeking: false,
            current_depth: 0,
            goal_depth: None,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() { self.instruction_pointer = Some(0); }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match self.instruction_pointer {
            None => {},
            Some(mut index) => {
                if self.is_seeking {
                    let (new_index, still_seeking) = self.seek_forward(index);
                    self.is_seeking = still_seeking;
                    index = new_index;
                }
                while index < self.instructions.len() {
                    let command = self.instructions[index];
                    if command == Command::JumpForward {
                        index = self.start_jump_forward(vm, index);
                    } else if command == Command::JumpBackward {
                        index = self.jump_backward(&vm, index);
                    } else {
                        vm.apply(command);
                        index = index + 1;
                    }
                }
                self.instruction_pointer = Some(index);
            }
        }
    }

    fn jump_backward (&mut self, vm: &VM, starting_index: usize) -> usize {
        match vm.cells[vm.data_pointer] {
            0 => {
                self.current_depth = self.current_depth - 1;
                starting_index + 1
            },
            _ => {
                let goal_depth = self.current_depth;
                for index in (0..starting_index).rev() {
                    match self.instructions[index] {
                        Command::JumpBackward => {
                            self.current_depth = self.current_depth + 1;
                        },
                        Command::JumpForward => {
                            if self.current_depth == goal_depth { return index + 1 }
                            self.current_depth = self.current_depth - 1;
                        },
                        _ => {},
                    }
                }
                panic!("No starting brace found!");
            },
        }
    }

    fn seek_forward(&mut self, starting_index: usize) -> (usize, bool) {
        let goal_depth = self.goal_depth.unwrap();
        for index in starting_index..self.instructions.len() {
            match self.instructions[index] {
                Command::JumpForward => self.current_depth = self.current_depth + 1,
                Command::JumpBackward => self.current_depth = self.current_depth - 1,
                _ => {},
            }
            if self.current_depth == goal_depth { return (index + 1, false) }
        }
        (self.instructions.len(), true)
    }

    fn start_jump_forward(&mut self, vm: &VM, index: usize) -> usize {
        if vm.cells[vm.data_pointer] == 0 {
            self.goal_depth = Some(self.current_depth);
            let (index, still_seeking) = self.seek_forward(index);
            self.is_seeking = still_seeking;
            return index;
        } else {
            self.current_depth = self.current_depth + 1;
            return index + 1;
        }
    }
}
<|endoftext|>


<commit_before>use command::Command;
use vm::VM;

pub struct Program {
    instructions : Vec<Command>,
    instruction_pointer: Option<usize>,
    pub is_seeking: bool,
    current_depth: u64,
    goal_depth: Option<u64>,
}

impl Program {
    pub fn new () -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            is_seeking: false,
            current_depth: 0,
            goal_depth: None,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() { self.instruction_pointer = Some(0); }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match self.instruction_pointer {
            None => {},
            Some(mut index) => {
                if self.is_seeking {
                    let (new_index, still_seeking) = self.seek_forward(index);
                    self.is_seeking = still_seeking;
                    index = new_index;
                }
                while index < self.instructions.len() {
                    let command = self.instructions[index];
                    if command == Command::JumpForward {
                        index = self.start_jump_forward(vm, index);
                    } else if command == Command::JumpBackward {
                        self.current_depth = self.current_depth - 1
                    } else {
                        vm.apply(command);
                        index = index + 1;
                    }
                }
                self.instruction_pointer = Some(index);
            }
        }
    }

    fn seek_forward(&mut self, starting_index: usize) -> (usize, bool) {
        let goal_depth = self.goal_depth.unwrap();
        for index in starting_index..self.instructions.len() {
            match self.instructions[index] {
                Command::JumpForward => self.current_depth = self.current_depth + 1,
                Command::JumpBackward => self.current_depth = self.current_depth - 1,
                _ => {},
            }
            if self.current_depth == goal_depth { return (index + 1, false) }
        }
        (self.instructions.len(), true)
    }

    fn start_jump_forward(&mut self, vm: &VM, index: usize) -> usize {
        if vm.cells[vm.data_pointer] == 0 {
            self.goal_depth = Some(self.current_depth);
            let (index, still_seeking) = self.seek_forward(index);
            self.is_seeking = still_seeking;
            return index;
        } else {
            self.current_depth = self.current_depth + 1;
            return index + 1;
        }
    }
}
<commit_msg>Add program.jump_backward<commit_after>use command::Command;
use vm::VM;

pub struct Program {
    instructions : Vec<Command>,
    instruction_pointer: Option<usize>,
    pub is_seeking: bool,
    current_depth: u64,
    goal_depth: Option<u64>,
}

impl Program {
    pub fn new () -> Program {
        Program {
            instructions: Vec::new(),
            instruction_pointer: None,
            is_seeking: false,
            current_depth: 0,
            goal_depth: None,
        }
    }

    pub fn append(&mut self, instructions: &[Command]) {
        self.instructions.extend(instructions.iter().cloned());
        if self.instruction_pointer.is_none() { self.instruction_pointer = Some(0); }
    }

    pub fn execute(&mut self, vm: &mut VM) {
        match self.instruction_pointer {
            None => {},
            Some(mut index) => {
                if self.is_seeking {
                    let (new_index, still_seeking) = self.seek_forward(index);
                    self.is_seeking = still_seeking;
                    index = new_index;
                }
                while index < self.instructions.len() {
                    let command = self.instructions[index];
                    if command == Command::JumpForward {
                        index = self.start_jump_forward(vm, index);
                    } else if command == Command::JumpBackward {
                        self.current_depth = self.current_depth - 1
                    } else {
                        vm.apply(command);
                        index = index + 1;
                    }
                }
                self.instruction_pointer = Some(index);
            }
        }
    }

    fn jump_backward (&mut self, vm: &VM, starting_index: usize) -> usize {
        match vm.cells[vm.data_pointer] {
            0 => {
                self.current_depth = self.current_depth - 1;
                starting_index + 1
            },
            _ => {
                let goal_depth = self.current_depth;
                for index in (0..starting_index).rev() {
                    match self.instructions[index] {
                        Command::JumpBackward => {
                            self.current_depth = self.current_depth + 1;
                        },
                        Command::JumpForward => {
                            if self.current_depth == goal_depth { return index + 1 }
                            self.current_depth = self.current_depth - 1;
                        },
                        _ => {},
                    }
                }
                panic!("No starting brace found!");
            },
        }
    }

    fn seek_forward(&mut self, starting_index: usize) -> (usize, bool) {
        let goal_depth = self.goal_depth.unwrap();
        for index in starting_index..self.instructions.len() {
            match self.instructions[index] {
                Command::JumpForward => self.current_depth = self.current_depth + 1,
                Command::JumpBackward => self.current_depth = self.current_depth - 1,
                _ => {},
            }
            if self.current_depth == goal_depth { return (index + 1, false) }
        }
        (self.instructions.len(), true)
    }

    fn start_jump_forward(&mut self, vm: &VM, index: usize) -> usize {
        if vm.cells[vm.data_pointer] == 0 {
            self.goal_depth = Some(self.current_depth);
            let (index, still_seeking) = self.seek_forward(index);
            self.is_seeking = still_seeking;
            return index;
        } else {
            self.current_depth = self.current_depth + 1;
            return index + 1;
        }
    }
}
<|endoftext|>


<commit_before>//! Common configuration utilities

use directories::ProjectDirs;
#[cfg(unix)]
use std::path::Path;
use std::path::PathBuf;

/// Default configuration file path
pub fn get_default_config_path() -> Option<PathBuf> {
    // System standard directories
    if let Some(project_dirs) = ProjectDirs::from("org", "shadowsocks", "shadowsocks-rust") {
        // Linux: $XDG_CONFIG_HOME/shadowsocks-rust/config.json
        //        $HOME/.config/shadowsocks-rust/config.json
        // macOS: $HOME/Library/Application Support/org.shadowsocks.shadowsocks-rust/config.json
        // Windows: {FOLDERID_RoamingAppData}/shadowsocks/shadowsocks-rust/config/config.json

        let mut config_path = project_dirs.config_dir().to_path_buf();
        config_path.push("config.json");

        if config_path.exists() {
            return Some(config_path);
        }
    }

    // UNIX systems, XDG Base Directory
    // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    #[cfg(unix)]
    if let Ok(base_directories) = xdg::BaseDirectories::with_prefix("shadowsocks-rust") {
        // $XDG_CONFIG_HOME/shadowsocks-rust/config.json
        // for dir in $XDG_CONFIG_DIRS; $dir/shadowsocks-rust/config.json
        if let Some(config_path) = base_directories.find_config_file("config.json") {
            return Some(config_path);
        }
    }

    // UNIX global configuration file
    #[cfg(unix)]
    {
        let global_config_path = Path::new("/etc/shadowsocks-rust/config.json");
        if global_config_path.exists() {
            return Some(global_config_path.to_path_buf());
        }
    }

    None
}
<commit_msg>default configuration path includes $PWD/config.json<commit_after>//! Common configuration utilities

use directories::ProjectDirs;
#[cfg(unix)]
use std::path::Path;
use std::{env, path::PathBuf};

/// Default configuration file path
pub fn get_default_config_path() -> Option<PathBuf> {
    // config.json in the current working directory ($PWD)
    if let Ok(mut path) = env::current_dir() {
        path.push("config.json");

        if path.exists() {
            return Some(path);
        }
    }

    // config.json in the current working directory (relative path)
    let relative_path = PathBuf::from("config.json");
    if relative_path.exists() {
        return Some(relative_path);
    }

    // System standard directories
    if let Some(project_dirs) = ProjectDirs::from("org", "shadowsocks", "shadowsocks-rust") {
        // Linux: $XDG_CONFIG_HOME/shadowsocks-rust/config.json
        //        $HOME/.config/shadowsocks-rust/config.json
        // macOS: $HOME/Library/Application Support/org.shadowsocks.shadowsocks-rust/config.json
        // Windows: {FOLDERID_RoamingAppData}/shadowsocks/shadowsocks-rust/config/config.json

        let mut config_path = project_dirs.config_dir().to_path_buf();
        config_path.push("config.json");

        if config_path.exists() {
            return Some(config_path);
        }
    }

    // UNIX systems, XDG Base Directory
    // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    #[cfg(unix)]
    if let Ok(base_directories) = xdg::BaseDirectories::with_prefix("shadowsocks-rust") {
        // $XDG_CONFIG_HOME/shadowsocks-rust/config.json
        // for dir in $XDG_CONFIG_DIRS; $dir/shadowsocks-rust/config.json
        if let Some(config_path) = base_directories.find_config_file("config.json") {
            return Some(config_path);
        }
    }

    // UNIX global configuration file
    #[cfg(unix)]
    {
        let global_config_path = Path::new("/etc/shadowsocks-rust/config.json");
        if global_config_path.exists() {
            return Some(global_config_path.to_path_buf());
        }
    }

    None
}
<|endoftext|>


<commit_before>use std::fmt;
use std::sync::{Arc, RwLock};
use std::net::AddrParseError;
use std::error::Error;
use toml;

use std::net::SocketAddr;

pub type ConfigResult<T> = Result<T, ConfigError>;

lazy_static! {
    static ref DEFAULT_DNS_SERVERS: Vec<SocketAddr> = vec![
        "8.8.8.8:53".parse().unwrap(),
        "8.8.4.4:53".parse().unwrap(),
    ];

    static ref DEFAULT_TIMEOUT_RETRIES: u32 = 10;
    static ref DEFAULT_QPS: u32 = 500;

    pub static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::new()));
}

#[derive(Debug)]
pub struct Config {
    dns_list: Vec<SocketAddr>,
    qps: u32,
    timeout_retries: u32,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            dns_list: DEFAULT_DNS_SERVERS.clone(),
            qps: *DEFAULT_QPS,
            timeout_retries: *DEFAULT_TIMEOUT_RETRIES,
        }
    }
}

impl Config {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn timeout_retries(&self) -> u32 {
        self.timeout_retries
    }

    pub fn qps(&self) -> u32 {
        self.qps
    }

    pub fn dns_list(&self) -> &[SocketAddr] {
        &self.dns_list
    }

    pub fn parse(&mut self, string: &str) -> ConfigResult<()> {
        #[derive(Serialize, Deserialize, Debug)]
        struct Config {
            dns: Option<Vec<String>>,
            retry: Option<u32>,
            queries_per_second: Option<u32>,
        }

        let mut cfg_fmt: Config = toml::from_str(string)?;

        if let Some(mut dns_fmt_vec) = cfg_fmt.dns.take() {
            let mut dns_servers = Vec::new();

            for dns in &mut dns_fmt_vec {
                if !dns.contains(':') {
                    dns.push_str(":53")
                }
                dns_servers.push(dns.parse()?);
            }

            debug!("{:?}", dns_servers);

            self.dns_list = dns_servers;
        }

        if let Some(retry) = cfg_fmt.retry {
            self.timeout_retries = retry;
        }

        if let Some(qps) = cfg_fmt.queries_per_second {
            self.qps = qps;
        }

        Ok(())
    }
}

#[derive(Debug)]
pub enum ConfigError {
    AddrParseError(AddrParseError),
    TomlParseError(toml::de::Error),
}

impl Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::AddrParseError(ref err) => err.description(),
            ConfigError::TomlParseError(ref err) => err.description(),
        }
    }
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.description())
    }
}

impl From<AddrParseError> for ConfigError {
    fn from(err: AddrParseError) -> Self {
        ConfigError::AddrParseError(err)
    }
}

impl From<toml::de::Error> for ConfigError {
    fn from(err: toml::de::Error) -> Self {
        ConfigError::TomlParseError(err)
    }
}
<commit_msg>Removed redundant derive<commit_after>use std::fmt;
use std::sync::{Arc, RwLock};
use std::net::AddrParseError;
use std::error::Error;
use toml;

use std::net::SocketAddr;

pub type ConfigResult<T> = Result<T, ConfigError>;

lazy_static! {
    static ref DEFAULT_DNS_SERVERS: Vec<SocketAddr> = vec![
        "8.8.8.8:53".parse().unwrap(),
        "8.8.4.4:53".parse().unwrap(),
    ];

    static ref DEFAULT_TIMEOUT_RETRIES: u32 = 10;
    static ref DEFAULT_QPS: u32 = 500;

    pub static ref CONFIG: Arc<RwLock<Config>> = Arc::new(RwLock::new(Config::new()));
}

#[derive(Debug)]
pub struct Config {
    dns_list: Vec<SocketAddr>,
    qps: u32,
    timeout_retries: u32,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            dns_list: DEFAULT_DNS_SERVERS.clone(),
            qps: *DEFAULT_QPS,
            timeout_retries: *DEFAULT_TIMEOUT_RETRIES,
        }
    }
}

impl Config {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn timeout_retries(&self) -> u32 {
        self.timeout_retries
    }

    pub fn qps(&self) -> u32 {
        self.qps
    }

    pub fn dns_list(&self) -> &[SocketAddr] {
        &self.dns_list
    }

    pub fn parse(&mut self, string: &str) -> ConfigResult<()> {
        #[derive(Deserialize, Debug)]
        struct Config {
            dns: Option<Vec<String>>,
            retry: Option<u32>,
            queries_per_second: Option<u32>,
        }

        let mut cfg_fmt: Config = toml::from_str(string)?;

        if let Some(mut dns_fmt_vec) = cfg_fmt.dns.take() {
            let mut dns_servers = Vec::new();

            for dns in &mut dns_fmt_vec {
                if !dns.contains(':') {
                    dns.push_str(":53")
                }
                dns_servers.push(dns.parse()?);
            }

            debug!("{:?}", dns_servers);

            self.dns_list = dns_servers;
        }

        if let Some(retry) = cfg_fmt.retry {
            self.timeout_retries = retry;
        }

        if let Some(qps) = cfg_fmt.queries_per_second {
            self.qps = qps;
        }

        Ok(())
    }
}

#[derive(Debug)]
pub enum ConfigError {
    AddrParseError(AddrParseError),
    TomlParseError(toml::de::Error),
}

impl Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::AddrParseError(ref err) => err.description(),
            ConfigError::TomlParseError(ref err) => err.description(),
        }
    }
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.description())
    }
}

impl From<AddrParseError> for ConfigError {
    fn from(err: AddrParseError) -> Self {
        ConfigError::AddrParseError(err)
    }
}

impl From<toml::de::Error> for ConfigError {
    fn from(err: toml::de::Error) -> Self {
        ConfigError::TomlParseError(err)
    }
}
<|endoftext|>


<commit_before>//! # Config module.
//!
//! The Config module provides the beamium configuration.
//! It set defaults and then load config from '/etc', local dir and provided path.
use std::fs::File;
use std::io::Read;
use std::io;
use std::fmt;
use std::string::String;
use std::path::Path;
use std::error;
use std::error::Error;
use yaml_rust::{YamlLoader, ScanError};
use cast;
use std::collections::HashMap;
use regex;
use slog;

#[derive(Debug)]
#[derive(Clone)]
/// Config root.
pub struct Config {
    pub sources: Vec<Source>,
    pub sinks: Vec<Sink>,
    pub labels: HashMap<String, String>,
    pub parameters: Parameters,
}

#[derive(Debug)]
#[derive(Clone)]
/// Source config.
pub struct Source {
    pub name: String,
    pub url: String,
    pub period: u64,
    pub format: SourceFormat,
    pub metrics: Option<regex::RegexSet>,
}

#[derive(Debug)]
#[derive(Clone)]
/// Source format.
pub enum SourceFormat {
    Prometheus,
    Sensision,
}

#[derive(Debug)]
#[derive(Clone)]
/// Sink config.
pub struct Sink {
    pub name: String,
    pub url: String,
    pub token: String,
    pub token_header: String,
    pub selector: Option<regex::Regex>,
    pub ttl: u64,
    pub size: u64,
}

#[derive(Debug)]
#[derive(Clone)]
/// Parameters config.
pub struct Parameters {
    pub scan_period: u64,
    pub sink_dir: String,
    pub source_dir: String,
    pub batch_size: u64,
    pub batch_count: u64,
    pub log_file: String,
    pub log_level: slog::Level,
    pub timeout: u64,
}

#[derive(Debug)]
/// Config Error.
pub enum ConfigError {
    Io(io::Error),
    Yaml(ScanError),
    Regex(regex::Error),
    Format(Box<Error>),
}

impl From<io::Error> for ConfigError {
    fn from(err: io::Error) -> ConfigError {
        ConfigError::Io(err)
    }
}
impl From<ScanError> for ConfigError {
    fn from(err: ScanError) -> ConfigError {
        ConfigError::Yaml(err)
    }
}
impl From<regex::Error> for ConfigError {
    fn from(err: regex::Error) -> ConfigError {
        ConfigError::Regex(err)
    }
}
impl From<Box<Error>> for ConfigError {
    fn from(err: Box<Error>) -> ConfigError {
        ConfigError::Format(err)
    }
}
impl<'a> From<&'a str> for ConfigError {
    fn from(err: &str) -> ConfigError {
        ConfigError::Format(From::from(err))
    }
}
impl From<String> for ConfigError {
    fn from(err: String) -> ConfigError {
        ConfigError::Format(From::from(err))
    }
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ConfigError::Io(ref err) => err.fmt(f),
            ConfigError::Yaml(ref err) => err.fmt(f),
            ConfigError::Regex(ref err) => err.fmt(f),
            ConfigError::Format(ref err) => err.fmt(f),
        }
    }
}

impl error::Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::Io(ref err) => err.description(),
            ConfigError::Yaml(ref err) => err.description(),
            ConfigError::Regex(ref err) => err.description(),
            ConfigError::Format(ref err) => err.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            ConfigError::Io(ref err) => Some(err),
            ConfigError::Yaml(ref err) => Some(err),
            ConfigError::Regex(ref err) => Some(err),
            ConfigError::Format(ref err) => Some(err.as_ref()),
        }
    }
}

/// Load config.
///
/// Setup a defaults config and then load config from '/etc', local dir and provided path.
/// Return Err if provided path is not found or if config is unprocessable.
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
    // Defaults
    let mut config = Config {
        sources: Vec::new(),
        labels: HashMap::new(),
        sinks: Vec::new(),
        parameters: Parameters {
            scan_period: 1000,
            sink_dir: String::from("sinks"),
            source_dir: String::from("sources"),
            batch_size: 200000,
            batch_count: 250,
            log_file: String::from(env!("CARGO_PKG_NAME")) + ".log",
            log_level: slog::Level::Info,
            timeout: 300,
        },
    };

    // Load from etc
    if Path::new("/etc/beamium/config.yaml").exists() {
        try!(load_path("/etc/beamium/config.yaml", &mut config));
    }

    // Load local
    if Path::new("config.yaml").exists() {
        try!(load_path("config.yaml", &mut config));
    }

    // Load from provided path
    if !config_path.is_empty() {
        try!(load_path(config_path, &mut config));
    }

    Ok(config)
}

/// Extend confif from file.
fn load_path<P: AsRef<Path>>(file_path: P, config: &mut Config) -> Result<(), ConfigError> {
    let mut file = try!(File::open(file_path));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let docs = try!(YamlLoader::load_from_str(&contents));

    for doc in &docs {
        if !doc["sources"].is_badvalue() {
            let sources = try!(doc["sources"]
                .as_hash()
                .ok_or("sources should be a map"));

            for (k, v) in sources {
                let name = try!(k.as_str()
                    .ok_or("sources keys should be a string"));
                let url = try!(v["url"]
                    .as_str()
                    .ok_or(format!("sources.{}.url is required and should be a string", name)));
                let period = try!(v["period"]
                    .as_i64()
                    .ok_or(format!("sources.{}.period is required and should be a number", name)));
                let period = try!(cast::u64(period)
                    .map_err(|_| format!("sources.{}.period is invalid", name)));
                let format = if v["format"].is_badvalue() {
                        SourceFormat::Prometheus
                    } else {
                        let f = try!(v["format"]
                            .as_str()
                            .ok_or(format!("sinks.{}.format should be a string", name)));

                        if f == "prometheus" {
                            SourceFormat::Prometheus
                        } else if f == "sensision" {
                            SourceFormat::Sensision
                        } else {
                            return Err(format!("sinks.{}.format should be 'Prometheus' or 'sensision'", name).into())
                        }
                    };
                let metrics = if v["metrics"].is_badvalue() {
                    None
                } else {
                    let mut metrics = Vec::new();
                    let values = try!(v["metrics"].as_vec().ok_or("metrics should be an array"));
                    for v in values {
                        let value = try!(regex::Regex::new(try!(v.as_str()
                                .ok_or(format!("metrics.{} is invalid", name)))));
                        metrics.push(String::from(r"^(\S*)\s") + value.as_str());
                    }

                    Some(try!(regex::RegexSet::new(&metrics)))
                };

                config.sources.push(Source {
                    name: String::from(name),
                    url: String::from(url),
                    period: period,
                    format: format,
                    metrics: metrics,
                })
            }
        }

        if !doc["sinks"].is_badvalue() {
            let sinks = try!(doc["sinks"].as_hash().ok_or("sinks should be a map"));
            for (k, v) in sinks {
                let name = try!(k.as_str().ok_or("sinks keys should be a string"));
                let url = try!(v["url"]
                    .as_str()
                    .ok_or(format!("sinks.{}.url is required and should be a string", name)));
                let token = try!(v["token"]
                    .as_str()
                    .ok_or(format!("sinks.{}.token is required and should be a string", name)));
                let token_header = if v["token-header"].is_badvalue() {
                    "X-Warp10-Token"
                } else {
                    try!(v["token-header"]
                        .as_str()
                        .ok_or(format!("sinks.{}.token-header should be a string", name)))
                };

                let selector = if v["selector"].is_badvalue() {
                    None
                } else {
                    Some(try!(regex::Regex::new(try!(v["selector"]
                        .as_str()
                        .ok_or(format!("sinks.{}.selector is invalid", name))))))
                };

                let ttl = if v["ttl"].is_badvalue() {
                    3600
                } else {
                    let ttl = try!(v["ttl"]
                        .as_i64()
                        .ok_or(format!("sinks.{}.ttl should be a number", name)));
                    try!(cast::u64(ttl)
                        .map_err(|_| format!("sinks.{}.ttl should be a positive number", name)))
                };

                let size = if v["size"].is_badvalue() {
                    1073741824
                } else {
                    let size = try!(v["size"]
                        .as_i64()
                        .ok_or(format!("sinks.{}.size should be a number", name)));
                    try!(cast::u64(size)
                        .map_err(|_| format!("sinks.{}.size should be a positive number", name)))
                };

                config.sinks.push(Sink {
                    name: String::from(name),
                    url: String::from(url),
                    token: String::from(token),
                    token_header: String::from(token_header),
                    selector: selector,
                    ttl: ttl,
                    size: size,
                })
            }
        }

        if !doc["labels"].is_badvalue() {
            let labels = try!(doc["labels"].as_hash().ok_or("labels should be a map"));
            for (k, v) in labels {
                let name = try!(k.as_str().ok_or("labels keys should be a string"));
                let value = try!(v.as_str()
                    .ok_or(format!("labels.{} value should be a string", name)));
                config.labels.insert(String::from(name), String::from(value));
            }
        }

        if !doc["parameters"].is_badvalue() {
            if !doc["parameters"]["source-dir"].is_badvalue() {
                let source_dir = try!(doc["parameters"]["source-dir"]
                    .as_str()
                    .ok_or(format!("parameters.source-dir should be a string")));
                config.parameters.source_dir = String::from(source_dir);
            }

            if !doc["parameters"]["sink-dir"].is_badvalue() {
                let sink_dir = try!(doc["parameters"]["sink-dir"]
                    .as_str()
                    .ok_or(format!("parameters.sink-dir should be a string")));
                config.parameters.sink_dir = String::from(sink_dir);
            }

            if !doc["parameters"]["scan-period"].is_badvalue() {
                let scan_period = try!(doc["parameters"]["scan-period"]
                    .as_i64()
                    .ok_or(format!("parameters.scan-period should be a number")));
                let scan_period = try!(cast::u64(scan_period)
                    .map_err(|_| format!("parameters.scan-period is invalid")));
                config.parameters.scan_period = scan_period;
            }

            if !doc["parameters"]["batch-size"].is_badvalue() {
                let batch_size = try!(doc["parameters"]["batch-size"]
                    .as_i64()
                    .ok_or(format!("parameters.batch-size should be a number")));
                let batch_size = try!(cast::u64(batch_size)
                    .map_err(|_| format!("parameters.batch-size is invalid")));
                config.parameters.batch_size = batch_size;
            }

            if !doc["parameters"]["batch-count"].is_badvalue() {
                let batch_count = try!(doc["parameters"]["batch-count"]
                    .as_i64()
                    .ok_or(format!("parameters.batch-count should be a number")));
                let batch_count = try!(cast::u64(batch_count)
                    .map_err(|_| format!("parameters.batch-count is invalid")));
                config.parameters.batch_count = batch_count;
            }

            if !doc["parameters"]["log-file"].is_badvalue() {
                let log_file = try!(doc["parameters"]["log-file"]
                    .as_str()
                    .ok_or(format!("parameters.log-file should be a string")));
                config.parameters.log_file = String::from(log_file);
            }

            if !doc["parameters"]["log-level"].is_badvalue() {
                let log_level = try!(doc["parameters"]["log-level"]
                    .as_i64()
                    .ok_or(format!("parameters.log-level should be a number")));
                let log_level = try!(cast::u64(log_level)
                    .map_err(|_| format!("parameters.log-level is invalid")));
                let log_level = try!(slog::Level::from_usize(log_level as usize)
                    .ok_or(format!("parameters.log-level is invalid")));
                config.parameters.log_level = log_level;
            }

            if !doc["parameters"]["timeout"].is_badvalue() {
                let timeout = try!(doc["parameters"]["timeout"]
                    .as_i64()
                    .ok_or(format!("parameters.timeout should be a number")));
                let timeout = try!(cast::u64(timeout)
                    .map_err(|_| format!("parameters.timeout is invalid")));
                config.parameters.timeout = timeout;
            }
        }
    }

    Ok(())
}
<commit_msg>feat: read only specified config<commit_after>//! # Config module.
//!
//! The Config module provides the beamium configuration.
//! It set defaults and then load config from '/etc', local dir and provided path.
use std::fs::File;
use std::io::Read;
use std::io;
use std::fmt;
use std::string::String;
use std::path::Path;
use std::error;
use std::error::Error;
use yaml_rust::{YamlLoader, ScanError};
use cast;
use std::collections::HashMap;
use regex;
use slog;

#[derive(Debug)]
#[derive(Clone)]
/// Config root.
pub struct Config {
    pub sources: Vec<Source>,
    pub sinks: Vec<Sink>,
    pub labels: HashMap<String, String>,
    pub parameters: Parameters,
}

#[derive(Debug)]
#[derive(Clone)]
/// Source config.
pub struct Source {
    pub name: String,
    pub url: String,
    pub period: u64,
    pub format: SourceFormat,
    pub metrics: Option<regex::RegexSet>,
}

#[derive(Debug)]
#[derive(Clone)]
/// Source format.
pub enum SourceFormat {
    Prometheus,
    Sensision,
}

#[derive(Debug)]
#[derive(Clone)]
/// Sink config.
pub struct Sink {
    pub name: String,
    pub url: String,
    pub token: String,
    pub token_header: String,
    pub selector: Option<regex::Regex>,
    pub ttl: u64,
    pub size: u64,
}

#[derive(Debug)]
#[derive(Clone)]
/// Parameters config.
pub struct Parameters {
    pub scan_period: u64,
    pub sink_dir: String,
    pub source_dir: String,
    pub batch_size: u64,
    pub batch_count: u64,
    pub log_file: String,
    pub log_level: slog::Level,
    pub timeout: u64,
}

#[derive(Debug)]
/// Config Error.
pub enum ConfigError {
    Io(io::Error),
    Yaml(ScanError),
    Regex(regex::Error),
    Format(Box<Error>),
}

impl From<io::Error> for ConfigError {
    fn from(err: io::Error) -> ConfigError {
        ConfigError::Io(err)
    }
}
impl From<ScanError> for ConfigError {
    fn from(err: ScanError) -> ConfigError {
        ConfigError::Yaml(err)
    }
}
impl From<regex::Error> for ConfigError {
    fn from(err: regex::Error) -> ConfigError {
        ConfigError::Regex(err)
    }
}
impl From<Box<Error>> for ConfigError {
    fn from(err: Box<Error>) -> ConfigError {
        ConfigError::Format(err)
    }
}
impl<'a> From<&'a str> for ConfigError {
    fn from(err: &str) -> ConfigError {
        ConfigError::Format(From::from(err))
    }
}
impl From<String> for ConfigError {
    fn from(err: String) -> ConfigError {
        ConfigError::Format(From::from(err))
    }
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ConfigError::Io(ref err) => err.fmt(f),
            ConfigError::Yaml(ref err) => err.fmt(f),
            ConfigError::Regex(ref err) => err.fmt(f),
            ConfigError::Format(ref err) => err.fmt(f),
        }
    }
}

impl error::Error for ConfigError {
    fn description(&self) -> &str {
        match *self {
            ConfigError::Io(ref err) => err.description(),
            ConfigError::Yaml(ref err) => err.description(),
            ConfigError::Regex(ref err) => err.description(),
            ConfigError::Format(ref err) => err.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            ConfigError::Io(ref err) => Some(err),
            ConfigError::Yaml(ref err) => Some(err),
            ConfigError::Regex(ref err) => Some(err),
            ConfigError::Format(ref err) => Some(err.as_ref()),
        }
    }
}

/// Load config.
///
/// Setup a defaults config and then load config from '/etc', local dir and provided path.
/// Return Err if provided path is not found or if config is unprocessable.
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
    // Defaults
    let mut config = Config {
        sources: Vec::new(),
        labels: HashMap::new(),
        sinks: Vec::new(),
        parameters: Parameters {
            scan_period: 1000,
            sink_dir: String::from("sinks"),
            source_dir: String::from("sources"),
            batch_size: 200000,
            batch_count: 250,
            log_file: String::from(env!("CARGO_PKG_NAME")) + ".log",
            log_level: slog::Level::Info,
            timeout: 300,
        },
    };

    if config_path.is_empty() {
        // Load from etc
        if Path::new("/etc/beamium/config.yaml").exists() {
            try!(load_path("/etc/beamium/config.yaml", &mut config));
        }

        // Load local
        if Path::new("config.yaml").exists() {
            try!(load_path("config.yaml", &mut config));
        }
    } else {
        // Load from provided path
        try!(load_path(config_path, &mut config));
    }

    Ok(config)
}

/// Extend confif from file.
fn load_path<P: AsRef<Path>>(file_path: P, config: &mut Config) -> Result<(), ConfigError> {
    let mut file = try!(File::open(file_path));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    let docs = try!(YamlLoader::load_from_str(&contents));

    for doc in &docs {
        if !doc["sources"].is_badvalue() {
            let sources = try!(doc["sources"]
                .as_hash()
                .ok_or("sources should be a map"));

            for (k, v) in sources {
                let name = try!(k.as_str()
                    .ok_or("sources keys should be a string"));
                let url = try!(v["url"]
                    .as_str()
                    .ok_or(format!("sources.{}.url is required and should be a string", name)));
                let period = try!(v["period"]
                    .as_i64()
                    .ok_or(format!("sources.{}.period is required and should be a number", name)));
                let period = try!(cast::u64(period)
                    .map_err(|_| format!("sources.{}.period is invalid", name)));
                let format = if v["format"].is_badvalue() {
                        SourceFormat::Prometheus
                    } else {
                        let f = try!(v["format"]
                            .as_str()
                            .ok_or(format!("sinks.{}.format should be a string", name)));

                        if f == "prometheus" {
                            SourceFormat::Prometheus
                        } else if f == "sensision" {
                            SourceFormat::Sensision
                        } else {
                            return Err(format!("sinks.{}.format should be 'Prometheus' or 'sensision'", name).into())
                        }
                    };
                let metrics = if v["metrics"].is_badvalue() {
                    None
                } else {
                    let mut metrics = Vec::new();
                    let values = try!(v["metrics"].as_vec().ok_or("metrics should be an array"));
                    for v in values {
                        let value = try!(regex::Regex::new(try!(v.as_str()
                                .ok_or(format!("metrics.{} is invalid", name)))));
                        metrics.push(String::from(r"^(\S*)\s") + value.as_str());
                    }

                    Some(try!(regex::RegexSet::new(&metrics)))
                };

                config.sources.push(Source {
                    name: String::from(name),
                    url: String::from(url),
                    period: period,
                    format: format,
                    metrics: metrics,
                })
            }
        }

        if !doc["sinks"].is_badvalue() {
            let sinks = try!(doc["sinks"].as_hash().ok_or("sinks should be a map"));
            for (k, v) in sinks {
                let name = try!(k.as_str().ok_or("sinks keys should be a string"));
                let url = try!(v["url"]
                    .as_str()
                    .ok_or(format!("sinks.{}.url is required and should be a string", name)));
                let token = try!(v["token"]
                    .as_str()
                    .ok_or(format!("sinks.{}.token is required and should be a string", name)));
                let token_header = if v["token-header"].is_badvalue() {
                    "X-Warp10-Token"
                } else {
                    try!(v["token-header"]
                        .as_str()
                        .ok_or(format!("sinks.{}.token-header should be a string", name)))
                };

                let selector = if v["selector"].is_badvalue() {
                    None
                } else {
                    Some(try!(regex::Regex::new(try!(v["selector"]
                        .as_str()
                        .ok_or(format!("sinks.{}.selector is invalid", name))))))
                };

                let ttl = if v["ttl"].is_badvalue() {
                    3600
                } else {
                    let ttl = try!(v["ttl"]
                        .as_i64()
                        .ok_or(format!("sinks.{}.ttl should be a number", name)));
                    try!(cast::u64(ttl)
                        .map_err(|_| format!("sinks.{}.ttl should be a positive number", name)))
                };

                let size = if v["size"].is_badvalue() {
                    1073741824
                } else {
                    let size = try!(v["size"]
                        .as_i64()
                        .ok_or(format!("sinks.{}.size should be a number", name)));
                    try!(cast::u64(size)
                        .map_err(|_| format!("sinks.{}.size should be a positive number", name)))
                };

                config.sinks.push(Sink {
                    name: String::from(name),
                    url: String::from(url),
                    token: String::from(token),
                    token_header: String::from(token_header),
                    selector: selector,
                    ttl: ttl,
                    size: size,
                })
            }
        }

        if !doc["labels"].is_badvalue() {
            let labels = try!(doc["labels"].as_hash().ok_or("labels should be a map"));
            for (k, v) in labels {
                let name = try!(k.as_str().ok_or("labels keys should be a string"));
                let value = try!(v.as_str()
                    .ok_or(format!("labels.{} value should be a string", name)));
                config.labels.insert(String::from(name), String::from(value));
            }
        }

        if !doc["parameters"].is_badvalue() {
            if !doc["parameters"]["source-dir"].is_badvalue() {
                let source_dir = try!(doc["parameters"]["source-dir"]
                    .as_str()
                    .ok_or(format!("parameters.source-dir should be a string")));
                config.parameters.source_dir = String::from(source_dir);
            }

            if !doc["parameters"]["sink-dir"].is_badvalue() {
                let sink_dir = try!(doc["parameters"]["sink-dir"]
                    .as_str()
                    .ok_or(format!("parameters.sink-dir should be a string")));
                config.parameters.sink_dir = String::from(sink_dir);
            }

            if !doc["parameters"]["scan-period"].is_badvalue() {
                let scan_period = try!(doc["parameters"]["scan-period"]
                    .as_i64()
                    .ok_or(format!("parameters.scan-period should be a number")));
                let scan_period = try!(cast::u64(scan_period)
                    .map_err(|_| format!("parameters.scan-period is invalid")));
                config.parameters.scan_period = scan_period;
            }

            if !doc["parameters"]["batch-size"].is_badvalue() {
                let batch_size = try!(doc["parameters"]["batch-size"]
                    .as_i64()
                    .ok_or(format!("parameters.batch-size should be a number")));
                let batch_size = try!(cast::u64(batch_size)
                    .map_err(|_| format!("parameters.batch-size is invalid")));
                config.parameters.batch_size = batch_size;
            }

            if !doc["parameters"]["batch-count"].is_badvalue() {
                let batch_count = try!(doc["parameters"]["batch-count"]
                    .as_i64()
                    .ok_or(format!("parameters.batch-count should be a number")));
                let batch_count = try!(cast::u64(batch_count)
                    .map_err(|_| format!("parameters.batch-count is invalid")));
                config.parameters.batch_count = batch_count;
            }

            if !doc["parameters"]["log-file"].is_badvalue() {
                let log_file = try!(doc["parameters"]["log-file"]
                    .as_str()
                    .ok_or(format!("parameters.log-file should be a string")));
                config.parameters.log_file = String::from(log_file);
            }

            if !doc["parameters"]["log-level"].is_badvalue() {
                let log_level = try!(doc["parameters"]["log-level"]
                    .as_i64()
                    .ok_or(format!("parameters.log-level should be a number")));
                let log_level = try!(cast::u64(log_level)
                    .map_err(|_| format!("parameters.log-level is invalid")));
                let log_level = try!(slog::Level::from_usize(log_level as usize)
                    .ok_or(format!("parameters.log-level is invalid")));
                config.parameters.log_level = log_level;
            }

            if !doc["parameters"]["timeout"].is_badvalue() {
                let timeout = try!(doc["parameters"]["timeout"]
                    .as_i64()
                    .ok_or(format!("parameters.timeout should be a number")));
                let timeout = try!(cast::u64(timeout)
                    .map_err(|_| format!("parameters.timeout is invalid")));
                config.parameters.timeout = timeout;
            }
        }
    }

    Ok(())
}
<|endoftext|>


<commit_before>use std::io::prelude::*;
use std::fs::File;
use std::str::FromStr;
use docopt::Docopt;
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())
        }
    }
}

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: String,
    pub library_name: String,
    pub library_version: String,
    pub target_path: String,
    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, String> {
        let args = Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse())
            .unwrap_or_else(|e| e.exit());

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

        //TODO: add check file existence when stable std::fs::PathExt
        let toml = match read_toml(config_file) {
            Ok(t) => t,
            Err(e) => {
                return Err(e);
            }
        };

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

        let girs_dir = match args.get_str("-d") {
            "" => toml.lookup("options.girs_dir")
                      .expect("No options.girs_dir in config")
                      .as_str().unwrap(),
            a => a
        };

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                toml.lookup("options.library")
                    .expect("No options.library in config")
                    .as_str().unwrap(),
                toml.lookup("options.version")
                    .expect("No options.version in config")
                    .as_str().unwrap()
            ),
            ("", _) | (_, "") => panic!("Library and version can not be specified separately"),
            (a, b) => (a, b)
        };

        let target_path = match args.get_str("-o") {
            "" => toml.lookup("options.target_path")
                    .expect("No target path specified")
                    .as_str().unwrap(),
            a => a
        };

        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 = toml.lookup("options.external_libraries")
            .map(|a| a.as_slice().unwrap().iter()
                .filter_map(|v|
                    if let &toml::Value::String(ref s) = v { Some(s.clone()) } else { None } )
                .collect())
            .unwrap_or_else(|| Vec::new());

        let min_cfg_version = toml.lookup("options.min_cfg_version")
            .map_or_else(|| Ok(Default::default()), |t| t.as_str().unwrap().parse())
            .unwrap_or_else(|e| panic!(e));

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

        Ok(Config {
            work_mode: work_mode,
            girs_dir: girs_dir.into(),
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path.into(),
            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(filename: &str) -> Result<toml::Value, String> {
    let mut input = String::new();
    match File::open(filename).and_then(|mut f| {
        f.read_to_string(&mut input)
    }) {
        Ok(_) => {}
        Err(e) => {
            return Err(format!("Error on \"{}\": {}", filename, e))
        }
    }
    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => Ok(toml::Value::Table(toml)),
        None => {
            for err in &parser.errors {
                let (loline, locol) = parser.to_linecol(err.lo);
                let (hiline, hicol) = parser.to_linecol(err.hi);
                println!("{}:{}:{}-{}:{} error: {}",
                         filename, loline, locol, hiline, hicol, err.desc);
            }
            Err("Errors in config".to_owned())
        }
    }
}
<commit_msg>New enum to have a better error handling<commit_after>use std::io::prelude::*;
use std::fs::File;
use std::str::FromStr;
use std::error::Error;
use std::io::Error as IoError;
use std::fmt::{self, Display, Formatter};
use std::convert::From;
use docopt::Docopt;
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 ConfigError {
    Io(IoError),
    TomlError(String),
}

impl Error for ConfigError {
    fn description(&self) -> &str {
        match self {
            &ConfigError::Io(ref e) => e.description(),
            &ConfigError::TomlError(ref s) => {
                s
            }
        }
    }
}

impl Display for ConfigError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            &ConfigError::Io(ref e) => e.fmt(f),
            &ConfigError::TomlError(ref s) => {
                write!(f, "Toml parsing error on '{}'", s)
            }
        }
    }
}

impl From<IoError> for ConfigError {
    fn from(e: IoError) -> ConfigError {
        ConfigError::Io(e)
    }
}

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: String,
    pub library_name: String,
    pub library_version: String,
    pub target_path: String,
    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, ConfigError> {
        let args = Docopt::new(USAGE)
            .and_then(|dopt| dopt.parse())
            .unwrap_or_else(|e| e.exit());

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

        //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") {
            "" => toml.lookup("options.work_mode")
                    .expect("No options.work_mode in config")
                    .as_str().unwrap(),
            a => a,
        };
        let work_mode = WorkMode::from_str(work_mode_str)
            .unwrap_or_else(|e| panic!(e));

        let girs_dir = match args.get_str("-d") {
            "" => toml.lookup("options.girs_dir")
                      .expect("No options.girs_dir in config")
                      .as_str().unwrap(),
            a => a
        };

        let (library_name, library_version) =
            match (args.get_str("<library>"), args.get_str("<version>")) {
            ("", "") => (
                toml.lookup("options.library")
                    .expect("No options.library in config")
                    .as_str().unwrap(),
                toml.lookup("options.version")
                    .expect("No options.version in config")
                    .as_str().unwrap()
            ),
            ("", _) | (_, "") => panic!("Library and version can not be specified separately"),
            (a, b) => (a, b)
        };

        let target_path = match args.get_str("-o") {
            "" => toml.lookup("options.target_path")
                    .expect("No target path specified")
                    .as_str().unwrap(),
            a => a
        };

        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 = toml.lookup("options.external_libraries")
            .map(|a| a.as_slice().unwrap().iter()
                .filter_map(|v|
                    if let &toml::Value::String(ref s) = v { Some(s.clone()) } else { None } )
                .collect())
            .unwrap_or_else(|| Vec::new());

        let min_cfg_version = toml.lookup("options.min_cfg_version")
            .map_or_else(|| Ok(Default::default()), |t| t.as_str().unwrap().parse())
            .unwrap_or_else(|e| panic!(e));

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

        Ok(Config {
            work_mode: work_mode,
            girs_dir: girs_dir.into(),
            library_name: library_name.into(),
            library_version: library_version.into(),
            target_path: target_path.into(),
            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(filename: &str) -> Result<toml::Value, ConfigError> {
    let mut input = String::new();
    try!(File::open(filename).and_then(|mut f| {
        f.read_to_string(&mut input)
    }));

    let mut parser = toml::Parser::new(&input);
    match parser.parse() {
        Some(toml) => Ok(toml::Value::Table(toml)),
        None => {
            for err in &parser.errors {
                let (loline, locol) = parser.to_linecol(err.lo);
                let (hiline, hicol) = parser.to_linecol(err.hi);
                println!("{}:{}:{}-{}:{} error: {}",
                         filename, loline, locol, hiline, hicol, err.desc);
            }
            Err(ConfigError::TomlError(filename.to_owned()))
        }
    }
}
<|endoftext|>