<commit_before>//! Process configurations for Lightning sites.

// First-party
use std::collections::{BTreeMap, HashMap};
use std::convert::From;
use std::error::Error;
use std::io::Read;
use std::fs::File;
use std::path::PathBuf;
use std::u8;

// Third-party
use yaml_rust::{yaml, Yaml, YamlLoader};

// First-party
use yaml_util::*;

const CONFIG_FILE_NAME: &'static str = "lightning.yaml";

pub struct Config {
    pub site: Site,
    pub directories: Directories,
    pub taxonomies: Vec<Taxonomy>,
}

pub struct Directories {
    pub content: PathBuf,
    pub output: PathBuf,
    pub template: PathBuf,
}

impl Directories {
    fn from_yaml(config_map: &BTreeMap<Yaml, Yaml>,
                 config_path: &PathBuf)
                 -> Result<Directories, String> {
        const CONTENT_DIRECTORY: &'static str = "content_directory";
        const OUTPUT_DIRECTORY: &'static str = "output_directory";
        const TEMPLATE_DIRECTORY: &'static str = "directory";

        let content_directory_yaml = config_map.get(&Yaml::from_str(CONTENT_DIRECTORY))
            .ok_or(format!("No `{:}` key in {:?}", CONTENT_DIRECTORY, config_path))?;

        let content_directory =
            path_buf_from_yaml(&content_directory_yaml, CONTENT_DIRECTORY, &config_path)?;

        let output_directory_yaml = config_map.get(&Yaml::from_str(OUTPUT_DIRECTORY))
            .ok_or(format!("No `{:} key in `{:?}", OUTPUT_DIRECTORY, config_path))?;

        let output_directory =
            path_buf_from_yaml(output_directory_yaml, OUTPUT_DIRECTORY, &config_path)?;

        let structure = get_structure(&config_map, &config_path)?;

        let template_directory_yaml = structure.get(&Yaml::from_str(TEMPLATE_DIRECTORY))
            .ok_or(format!("No `directory` key in `structure` in {:?}", config_path))?;

        let template_directory =
            path_buf_from_yaml(&template_directory_yaml, TEMPLATE_DIRECTORY, &config_path)?;

        Ok(Directories {
            content: content_directory,
            output: output_directory,
            template: template_directory,
        })
    }
}

pub enum Taxonomy {
    Binary { name: String, templates: Templates },
    Multiple {
        name: String,
        limit: Option<u8>,
        required: bool,
        hierarchical: bool,
        templates: Templates,
    },
    Temporal {
        name: String,
        required: bool,
        templates: Templates,
    },
}

impl Taxonomy {
    // TODO: make Result<Taxonomy, Vec<String>> instead? Collect errors to
    //       supply *all* validation errors to the user? And generalize that to
    //       all types?
    fn from_yaml_hash(hash: &yaml::Hash, name: &str) -> Result<Taxonomy, String> {
        const TYPE: &'static str = "type";
        const BINARY: &'static str = "binary";
        const MULTIPLE: &'static str = "multiple";
        const TEMPORAL: &'static str = "temporal";

        let name = String::from(name);

        // Name can't collide with keyword `type`.
        let type_ = hash.get(&Yaml::from_str(TYPE))
            .ok_or(required_key(TYPE, hash))?
            .as_str()
            .ok_or(key_of_type(TYPE, Required::Yes, hash, "string"))?;

        let templates = Templates::from_yaml(hash)?;

        match type_ {
            BINARY => {
                Ok(Taxonomy::Binary {
                    name: name,
                    templates: templates,
                })
            }
            MULTIPLE => {
                Ok(Taxonomy::Multiple {
                    name: name,
                    templates: templates,
                    hierarchical: Self::is_hierarchical(hash)?,
                    required: Self::is_required(hash)?,
                    limit: Self::limit(hash)?,
                })
            }
            TEMPORAL => {
                panic!("Taxonomy {} is of type {}, which is not yet implemented.",
                       name,
                       type_);
                // Ok(Taxonomy::Temporal {}),
            }
            _ => Err(format!("Invalid type `{:?}` in {:?}", type_, hash)),
        }
    }

    fn is_hierarchical(hash: &yaml::Hash) -> Result<bool, String> {
        const HIERARCHICAL: &'static str = "hierarchical";

        match hash.get(&Yaml::from_str(HIERARCHICAL)) {
            None |
            Some(&Yaml::Boolean(false)) => Ok(false),
            Some(&Yaml::Boolean(true)) => Ok(true),
            _ => Err(key_of_type(HIERARCHICAL, Required::Yes, hash, "bool")),
        }
    }

    fn is_required(hash: &yaml::Hash) -> Result<bool, String> {
        const REQUIRED: &'static str = "required";

        match hash.get(&Yaml::from_str(REQUIRED)) {
            None |
            Some(&Yaml::Boolean(false)) => Ok(false),
            Some(&Yaml::Boolean(true)) => Ok(true),
            _ => Err(key_of_type(REQUIRED, Required::No, hash, "bool")),
        }
    }

    fn limit(hash: &yaml::Hash) -> Result<Option<u8>, String> {
        const LIMIT: &'static str = "limit";
        let max = u8::MAX as i64;

        match hash.get(&Yaml::from_str(LIMIT)) {
            None  => Ok(None),
            Some(&Yaml::Integer(i)) if i < 0 => Err(bad_value(i, LIMIT, hash)),
            Some(&Yaml::Integer(i)) if i == 0 => Ok(None),
            Some(&Yaml::Integer(i)) if i > 0 && i < max => Ok(Some(i as u8)),
            Some(&Yaml::Integer(i)) if i > max as i64 => Err(ridiculous_number(i, LIMIT, hash)),
            _ => Err(key_of_type(LIMIT, Required::No, hash, "integer")),
        }
    }
}

pub struct Site {
    pub name: String,
    pub description: String,
    pub metadata: HashMap<Yaml, Yaml>,
    pub url: ValidatedUrl,
}

pub struct Templates {
    pub item: PathBuf,
    pub list: Option<PathBuf>,
}

impl Templates {
    fn from_yaml(yaml: &yaml::Hash) -> Result<Templates, String> {
        const TEMPLATES: &'static str = "templates";
        let template_yaml = yaml.get(&Yaml::from_str(TEMPLATES))
            .ok_or(required_key(TEMPLATES, yaml))?
            .as_hash()
            .ok_or(key_of_type(TEMPLATES, Required::Yes, yaml, "hash"))?;

        let item = Self::item_from_yaml(template_yaml)?;
        let list = Self::list_from_yaml(template_yaml)?;

        Ok(Templates {
            item: item,
            list: list,
        })
    }

    /// Get the `item` value for a taxonomy's templates.
    fn item_from_yaml(yaml: &yaml::Hash) -> Result<PathBuf, String> {
        const ITEM: &'static str = "item";

        let item_str = yaml.get(&Yaml::from_str(ITEM))
            .ok_or(required_key(ITEM, yaml))?
            .as_str()
            .ok_or(key_of_type(ITEM, Required::Yes, yaml, "string"))?;

        Ok(item_str.into())
    }

    /// Get the `list` value for a taxonomy's templates.
    ///
    /// This return type isn't as crazy as it looks. A `list` entry is allowed
    /// to be explicitly `null`/`~` or simply unset, but if the key is
    /// included, it is not allowed to be anything other than a `string` or
    /// explicitly set to null.
    fn list_from_yaml(yaml: &yaml::Hash) -> Result<Option<PathBuf>, String> {
        const LIST: &'static str = "list";

        match yaml.get(&Yaml::from_str(LIST)) {
            None |
            Some(&Yaml::Null) => Ok(None),
            Some(&Yaml::String(ref string)) => Ok(Some(string.into())),
            _ => Err(key_of_type(LIST, Required::No, yaml, "string")),
        }
    }
}

mod validated {
    pub struct Url(String);

    impl Url {
        /// Get a URL. `Err` if the item passed in is not a spec-conformant URL.
        pub fn new(unvalidated_url: String) -> Result<Url, String> {
            // TODO: validate the URLs!
            Ok(Url(unvalidated_url))
        }

        pub fn value(&self) -> String {
            self.0.clone()
        }
    }
}

pub use self::validated::Url as ValidatedUrl;

fn path_buf_from_yaml(yaml: &Yaml, key: &str, config_path: &PathBuf) -> Result<PathBuf, String> {
    match yaml {
        &Yaml::String(ref path_str) => Ok(PathBuf::from(path_str)),
        value => Err(format!("invalid `{:}` value {:?} in {:?}", key, value, config_path)),
    }
}

fn get_structure<'map>(config_map: &'map BTreeMap<Yaml, Yaml>,
                       config_path: &PathBuf)
                       -> Result<&'map BTreeMap<Yaml, Yaml>, String> {
    config_map.get(&Yaml::from_str("structure"))
        .ok_or(format!("No `structure` key in {:?}", config_path))?
        .as_hash()
        .ok_or(format!("`structure` is not a map in {:?}", config_path))
}

/// Load the site data from the configuration file.
fn site(config_map: &BTreeMap<Yaml, Yaml>) -> Result<Site, String> {
    // TODO: build these:
    let name = String::new();
    let description = String::new();
    let metadata = HashMap::new();
    let url = ValidatedUrl::new(String::new())?;

    Ok(Site {
        name: name,
        description: description,
        metadata: metadata,
        url: url,
    })
}

fn taxonomies(config_map: &BTreeMap<Yaml, Yaml>,
              config_path: &PathBuf)
              -> Result<Vec<Taxonomy>, String> {
    const KEY: &'static str = "taxonomies";

    let structure = get_structure(config_map, config_path)?;
    let taxonomies_yaml = structure.get(&Yaml::from_str(KEY))
        .ok_or(format!("No `{}` key in {:?}", KEY, config_path))?
        .as_vec()
        .ok_or(format!("`{}` is not an array in {:?}", KEY, config_path))?;

    if taxonomies_yaml.len() == 0 {
        return Ok(Vec::new());
    }

    // TODO: actually get all the keys instead of just doing it with the first one.
    // This is safe because we have at least one; also it's temporary. We'll
    // extract all of this to be a function which can be used as an argument to
    // part of a map: Vec<Yaml> -> Vec<Taxonomy>
    let first = taxonomies_yaml.first()
        .unwrap()
        .as_hash()
        .ok_or(format!("Cannot expand `{}` item as a hash", KEY))?;

    let first_key =
        first.keys().next().ok_or(key_of_type("first key", Required::Yes, first, "hash"))?;
    let first_key_string = first_key.as_str().expect(":wat:");  // FIXME: this is dumb.
    let first_yaml_hash = first.get(first_key)
        .ok_or(required_key(first_key_string, first))?
        .as_hash()
        .ok_or(key_of_type(first_key_string, Required::Yes, first, "hash"))?;
    let first_taxonomy = Taxonomy::from_yaml_hash(first_yaml_hash, first_key_string)?;
    let taxonomy_data: Vec<Taxonomy> = vec![first_taxonomy];

    Ok(taxonomy_data)
}

pub fn load(directory: &PathBuf) -> Result<Config, String> {
    let config_path = directory.join(CONFIG_FILE_NAME);
    if !config_path.exists() {
        return Err(format!("The specified configuration path {:?} does not exist.",
                           config_path.to_string_lossy()));
    }

    let mut file = File::open(&config_path)
        .map_err(|reason| format!("Error reading {:?}: {:?}", config_path, reason))?;

    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => (),
        Err(err) => return Err(String::from(err.description())),
    };

    // We need all these intermediate bindings because the temporaries created
    // along the way don't live long enough otherwise.
    let load_result = YamlLoader::load_from_str(&contents)
        .map_err(|err| format!("{} ({:?})", err, &config_path))?;
    let yaml_config = load_result.into_iter().next().ok_or("Empty configuration file")?;
    let config_map = yaml_config.as_hash().ok_or("Configuration is not a map")?;

    Ok(Config {
        site: site(config_map)?,
        directories: Directories::from_yaml(config_map, &config_path)?,
        taxonomies: taxonomies(config_map, &config_path)?,
    })
}
<commit_msg>Parse config for `Taxonomy::Temporal`; improve names.<commit_after>//! Process configurations for Lightning sites.

// First-party
use std::collections::{BTreeMap, HashMap};
use std::convert::From;
use std::error::Error;
use std::io::Read;
use std::fs::File;
use std::path::PathBuf;
use std::u8;

// Third-party
use yaml_rust::{yaml, Yaml, YamlLoader};

// First-party
use yaml_util::*;

const CONFIG_FILE_NAME: &'static str = "lightning.yaml";

pub struct Config {
    pub site: Site,
    pub directories: Directories,
    pub taxonomies: Vec<Taxonomy>,
}

pub struct Directories {
    pub content: PathBuf,
    pub output: PathBuf,
    pub template: PathBuf,
}

impl Directories {
    fn from_yaml(config_map: &BTreeMap<Yaml, Yaml>,
                 config_path: &PathBuf)
                 -> Result<Directories, String> {
        const CONTENT_DIRECTORY: &'static str = "content_directory";
        const OUTPUT_DIRECTORY: &'static str = "output_directory";
        const TEMPLATE_DIRECTORY: &'static str = "directory";

        let content_directory_yaml = config_map.get(&Yaml::from_str(CONTENT_DIRECTORY))
            .ok_or(format!("No `{:}` key in {:?}", CONTENT_DIRECTORY, config_path))?;

        let content_directory =
            path_buf_from_yaml(&content_directory_yaml, CONTENT_DIRECTORY, &config_path)?;

        let output_directory_yaml = config_map.get(&Yaml::from_str(OUTPUT_DIRECTORY))
            .ok_or(format!("No `{:} key in `{:?}", OUTPUT_DIRECTORY, config_path))?;

        let output_directory =
            path_buf_from_yaml(output_directory_yaml, OUTPUT_DIRECTORY, &config_path)?;

        let structure = get_structure(&config_map, &config_path)?;

        let template_directory_yaml = structure.get(&Yaml::from_str(TEMPLATE_DIRECTORY))
            .ok_or(format!("No `directory` key in `structure` in {:?}", config_path))?;

        let template_directory =
            path_buf_from_yaml(&template_directory_yaml, TEMPLATE_DIRECTORY, &config_path)?;

        Ok(Directories {
            content: content_directory,
            output: output_directory,
            template: template_directory,
        })
    }
}

pub enum Taxonomy {
    Binary { name: String, templates: Templates },
    Multiple {
        name: String,
        templates: Templates,
        limit: Option<u8>,
        required: bool,
        hierarchical: bool,
    },
    Temporal {
        name: String,
        templates: Templates,
        required: bool,
    },
}

impl Taxonomy {
    // TODO: make Result<Taxonomy, Vec<String>> instead? Collect errors to
    //       supply *all* validation errors to the user? And generalize that to
    //       all types?
    fn from_yaml_hash(hash: &yaml::Hash, name: &str) -> Result<Taxonomy, String> {
        const TYPE: &'static str = "type";
        const BINARY: &'static str = "binary";
        const MULTIPLE: &'static str = "multiple";
        const TEMPORAL: &'static str = "temporal";

        let name = String::from(name);

        // Name can't collide with keyword `type`.
        let taxonomy_type = hash.get(&Yaml::from_str(TYPE))
            .ok_or(required_key(TYPE, hash))?
            .as_str()
            .ok_or(key_of_type(TYPE, Required::Yes, hash, "string"))?;

        let templates = Templates::from_yaml(hash)?;

        match taxonomy_type {
            BINARY => {
                Ok(Taxonomy::Binary {
                    name: name,
                    templates: templates,
                })
            }
            MULTIPLE => {
                Ok(Taxonomy::Multiple {
                    name: name,
                    templates: templates,
                    hierarchical: Self::is_hierarchical(hash)?,
                    required: Self::is_required(hash)?,
                    limit: Self::limit(hash)?,
                })
            }
            TEMPORAL => {
                Ok(Taxonomy::Temporal {
                    name: name,
                    templates: templates,
                    required: Self::is_required(hash)?
                })
            }
            _ => Err(format!("Invalid taxonomy type `{:?}` in {:?}", taxonomy_type, hash)),
        }
    }

    fn is_hierarchical(hash: &yaml::Hash) -> Result<bool, String> {
        const HIERARCHICAL: &'static str = "hierarchical";

        match hash.get(&Yaml::from_str(HIERARCHICAL)) {
            None |
            Some(&Yaml::Boolean(false)) => Ok(false),
            Some(&Yaml::Boolean(true)) => Ok(true),
            _ => Err(key_of_type(HIERARCHICAL, Required::Yes, hash, "bool")),
        }
    }

    fn is_required(hash: &yaml::Hash) -> Result<bool, String> {
        const REQUIRED: &'static str = "required";

        match hash.get(&Yaml::from_str(REQUIRED)) {
            None |
            Some(&Yaml::Boolean(false)) => Ok(false),
            Some(&Yaml::Boolean(true)) => Ok(true),
            _ => Err(key_of_type(REQUIRED, Required::No, hash, "bool")),
        }
    }

    fn limit(hash: &yaml::Hash) -> Result<Option<u8>, String> {
        const LIMIT: &'static str = "limit";
        let max = u8::MAX as i64;

        match hash.get(&Yaml::from_str(LIMIT)) {
            None  => Ok(None),
            Some(&Yaml::Integer(i)) if i < 0 => Err(bad_value(i, LIMIT, hash)),
            Some(&Yaml::Integer(i)) if i == 0 => Ok(None),
            Some(&Yaml::Integer(i)) if i > 0 && i < max => Ok(Some(i as u8)),
            Some(&Yaml::Integer(i)) if i > max as i64 => Err(ridiculous_number(i, LIMIT, hash)),
            _ => Err(key_of_type(LIMIT, Required::No, hash, "integer")),
        }
    }
}

pub struct Site {
    pub name: String,
    pub description: String,
    pub metadata: HashMap<Yaml, Yaml>,
    pub url: ValidatedUrl,
}

pub struct Templates {
    pub item: PathBuf,
    pub list: Option<PathBuf>,
}

impl Templates {
    fn from_yaml(yaml: &yaml::Hash) -> Result<Templates, String> {
        const TEMPLATES: &'static str = "templates";
        let template_yaml = yaml.get(&Yaml::from_str(TEMPLATES))
            .ok_or(required_key(TEMPLATES, yaml))?
            .as_hash()
            .ok_or(key_of_type(TEMPLATES, Required::Yes, yaml, "hash"))?;

        let item = Self::item_from_yaml(template_yaml)?;
        let list = Self::list_from_yaml(template_yaml)?;

        Ok(Templates {
            item: item,
            list: list,
        })
    }

    /// Get the `item` value for a taxonomy's templates.
    fn item_from_yaml(yaml: &yaml::Hash) -> Result<PathBuf, String> {
        const ITEM: &'static str = "item";

        let item_str = yaml.get(&Yaml::from_str(ITEM))
            .ok_or(required_key(ITEM, yaml))?
            .as_str()
            .ok_or(key_of_type(ITEM, Required::Yes, yaml, "string"))?;

        Ok(item_str.into())
    }

    /// Get the `list` value for a taxonomy's templates.
    ///
    /// This return type isn't as crazy as it looks. A `list` entry is allowed
    /// to be explicitly `null`/`~` or simply unset, but if the key is
    /// included, it is not allowed to be anything other than a `string` or
    /// explicitly set to null.
    fn list_from_yaml(yaml: &yaml::Hash) -> Result<Option<PathBuf>, String> {
        const LIST: &'static str = "list";

        match yaml.get(&Yaml::from_str(LIST)) {
            None |
            Some(&Yaml::Null) => Ok(None),
            Some(&Yaml::String(ref string)) => Ok(Some(string.into())),
            _ => Err(key_of_type(LIST, Required::No, yaml, "string")),
        }
    }
}

mod validated {
    pub struct Url(String);

    impl Url {
        /// Get a URL. `Err` if the item passed in is not a spec-conformant URL.
        pub fn new(unvalidated_url: String) -> Result<Url, String> {
            // TODO: validate the URLs!
            Ok(Url(unvalidated_url))
        }

        pub fn value(&self) -> String {
            self.0.clone()
        }
    }
}

pub use self::validated::Url as ValidatedUrl;

fn path_buf_from_yaml(yaml: &Yaml, key: &str, config_path: &PathBuf) -> Result<PathBuf, String> {
    match yaml {
        &Yaml::String(ref path_str) => Ok(PathBuf::from(path_str)),
        value => Err(format!("invalid `{:}` value {:?} in {:?}", key, value, config_path)),
    }
}

fn get_structure<'map>(config_map: &'map BTreeMap<Yaml, Yaml>,
                       config_path: &PathBuf)
                       -> Result<&'map BTreeMap<Yaml, Yaml>, String> {
    config_map.get(&Yaml::from_str("structure"))
        .ok_or(format!("No `structure` key in {:?}", config_path))?
        .as_hash()
        .ok_or(format!("`structure` is not a map in {:?}", config_path))
}

/// Load the site data from the configuration file.
fn site(config_map: &BTreeMap<Yaml, Yaml>) -> Result<Site, String> {
    // TODO: build these:
    let name = String::new();
    let description = String::new();
    let metadata = HashMap::new();
    let url = ValidatedUrl::new(String::new())?;

    Ok(Site {
        name: name,
        description: description,
        metadata: metadata,
        url: url,
    })
}

fn taxonomies(config_map: &BTreeMap<Yaml, Yaml>,
              config_path: &PathBuf)
              -> Result<Vec<Taxonomy>, String> {
    const KEY: &'static str = "taxonomies";

    let structure = get_structure(config_map, config_path)?;
    let taxonomies_yaml = structure.get(&Yaml::from_str(KEY))
        .ok_or(format!("No `{}` key in {:?}", KEY, config_path))?
        .as_vec()
        .ok_or(format!("`{}` is not an array in {:?}", KEY, config_path))?;

    if taxonomies_yaml.len() == 0 {
        return Ok(Vec::new());
    }

    // TODO: actually get all the keys instead of just doing it with the first one.
    // This is safe because we have at least one; also it's temporary. We'll
    // extract all of this to be a function which can be used as an argument to
    // part of a map: Vec<Yaml> -> Vec<Taxonomy>
    let first = taxonomies_yaml.first()
        .unwrap()
        .as_hash()
        .ok_or(format!("Cannot expand `{}` item as a hash", KEY))?;

    let first_key =
        first.keys().next().ok_or(key_of_type("first key", Required::Yes, first, "hash"))?;
    let first_key_string = first_key.as_str().expect(":wat:");  // FIXME: this is dumb.
    let first_yaml_hash = first.get(first_key)
        .ok_or(required_key(first_key_string, first))?
        .as_hash()
        .ok_or(key_of_type(first_key_string, Required::Yes, first, "hash"))?;
    let first_taxonomy = Taxonomy::from_yaml_hash(first_yaml_hash, first_key_string)?;
    let taxonomy_data: Vec<Taxonomy> = vec![first_taxonomy];

    Ok(taxonomy_data)
}

pub fn load(directory: &PathBuf) -> Result<Config, String> {
    let config_path = directory.join(CONFIG_FILE_NAME);
    if !config_path.exists() {
        return Err(format!("The specified configuration path {:?} does not exist.",
                           config_path.to_string_lossy()));
    }

    let mut file = File::open(&config_path)
        .map_err(|reason| format!("Error reading {:?}: {:?}", config_path, reason))?;

    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => (),
        Err(err) => return Err(String::from(err.description())),
    };

    // We need all these intermediate bindings because the temporaries created
    // along the way don't live long enough otherwise.
    let load_result = YamlLoader::load_from_str(&contents)
        .map_err(|err| format!("{} ({:?})", err, &config_path))?;
    let yaml_config = load_result.into_iter().next().ok_or("Empty configuration file")?;
    let config_map = yaml_config.as_hash().ok_or("Configuration is not a map")?;

    Ok(Config {
        site: site(config_map)?,
        directories: Directories::from_yaml(config_map, &config_path)?,
        taxonomies: taxonomies(config_map, &config_path)?,
    })
}
<|endoftext|>


<commit_before>use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::ops::Deref;
use std::path::PathBuf;

use csv;
use index::Indexed;
use rustc_serialize::{Decodable, Decoder};

use CliResult;
use select::{SelectColumns, Selection};
use util;

#[derive(Clone, Copy, Debug)]
pub struct Delimiter(pub u8);

/// Delimiter represents values that can be passed from the command line that
/// can be used as a field delimiter in CSV data.
///
/// Its purpose is to ensure that the Unicode character given decodes to a
/// valid ASCII character as required by the CSV parser.
impl Delimiter {
    pub fn as_byte(self) -> u8 {
        self.0
    }
}

impl Decodable for Delimiter {
    fn decode<D: Decoder>(d: &mut D) -> Result<Delimiter, D::Error> {
        let c = d.read_str()?;
        match &*c {
            r"\t" => Ok(Delimiter(b'\t')),
            s => {
                if s.len() != 1 {
                    let msg = format!("Could not convert '{}' to a single \
                                       ASCII character.", s);
                    return Err(d.error(&*msg));
                }
                let c = s.chars().next().unwrap();
                if c.is_ascii() {
                    Ok(Delimiter(c as u8))
                } else {
                    let msg = format!("Could not convert '{}' \
                                       to ASCII delimiter.", c);
                    Err(d.error(&*msg))
                }
            }
        }
    }
}

pub struct Config {
    path: Option<PathBuf>, // None implies <stdin>
    idx_path: Option<PathBuf>,
    select_columns: Option<SelectColumns>,
    delimiter: u8,
    pub no_headers: bool,
    flexible: bool,
    terminator: csv::Terminator,
    quote: u8,
    quote_style: csv::QuoteStyle,
    double_quote: bool,
    escape: u8,
}

impl Config {
    pub fn new(path: &Option<String>) -> Config {
        let (path, delim) = match *path {
            None => (None, b','),
            Some(ref s) if s.deref() == "-" => (None, b','),
            Some(ref s) => {
                let path = PathBuf::from(s);
                let delim =
                    if path.extension().map_or(false, |v| v == "tsv") {
                        b'\t'
                    } else {
                        b','
                    };
                (Some(path), delim)
            }
        };
        Config {
            path: path,
            idx_path: None,
            select_columns: None,
            delimiter: delim,
            no_headers: false,
            flexible: false,
            terminator: csv::Terminator::Any(b'\n'),
            quote: b'"',
            quote_style: csv::QuoteStyle::Necessary,
            double_quote: true,
            escape: b'\\',
        }
    }

    pub fn delimiter(mut self, d: Option<Delimiter>) -> Config {
        if let Some(d) = d {
            self.delimiter = d.as_byte();
        }
        self
    }

    pub fn no_headers(mut self, mut yes: bool) -> Config {
        if env::var("XSV_TOGGLE_HEADERS").unwrap_or("0".to_owned()) == "1" {
            yes = !yes;
        }
        self.no_headers = yes;
        self
    }

    pub fn flexible(mut self, yes: bool) -> Config {
        self.flexible = yes;
        self
    }

    pub fn crlf(mut self, yes: bool) -> Config {
        if yes {
            self.terminator = csv::Terminator::CRLF;
        } else {
            self.terminator = csv::Terminator::Any(b'\n');
        }
        self
    }

    pub fn terminator(mut self, term: csv::Terminator) -> Config {
        self.terminator = term;
        self
    }

    pub fn quote(mut self, quote: u8) -> Config {
        self.quote = quote;
        self
    }

    pub fn quote_style(mut self, style: csv::QuoteStyle) -> Config {
        self.quote_style = style;
        self
    }

    pub fn double_quote(mut self, yes: bool) -> Config {
        self.double_quote = yes;
        self
    }

    pub fn escape(mut self, escape: u8) -> Config {
        self.escape = escape;
        self
    }

    pub fn select(mut self, sel_cols: SelectColumns) -> Config {
        self.select_columns = Some(sel_cols);
        self
    }

    pub fn is_std(&self) -> bool {
        self.path.is_none()
    }

    pub fn selection(
        &self,
        first_record: &csv::ByteRecord,
    ) -> Result<Selection, String> {
        match self.select_columns {
            None => Err("Config has no 'SelectColums'. Did you call \
                         Config::select?".to_owned()),
            Some(ref sel) => sel.selection(first_record, !self.no_headers),
        }
    }

    pub fn write_headers<R: io::Read, W: io::Write>
                        (&self, r: &mut csv::Reader<R>, w: &mut csv::Writer<W>)
                        -> csv::Result<()> {
        if !self.no_headers {
            let r = r.byte_headers()?;
            if !r.is_empty() {
                w.write_record(r)?;
            }
        }
        Ok(())
    }

    pub fn writer(&self)
                 -> io::Result<csv::Writer<Box<io::Write+'static>>> {
        Ok(self.from_writer(self.io_writer()?))
    }

    pub fn reader(&self)
                 -> io::Result<csv::Reader<Box<io::Read+'static>>> {
        Ok(self.from_reader(self.io_reader()?))
    }

    pub fn reader_file(&self) -> io::Result<csv::Reader<fs::File>> {
        match self.path {
            None => Err(io::Error::new(
                io::ErrorKind::Other, "Cannot use <stdin> here",
            )),
            Some(ref p) => fs::File::open(p).map(|f| self.from_reader(f)),
        }
    }

    pub fn index_files(&self)
           -> io::Result<Option<(csv::Reader<fs::File>, fs::File)>> {
        let (csv_file, idx_file) = match (&self.path, &self.idx_path) {
            (&None, &None) => return Ok(None),
            (&None, &Some(_)) => return Err(io::Error::new(
                io::ErrorKind::Other,
                "Cannot use <stdin> with indexes",
                // Some(format!("index file: {}", p.display()))
            )),
            (&Some(ref p), &None) => {
                // We generally don't want to report an error here, since we're
                // passively trying to find an index.
                let idx_file = match fs::File::open(&util::idx_path(p)) {
                    // TODO: Maybe we should report an error if the file exists
                    // but is not readable.
                    Err(_) => return Ok(None),
                    Ok(f) => f,
                };
                (fs::File::open(p)?, idx_file)
            }
            (&Some(ref p), &Some(ref ip)) => {
                (fs::File::open(p)?, fs::File::open(ip)?)
            }
        };
        // If the CSV data was last modified after the index file was last
        // modified, then return an error and demand the user regenerate the
        // index.
        let data_modified = util::last_modified(&csv_file.metadata()?);
        let idx_modified = util::last_modified(&idx_file.metadata()?);
        if data_modified > idx_modified {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "The CSV file was modified after the index file. \
                 Please re-create the index.",
            ));
        }
        let csv_rdr = self.from_reader(csv_file);
        Ok(Some((csv_rdr, idx_file)))
    }

    pub fn indexed(&self)
                  -> CliResult<Option<Indexed<fs::File, fs::File>>> {
        match self.index_files()? {
            None => Ok(None),
            Some((r, i)) => Ok(Some(Indexed::open(r, i)?)),
        }
    }

    pub fn io_reader(&self) -> io::Result<Box<io::Read+'static>> {
        Ok(match self.path {
                None => Box::new(io::stdin()),
                Some(ref p) => {
                    match fs::File::open(p){
                        Ok(x) => Box::new(x),
                        Err(err) => {
                            let msg = format!(
                                "failed to open {}: {}", p.display(), err);
                            return Err(io::Error::new(
                                io::ErrorKind::NotFound,
                                msg,
                            ));
                        }
                    }
                },
            })
    }

    pub fn from_reader<R: Read>(&self, rdr: R) -> csv::Reader<R> {
        csv::ReaderBuilder::new()
            .flexible(self.flexible)
            .delimiter(self.delimiter)
            .has_headers(!self.no_headers)
            .from_reader(rdr)
    }

    pub fn io_writer(&self) -> io::Result<Box<io::Write+'static>> {
        Ok(match self.path {
            None => Box::new(io::stdout()),
            Some(ref p) => Box::new(fs::File::create(p)?),
        })
    }

    pub fn from_writer<W: io::Write>(&self, wtr: W) -> csv::Writer<W> {
        csv::WriterBuilder::new()
            .flexible(self.flexible)
            .delimiter(self.delimiter)
            .terminator(self.terminator)
            .quote(self.quote)
            .quote_style(self.quote_style)
            .double_quote(self.double_quote)
            .escape(self.escape)
            .buffer_capacity(32 * (1<<10))
            .from_writer(wtr)
    }
}
<commit_msg>fix config bug<commit_after>use std::ascii::AsciiExt;
use std::borrow::ToOwned;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::ops::Deref;
use std::path::PathBuf;

use csv;
use index::Indexed;
use rustc_serialize::{Decodable, Decoder};

use CliResult;
use select::{SelectColumns, Selection};
use util;

#[derive(Clone, Copy, Debug)]
pub struct Delimiter(pub u8);

/// Delimiter represents values that can be passed from the command line that
/// can be used as a field delimiter in CSV data.
///
/// Its purpose is to ensure that the Unicode character given decodes to a
/// valid ASCII character as required by the CSV parser.
impl Delimiter {
    pub fn as_byte(self) -> u8 {
        self.0
    }
}

impl Decodable for Delimiter {
    fn decode<D: Decoder>(d: &mut D) -> Result<Delimiter, D::Error> {
        let c = d.read_str()?;
        match &*c {
            r"\t" => Ok(Delimiter(b'\t')),
            s => {
                if s.len() != 1 {
                    let msg = format!("Could not convert '{}' to a single \
                                       ASCII character.", s);
                    return Err(d.error(&*msg));
                }
                let c = s.chars().next().unwrap();
                if c.is_ascii() {
                    Ok(Delimiter(c as u8))
                } else {
                    let msg = format!("Could not convert '{}' \
                                       to ASCII delimiter.", c);
                    Err(d.error(&*msg))
                }
            }
        }
    }
}

#[derive(Debug)]
pub struct Config {
    path: Option<PathBuf>, // None implies <stdin>
    idx_path: Option<PathBuf>,
    select_columns: Option<SelectColumns>,
    delimiter: u8,
    pub no_headers: bool,
    flexible: bool,
    terminator: csv::Terminator,
    quote: u8,
    quote_style: csv::QuoteStyle,
    double_quote: bool,
    escape: u8,
}

impl Config {
    pub fn new(path: &Option<String>) -> Config {
        let (path, delim) = match *path {
            None => (None, b','),
            Some(ref s) if s.deref() == "-" => (None, b','),
            Some(ref s) => {
                let path = PathBuf::from(s);
                let delim =
                    if path.extension().map_or(false, |v| v == "tsv") {
                        b'\t'
                    } else {
                        b','
                    };
                (Some(path), delim)
            }
        };
        Config {
            path: path,
            idx_path: None,
            select_columns: None,
            delimiter: delim,
            no_headers: false,
            flexible: false,
            terminator: csv::Terminator::Any(b'\n'),
            quote: b'"',
            quote_style: csv::QuoteStyle::Necessary,
            double_quote: true,
            escape: b'\\',
        }
    }

    pub fn delimiter(mut self, d: Option<Delimiter>) -> Config {
        if let Some(d) = d {
            self.delimiter = d.as_byte();
        }
        self
    }

    pub fn no_headers(mut self, mut yes: bool) -> Config {
        if env::var("XSV_TOGGLE_HEADERS").unwrap_or("0".to_owned()) == "1" {
            yes = !yes;
        }
        self.no_headers = yes;
        self
    }

    pub fn flexible(mut self, yes: bool) -> Config {
        self.flexible = yes;
        self
    }

    pub fn crlf(mut self, yes: bool) -> Config {
        if yes {
            self.terminator = csv::Terminator::CRLF;
        } else {
            self.terminator = csv::Terminator::Any(b'\n');
        }
        self
    }

    pub fn terminator(mut self, term: csv::Terminator) -> Config {
        self.terminator = term;
        self
    }

    pub fn quote(mut self, quote: u8) -> Config {
        self.quote = quote;
        self
    }

    pub fn quote_style(mut self, style: csv::QuoteStyle) -> Config {
        self.quote_style = style;
        self
    }

    pub fn double_quote(mut self, yes: bool) -> Config {
        self.double_quote = yes;
        self
    }

    pub fn escape(mut self, escape: u8) -> Config {
        self.escape = escape;
        self
    }

    pub fn select(mut self, sel_cols: SelectColumns) -> Config {
        self.select_columns = Some(sel_cols);
        self
    }

    pub fn is_std(&self) -> bool {
        self.path.is_none()
    }

    pub fn selection(
        &self,
        first_record: &csv::ByteRecord,
    ) -> Result<Selection, String> {
        match self.select_columns {
            None => Err("Config has no 'SelectColums'. Did you call \
                         Config::select?".to_owned()),
            Some(ref sel) => sel.selection(first_record, !self.no_headers),
        }
    }

    pub fn write_headers<R: io::Read, W: io::Write>
                        (&self, r: &mut csv::Reader<R>, w: &mut csv::Writer<W>)
                        -> csv::Result<()> {
        if !self.no_headers {
            let r = r.byte_headers()?;
            if !r.is_empty() {
                w.write_record(r)?;
            }
        }
        Ok(())
    }

    pub fn writer(&self)
                 -> io::Result<csv::Writer<Box<io::Write+'static>>> {
        Ok(self.from_writer(self.io_writer()?))
    }

    pub fn reader(&self)
                 -> io::Result<csv::Reader<Box<io::Read+'static>>> {
        Ok(self.from_reader(self.io_reader()?))
    }

    pub fn reader_file(&self) -> io::Result<csv::Reader<fs::File>> {
        match self.path {
            None => Err(io::Error::new(
                io::ErrorKind::Other, "Cannot use <stdin> here",
            )),
            Some(ref p) => fs::File::open(p).map(|f| self.from_reader(f)),
        }
    }

    pub fn index_files(&self)
           -> io::Result<Option<(csv::Reader<fs::File>, fs::File)>> {
        let (csv_file, idx_file) = match (&self.path, &self.idx_path) {
            (&None, &None) => return Ok(None),
            (&None, &Some(_)) => return Err(io::Error::new(
                io::ErrorKind::Other,
                "Cannot use <stdin> with indexes",
                // Some(format!("index file: {}", p.display()))
            )),
            (&Some(ref p), &None) => {
                // We generally don't want to report an error here, since we're
                // passively trying to find an index.
                let idx_file = match fs::File::open(&util::idx_path(p)) {
                    // TODO: Maybe we should report an error if the file exists
                    // but is not readable.
                    Err(_) => return Ok(None),
                    Ok(f) => f,
                };
                (fs::File::open(p)?, idx_file)
            }
            (&Some(ref p), &Some(ref ip)) => {
                (fs::File::open(p)?, fs::File::open(ip)?)
            }
        };
        // If the CSV data was last modified after the index file was last
        // modified, then return an error and demand the user regenerate the
        // index.
        let data_modified = util::last_modified(&csv_file.metadata()?);
        let idx_modified = util::last_modified(&idx_file.metadata()?);
        if data_modified > idx_modified {
            return Err(io::Error::new(
                io::ErrorKind::Other,
                "The CSV file was modified after the index file. \
                 Please re-create the index.",
            ));
        }
        let csv_rdr = self.from_reader(csv_file);
        Ok(Some((csv_rdr, idx_file)))
    }

    pub fn indexed(&self)
                  -> CliResult<Option<Indexed<fs::File, fs::File>>> {
        match self.index_files()? {
            None => Ok(None),
            Some((r, i)) => Ok(Some(Indexed::open(r, i)?)),
        }
    }

    pub fn io_reader(&self) -> io::Result<Box<io::Read+'static>> {
        Ok(match self.path {
                None => Box::new(io::stdin()),
                Some(ref p) => {
                    match fs::File::open(p){
                        Ok(x) => Box::new(x),
                        Err(err) => {
                            let msg = format!(
                                "failed to open {}: {}", p.display(), err);
                            return Err(io::Error::new(
                                io::ErrorKind::NotFound,
                                msg,
                            ));
                        }
                    }
                },
            })
    }

    pub fn from_reader<R: Read>(&self, rdr: R) -> csv::Reader<R> {
        csv::ReaderBuilder::new()
            .flexible(self.flexible)
            .delimiter(self.delimiter)
            .has_headers(!self.no_headers)
            .quote(self.quote)
            .from_reader(rdr)
    }

    pub fn io_writer(&self) -> io::Result<Box<io::Write+'static>> {
        Ok(match self.path {
            None => Box::new(io::stdout()),
            Some(ref p) => Box::new(fs::File::create(p)?),
        })
    }

    pub fn from_writer<W: io::Write>(&self, wtr: W) -> csv::Writer<W> {
        csv::WriterBuilder::new()
            .flexible(self.flexible)
            .delimiter(self.delimiter)
            .terminator(self.terminator)
            .quote(self.quote)
            .quote_style(self.quote_style)
            .double_quote(self.double_quote)
            .escape(self.escape)
            .buffer_capacity(32 * (1<<10))
            .from_writer(wtr)
    }
}
<|endoftext|>


<commit_before>use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::env;
use std::io;
use std::fs;
use std::process::Command;
use std::fmt::{self, Display};

use itertools::Itertools;

use errors::*;
use rust_install::{temp, utils, dist};
use override_db::OverrideDB;
use toolchain::Toolchain;

pub const METADATA_VERSION: &'static str = "2";

#[derive(Debug)]
pub enum OverrideReason {
    Environment,
    OverrideDB(PathBuf),
}

impl Display for OverrideReason {
    fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
        match *self {
            OverrideReason::Environment => write!(f, "environment override by MULTIRUST_TOOLCHAIN"),
            OverrideReason::OverrideDB(ref path) => {
                write!(f, "directory override for '{}'", path.display())
            }
        }
    }
}

#[derive(Debug)]
pub struct Cfg {
    pub multirust_dir: PathBuf,
    pub version_file: PathBuf,
    pub override_db: OverrideDB,
    pub default_file: PathBuf,
    pub toolchains_dir: PathBuf,
    pub update_hash_dir: PathBuf,
    pub temp_cfg: temp::Cfg,
    pub gpg_key: Cow<'static, str>,
    pub env_override: Option<String>,
    pub dist_root_url: Cow<'static, str>,
    pub notify_handler: SharedNotifyHandler,
}

impl Cfg {
    pub fn from_env(notify_handler: SharedNotifyHandler) -> Result<Self> {
        // Get absolute home directory
        let data_dir = try!(utils::get_local_data_path());

        // Set up the multirust home directory
        let multirust_dir = env::var_os("MULTIRUST_HOME")
                                .and_then(utils::if_not_empty)
                                .map_or_else(|| data_dir.join(".multirust"),
                                             PathBuf::from);

        try!(utils::ensure_dir_exists("home", &multirust_dir, ntfy!(&notify_handler)));

        // Data locations
        let version_file = multirust_dir.join("version");
        let override_db = OverrideDB::new(multirust_dir.join("overrides"));
        let default_file = multirust_dir.join("default");
        let toolchains_dir = multirust_dir.join("toolchains");
        let update_hash_dir = multirust_dir.join("update-hashes");

        let notify_clone = notify_handler.clone();
        let temp_cfg = temp::Cfg::new(multirust_dir.join("tmp"),
                                      shared_ntfy!(move |n: temp::Notification| {
                                          notify_clone.call(Notification::Temp(n));
                                      }));

        // GPG key
        let gpg_key = if let Some(path) = env::var_os("MULTIRUST_GPG_KEY")
                                              .and_then(utils::if_not_empty) {
            Cow::Owned(try!(utils::read_file("public key", Path::new(&path))))
        } else {
            Cow::Borrowed(include_str!("rust-key.gpg.ascii"))
        };

        // Environment override
        let env_override = env::var("MULTIRUST_TOOLCHAIN")
                               .ok()
                               .and_then(utils::if_not_empty);

        let dist_root_url = env::var("MULTIRUST_DIST_ROOT")
                                .ok()
                                .and_then(utils::if_not_empty)
                                .map_or(Cow::Borrowed(dist::DEFAULT_DIST_ROOT),
                                        Cow::Owned);

        Ok(Cfg {
            multirust_dir: multirust_dir,
            version_file: version_file,
            override_db: override_db,
            default_file: default_file,
            toolchains_dir: toolchains_dir,
            update_hash_dir: update_hash_dir,
            temp_cfg: temp_cfg,
            gpg_key: gpg_key,
            notify_handler: notify_handler,
            env_override: env_override,
            dist_root_url: dist_root_url,
        })
    }

    pub fn set_default(&self, toolchain: &str) -> Result<()> {
        let work_file = try!(self.temp_cfg.new_file());

        try!(utils::write_file("temp", &work_file, toolchain));

        try!(utils::rename_file("default", &*work_file, &self.default_file));

        self.notify_handler.call(Notification::SetDefaultToolchain(toolchain));

        Ok(())
    }

    pub fn get_toolchain(&self, name: &str, create_parent: bool) -> Result<Toolchain> {
        if create_parent {
            try!(utils::ensure_dir_exists("toolchains",
                                          &self.toolchains_dir,
                                          ntfy!(&self.notify_handler)));
        }

        Ok(Toolchain::from(self, name))
    }

    pub fn verify_toolchain(&self, name: &str) -> Result<Toolchain> {
        let toolchain = try!(self.get_toolchain(name, false));
        try!(toolchain.verify());
        Ok(toolchain)
    }

    pub fn get_hash_file(&self, toolchain: &str, create_parent: bool) -> Result<PathBuf> {
        if create_parent {
            try!(utils::ensure_dir_exists("update-hash",
                                          &self.update_hash_dir,
                                          ntfy!(&self.notify_handler)));
        }

        Ok(self.update_hash_dir.join(toolchain))
    }

    pub fn which_binary(&self, path: &Path, binary: &str) -> Result<Option<PathBuf>> {

        if let Some((toolchain, _)) = try!(self.find_override_toolchain_or_default(path)) {
            Ok(Some(toolchain.prefix().binary_file(binary)))
        } else {
            Ok(None)
        }
    }

    pub fn upgrade_data(&self) -> Result<bool> {
        if !utils::is_file(&self.version_file) {
            return Ok(false);
        }

        let mut current_version = try!(utils::read_file("version", &self.version_file));
        let len = current_version.trim_right().len();
        current_version.truncate(len);

        if current_version == METADATA_VERSION {
            self.notify_handler
                .call(Notification::MetadataUpgradeNotNeeded(METADATA_VERSION));
            return Ok(false);
        }

        self.notify_handler
            .call(Notification::UpgradingMetadata(&current_version, METADATA_VERSION));

        match &*current_version {
            "1" => {
                // Ignore errors. These files may not exist.
                let _ = fs::remove_dir_all(self.multirust_dir.join("available-updates"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("update-sums"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("channel-sums"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("manifests"));

                try!(utils::write_file("version", &self.version_file, METADATA_VERSION));

                Ok(true)
            }
            _ => Err(Error::UnknownMetadataVersion(current_version)),
        }
    }

    pub fn delete_data(&self) -> Result<()> {
        if utils::path_exists(&self.multirust_dir) {
            Ok(try!(utils::remove_dir("home", &self.multirust_dir, ntfy!(&self.notify_handler))))
        } else {
            Ok(())
        }
    }

    pub fn find_default(&self) -> Result<Option<Toolchain>> {
        if !utils::is_file(&self.default_file) {
            return Ok(None);
        }
        let content = try!(utils::read_file("default", &self.default_file));
        let name = content.trim_matches('\n');
        if name.is_empty() {
            return Ok(None);
        }

        let toolchain = try!(self.verify_toolchain(name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));

        Ok(Some(toolchain))
    }

    pub fn find_override(&self, path: &Path) -> Result<Option<(Toolchain, OverrideReason)>> {
        if let Some(ref name) = self.env_override {
            let toolchain = try!(self.verify_toolchain(name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));

            return Ok(Some((toolchain, OverrideReason::Environment)));
        }

        if let Some((name, reason_path)) = try!(self.override_db
                                                    .find(path, self.notify_handler.as_ref())) {
            let toolchain = try!(self.verify_toolchain(&name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));
            return Ok(Some((toolchain, OverrideReason::OverrideDB(reason_path))));
        }

        Ok(None)
    }

    pub fn find_override_toolchain_or_default
        (&self,
         path: &Path)
         -> Result<Option<(Toolchain, Option<OverrideReason>)>> {
        Ok(if let Some((toolchain, reason)) = try!(self.find_override(path)) {
            Some((toolchain, Some(reason)))
        } else {
            try!(self.find_default()).map(|toolchain| (toolchain, None))
        })
    }

    pub fn list_toolchains(&self) -> Result<Vec<String>> {
        if utils::is_directory(&self.toolchains_dir) {
            let toolchains: Vec<_> = try!(utils::read_dir("toolchains", &self.toolchains_dir))
                                         .filter_map(io::Result::ok)
                                         .filter_map(|e| e.file_name().into_string().ok())
                                         .collect();

            Ok(toolchains)
        } else {
            Ok(Vec::new())
        }
    }

    pub fn update_all_channels(&self) -> Result<Vec<(String, Result<()>)>> {
        let mut toolchains = try!(self.list_toolchains());
        toolchains.sort();

        Ok(toolchains.into_iter()
                     .merge(["beta", "nightly", "stable"].into_iter().map(|s| (*s).to_owned()))
                     .dedup()
                     .filter(|name| {
                         dist::ToolchainDesc::from_str(&name).map(|d| d.is_tracking()) == Some(true)
                     })
                     .map(|name| {
                         let result = self.get_toolchain(&name, true)
                                          .and_then(|t| t.install_from_dist());
                         if let Err(ref e) = result {
                             self.notify_handler.call(Notification::NonFatalError(e));
                         }
                         (name, result)
                     })
                     .collect())
    }

    pub fn check_metadata_version(&self) -> Result<bool> {
        try!(utils::assert_is_directory(&self.multirust_dir));

        if !utils::is_file(&self.version_file) {
            self.notify_handler.call(Notification::WritingMetadataVersion(METADATA_VERSION));

            try!(utils::write_file("metadata version", &self.version_file, METADATA_VERSION));

            Ok(true)
        } else {
            let current_version = try!(utils::read_file("metadata version", &self.version_file));

            self.notify_handler.call(Notification::ReadMetadataVersion(&current_version));

            Ok(&*current_version == METADATA_VERSION)
        }
    }

    pub fn toolchain_for_dir(&self, path: &Path) -> Result<(Toolchain, Option<OverrideReason>)> {
        self.find_override_toolchain_or_default(path)
            .and_then(|r| r.ok_or(Error::NoDefaultToolchain))
    }

    pub fn create_command_for_dir(&self, path: &Path, binary: &str) -> Result<Command> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.create_command(binary)
    }

    pub fn doc_path_for_dir(&self, path: &Path, relative: &str) -> Result<PathBuf> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.doc_path(relative)
    }

    pub fn open_docs_for_dir(&self, path: &Path, relative: &str) -> Result<()> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.open_docs(relative)
    }
}
<commit_msg>Bump metadata version from 2 to 12<commit_after>use std::path::{Path, PathBuf};
use std::borrow::Cow;
use std::env;
use std::io;
use std::fs;
use std::process::Command;
use std::fmt::{self, Display};

use itertools::Itertools;

use errors::*;
use rust_install::{temp, utils, dist};
use override_db::OverrideDB;
use toolchain::Toolchain;

// Note: multirust-rs jumped from 2 to 12 to leave multirust.sh room to diverge
pub const METADATA_VERSION: &'static str = "12";

#[derive(Debug)]
pub enum OverrideReason {
    Environment,
    OverrideDB(PathBuf),
}

impl Display for OverrideReason {
    fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
        match *self {
            OverrideReason::Environment => write!(f, "environment override by MULTIRUST_TOOLCHAIN"),
            OverrideReason::OverrideDB(ref path) => {
                write!(f, "directory override for '{}'", path.display())
            }
        }
    }
}

#[derive(Debug)]
pub struct Cfg {
    pub multirust_dir: PathBuf,
    pub version_file: PathBuf,
    pub override_db: OverrideDB,
    pub default_file: PathBuf,
    pub toolchains_dir: PathBuf,
    pub update_hash_dir: PathBuf,
    pub temp_cfg: temp::Cfg,
    pub gpg_key: Cow<'static, str>,
    pub env_override: Option<String>,
    pub dist_root_url: Cow<'static, str>,
    pub notify_handler: SharedNotifyHandler,
}

impl Cfg {
    pub fn from_env(notify_handler: SharedNotifyHandler) -> Result<Self> {
        // Get absolute home directory
        let data_dir = try!(utils::get_local_data_path());

        // Set up the multirust home directory
        let multirust_dir = env::var_os("MULTIRUST_HOME")
                                .and_then(utils::if_not_empty)
                                .map_or_else(|| data_dir.join(".multirust"),
                                             PathBuf::from);

        try!(utils::ensure_dir_exists("home", &multirust_dir, ntfy!(&notify_handler)));

        // Data locations
        let version_file = multirust_dir.join("version");
        let override_db = OverrideDB::new(multirust_dir.join("overrides"));
        let default_file = multirust_dir.join("default");
        let toolchains_dir = multirust_dir.join("toolchains");
        let update_hash_dir = multirust_dir.join("update-hashes");

        let notify_clone = notify_handler.clone();
        let temp_cfg = temp::Cfg::new(multirust_dir.join("tmp"),
                                      shared_ntfy!(move |n: temp::Notification| {
                                          notify_clone.call(Notification::Temp(n));
                                      }));

        // GPG key
        let gpg_key = if let Some(path) = env::var_os("MULTIRUST_GPG_KEY")
                                              .and_then(utils::if_not_empty) {
            Cow::Owned(try!(utils::read_file("public key", Path::new(&path))))
        } else {
            Cow::Borrowed(include_str!("rust-key.gpg.ascii"))
        };

        // Environment override
        let env_override = env::var("MULTIRUST_TOOLCHAIN")
                               .ok()
                               .and_then(utils::if_not_empty);

        let dist_root_url = env::var("MULTIRUST_DIST_ROOT")
                                .ok()
                                .and_then(utils::if_not_empty)
                                .map_or(Cow::Borrowed(dist::DEFAULT_DIST_ROOT),
                                        Cow::Owned);

        Ok(Cfg {
            multirust_dir: multirust_dir,
            version_file: version_file,
            override_db: override_db,
            default_file: default_file,
            toolchains_dir: toolchains_dir,
            update_hash_dir: update_hash_dir,
            temp_cfg: temp_cfg,
            gpg_key: gpg_key,
            notify_handler: notify_handler,
            env_override: env_override,
            dist_root_url: dist_root_url,
        })
    }

    pub fn set_default(&self, toolchain: &str) -> Result<()> {
        let work_file = try!(self.temp_cfg.new_file());

        try!(utils::write_file("temp", &work_file, toolchain));

        try!(utils::rename_file("default", &*work_file, &self.default_file));

        self.notify_handler.call(Notification::SetDefaultToolchain(toolchain));

        Ok(())
    }

    pub fn get_toolchain(&self, name: &str, create_parent: bool) -> Result<Toolchain> {
        if create_parent {
            try!(utils::ensure_dir_exists("toolchains",
                                          &self.toolchains_dir,
                                          ntfy!(&self.notify_handler)));
        }

        Ok(Toolchain::from(self, name))
    }

    pub fn verify_toolchain(&self, name: &str) -> Result<Toolchain> {
        let toolchain = try!(self.get_toolchain(name, false));
        try!(toolchain.verify());
        Ok(toolchain)
    }

    pub fn get_hash_file(&self, toolchain: &str, create_parent: bool) -> Result<PathBuf> {
        if create_parent {
            try!(utils::ensure_dir_exists("update-hash",
                                          &self.update_hash_dir,
                                          ntfy!(&self.notify_handler)));
        }

        Ok(self.update_hash_dir.join(toolchain))
    }

    pub fn which_binary(&self, path: &Path, binary: &str) -> Result<Option<PathBuf>> {

        if let Some((toolchain, _)) = try!(self.find_override_toolchain_or_default(path)) {
            Ok(Some(toolchain.prefix().binary_file(binary)))
        } else {
            Ok(None)
        }
    }

    pub fn upgrade_data(&self) -> Result<bool> {
        if !utils::is_file(&self.version_file) {
            return Ok(false);
        }

        let mut current_version = try!(utils::read_file("version", &self.version_file));
        let len = current_version.trim_right().len();
        current_version.truncate(len);

        if current_version == METADATA_VERSION {
            self.notify_handler
                .call(Notification::MetadataUpgradeNotNeeded(METADATA_VERSION));
            return Ok(false);
        }

        self.notify_handler
            .call(Notification::UpgradingMetadata(&current_version, METADATA_VERSION));

        match &*current_version {
            "1" => {
                // Ignore errors. These files may not exist.
                let _ = fs::remove_dir_all(self.multirust_dir.join("available-updates"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("update-sums"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("channel-sums"));
                let _ = fs::remove_dir_all(self.multirust_dir.join("manifests"));

                try!(utils::write_file("version", &self.version_file, METADATA_VERSION));

                Ok(true)
            }
            _ => Err(Error::UnknownMetadataVersion(current_version)),
        }
    }

    pub fn delete_data(&self) -> Result<()> {
        if utils::path_exists(&self.multirust_dir) {
            Ok(try!(utils::remove_dir("home", &self.multirust_dir, ntfy!(&self.notify_handler))))
        } else {
            Ok(())
        }
    }

    pub fn find_default(&self) -> Result<Option<Toolchain>> {
        if !utils::is_file(&self.default_file) {
            return Ok(None);
        }
        let content = try!(utils::read_file("default", &self.default_file));
        let name = content.trim_matches('\n');
        if name.is_empty() {
            return Ok(None);
        }

        let toolchain = try!(self.verify_toolchain(name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));

        Ok(Some(toolchain))
    }

    pub fn find_override(&self, path: &Path) -> Result<Option<(Toolchain, OverrideReason)>> {
        if let Some(ref name) = self.env_override {
            let toolchain = try!(self.verify_toolchain(name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));

            return Ok(Some((toolchain, OverrideReason::Environment)));
        }

        if let Some((name, reason_path)) = try!(self.override_db
                                                    .find(path, self.notify_handler.as_ref())) {
            let toolchain = try!(self.verify_toolchain(&name).map_err(|_| Error::ToolchainNotInstalled(name.to_string())));
            return Ok(Some((toolchain, OverrideReason::OverrideDB(reason_path))));
        }

        Ok(None)
    }

    pub fn find_override_toolchain_or_default
        (&self,
         path: &Path)
         -> Result<Option<(Toolchain, Option<OverrideReason>)>> {
        Ok(if let Some((toolchain, reason)) = try!(self.find_override(path)) {
            Some((toolchain, Some(reason)))
        } else {
            try!(self.find_default()).map(|toolchain| (toolchain, None))
        })
    }

    pub fn list_toolchains(&self) -> Result<Vec<String>> {
        if utils::is_directory(&self.toolchains_dir) {
            let toolchains: Vec<_> = try!(utils::read_dir("toolchains", &self.toolchains_dir))
                                         .filter_map(io::Result::ok)
                                         .filter_map(|e| e.file_name().into_string().ok())
                                         .collect();

            Ok(toolchains)
        } else {
            Ok(Vec::new())
        }
    }

    pub fn update_all_channels(&self) -> Result<Vec<(String, Result<()>)>> {
        let mut toolchains = try!(self.list_toolchains());
        toolchains.sort();

        Ok(toolchains.into_iter()
                     .merge(["beta", "nightly", "stable"].into_iter().map(|s| (*s).to_owned()))
                     .dedup()
                     .filter(|name| {
                         dist::ToolchainDesc::from_str(&name).map(|d| d.is_tracking()) == Some(true)
                     })
                     .map(|name| {
                         let result = self.get_toolchain(&name, true)
                                          .and_then(|t| t.install_from_dist());
                         if let Err(ref e) = result {
                             self.notify_handler.call(Notification::NonFatalError(e));
                         }
                         (name, result)
                     })
                     .collect())
    }

    pub fn check_metadata_version(&self) -> Result<bool> {
        try!(utils::assert_is_directory(&self.multirust_dir));

        if !utils::is_file(&self.version_file) {
            self.notify_handler.call(Notification::WritingMetadataVersion(METADATA_VERSION));

            try!(utils::write_file("metadata version", &self.version_file, METADATA_VERSION));

            Ok(true)
        } else {
            let current_version = try!(utils::read_file("metadata version", &self.version_file));

            self.notify_handler.call(Notification::ReadMetadataVersion(&current_version));

            Ok(&*current_version == METADATA_VERSION)
        }
    }

    pub fn toolchain_for_dir(&self, path: &Path) -> Result<(Toolchain, Option<OverrideReason>)> {
        self.find_override_toolchain_or_default(path)
            .and_then(|r| r.ok_or(Error::NoDefaultToolchain))
    }

    pub fn create_command_for_dir(&self, path: &Path, binary: &str) -> Result<Command> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.create_command(binary)
    }

    pub fn doc_path_for_dir(&self, path: &Path, relative: &str) -> Result<PathBuf> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.doc_path(relative)
    }

    pub fn open_docs_for_dir(&self, path: &Path, relative: &str) -> Result<()> {
        let (toolchain, _) = try!(self.toolchain_for_dir(path));
        toolchain.open_docs(relative)
    }
}
<|endoftext|>


<commit_before>use super::{Tokenizer, Whitespace, CompareStrs};
use super::{ScanError, OtherScanError};

use std::fmt::{Show, Formatter, FormatError};
use std::str::CharRange;

pub trait ScanCursor<'scanee>: Clone + Eq {
	fn expect_tok(&self, s: &str) -> Result<Self, ScanError>;
	fn consumed(&self) -> uint;
	fn pop_token(&self) -> Option<(&'scanee str, Self)>;
	fn pop_ws(&self) -> Self;
	fn slice_from(&self, from: uint) -> Self;
	fn str_slice_to(&self, to: uint) -> &'scanee str;
	fn str_slice_to_cur(&self, to: &Self) -> &'scanee str;
	fn tail_str(&self) -> &'scanee str;
	fn is_empty(&self) -> bool;
	fn compare_strs(&self, a: &str, b: &str) -> bool;

	fn expect_eof(&self) -> Result<(), ScanError> {
		if self.pop_token().is_some() {
			Err(self.expected_eof())
		} else {
			Ok(())
		}
	}

	fn expected(&self, desc: &str) -> ScanError {
		let msg = match self.pop_token() {
			Some((got, _)) => format!("expected {}, got `{}`", desc, got.escape_default()),
			None => format!("expected {}, got end of input", desc)
		};

		OtherScanError(msg, self.consumed())
	}

	fn expected_tok(&self, tok: &str) -> ScanError {
		let toks = [tok];
		self.expected_one_of(&toks)
	}

	fn expected_eof(&self) -> ScanError {
		self.expected_one_of(&[])
	}

	fn expected_one_of(&self, toks: &[&str]) -> ScanError {
		let mut toks = toks.iter().map(|s| format!("`{}`", s.escape_default()));
		let toks = {
			let first = toks.next();
			first.map(|first| toks.fold(first, |a,b| format!("{}, {}", a, b)))
		};

		let msg = match (toks, self.pop_token()) {
			(Some(exp), Some((got, _))) => format!("expected {}, got `{}`", exp, got.escape_default()),
			(Some(exp), None) => format!("expected {}, got end of input", exp),
			(None, Some((got, _))) => format!("expected end of input, got `{}`", got.escape_default()),
			(None, None) => "expected end of input".into_string()
		};

		OtherScanError(msg, self.consumed())
	}

	fn expected_min_repeats(&self, min: uint, got: uint) -> ScanError {
		OtherScanError(format!("expected at least {} repeats, got {}", min, got), self.consumed())
	}
}

#[deriving(Clone, Eq, PartialEq)]
pub struct Cursor<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> {
	slice: &'a str,
	offset: uint,
	tc: Tok,
	sp: Sp,
	cs: Cs,
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> Cursor<'a, Tok, Sp, Cs> {
	pub fn new<'b>(s: &'b str, tc: Tok, sp: Sp, cs: Cs) -> Cursor<'b, Tok, Sp, Cs> {
		Cursor {
			slice: s,
			offset: 0,
			tc: tc,
			sp: sp,
			cs: cs,
		}
	}
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> Show for Cursor<'a, Tok, Sp, Cs> {
	fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
		try!(write!(f, "Cursor<{}, {}, {}> {{ offset: {}, .. }}", self.tc, self.sp, self.cs, self.offset));
		Ok(())
	}
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> ScanCursor<'a> for Cursor<'a, Tok, Sp, Cs> {
	fn expect_tok(&self, s: &str) -> Result<Cursor<'a, Tok, Sp, Cs>, ScanError> {
		debug!("{}.expect_tok({})", self, s);
		match self.pop_token() {
			Some((tok, ref cur)) if self.compare_strs(s, tok) => Ok(cur.clone()),
			_ => Err(self.expected_tok(s))
		}
	}

	fn consumed(&self) -> uint {
		self.offset
	}

	fn pop_token(&self) -> Option<(&'a str, Cursor<'a, Tok, Sp, Cs>)> {
		debug!("{}.pop_token()", self);
		// First, strip out leading whitespace.  It's up to the whitespace policy to *not* strip characters it wants to turn into a token.
		let cur = self.pop_ws();

		// Next, check to see if there is a whitespace token.  This allows the space policy to do things like ignore most whitespace, but turn line breaks into explicit tokens.  Note that unlike the regular Tokenizer, the Whitespace policy is responsible for returning the str slice itself.  This is used to do things like map all whitespace to a single `" "` token.
		match self.sp.token_len(cur.tail_str()) {
			Some(end) => return Some((cur.str_slice_to(end), cur.slice_from(end))),
			None => ()
		}

		// Do not assume that empty input means we can't match a token; the token class might, for example, turn end-of-input into an explicit token.
		let tail_str = cur.tail_str();
		match self.tc.token_len(tail_str) {
			Some(end) => Some((cur.str_slice_to(end), cur.slice_from(end))),
			None => {
				// One of two things: either we have some input left and will thus return a single-character token, or there is nothing left whereby we return None.
				if cur.is_empty() {
					return None;
				} else {
					let CharRange { ch: _, next } = tail_str.char_range_at(0);
					Some((cur.str_slice_to(next), cur.slice_from(next)))
				}
			},
		}
	}

	fn pop_ws(&self) -> Cursor<'a, Tok, Sp, Cs> {
		debug!("{}.pop_ws()", self);

		self.slice_from(self.sp.strip_len(self.tail_str()))
	}

	fn slice_from(&self, from: uint) -> Cursor<'a, Tok, Sp, Cs> {
		Cursor {
			offset: ::std::cmp::min(self.slice.len(), self.offset + from),
			..self.clone()
		}
	}

	fn str_slice_to(&self, to: uint) -> &'a str {
		self.tail_str().slice_to(to)
	}

	fn str_slice_to_cur(&self, to: &Cursor<'a, Tok, Sp, Cs>) -> &'a str {
		self.slice.slice(self.offset, to.offset)
	}

	fn tail_str(&self) -> &'a str {
		self.slice.slice_from(self.offset)
	}

	fn is_empty(&self) -> bool {
		self.offset == self.slice.len()
	}

	fn compare_strs(&self, a: &str, b: &str) -> bool {
		self.cs.compare_strs(a, b)
	}
}
<commit_msg>Added some more debug logging to Cursor.<commit_after>use super::{Tokenizer, Whitespace, CompareStrs};
use super::{ScanError, OtherScanError};

use std::fmt::{Show, Formatter, FormatError};
use std::str::CharRange;

pub trait ScanCursor<'scanee>: Clone + Eq {
	fn expect_tok(&self, s: &str) -> Result<Self, ScanError>;
	fn consumed(&self) -> uint;
	fn pop_token(&self) -> Option<(&'scanee str, Self)>;
	fn pop_ws(&self) -> Self;
	fn slice_from(&self, from: uint) -> Self;
	fn str_slice_to(&self, to: uint) -> &'scanee str;
	fn str_slice_to_cur(&self, to: &Self) -> &'scanee str;
	fn tail_str(&self) -> &'scanee str;
	fn is_empty(&self) -> bool;
	fn compare_strs(&self, a: &str, b: &str) -> bool;

	fn expect_eof(&self) -> Result<(), ScanError> {
		if self.pop_token().is_some() {
			Err(self.expected_eof())
		} else {
			Ok(())
		}
	}

	fn expected(&self, desc: &str) -> ScanError {
		let msg = match self.pop_token() {
			Some((got, _)) => format!("expected {}, got `{}`", desc, got.escape_default()),
			None => format!("expected {}, got end of input", desc)
		};

		OtherScanError(msg, self.consumed())
	}

	fn expected_tok(&self, tok: &str) -> ScanError {
		let toks = [tok];
		self.expected_one_of(&toks)
	}

	fn expected_eof(&self) -> ScanError {
		self.expected_one_of(&[])
	}

	fn expected_one_of(&self, toks: &[&str]) -> ScanError {
		let mut toks = toks.iter().map(|s| format!("`{}`", s.escape_default()));
		let toks = {
			let first = toks.next();
			first.map(|first| toks.fold(first, |a,b| format!("{}, {}", a, b)))
		};

		let msg = match (toks, self.pop_token()) {
			(Some(exp), Some((got, _))) => format!("expected {}, got `{}`", exp, got.escape_default()),
			(Some(exp), None) => format!("expected {}, got end of input", exp),
			(None, Some((got, _))) => format!("expected end of input, got `{}`", got.escape_default()),
			(None, None) => "expected end of input".into_string()
		};

		OtherScanError(msg, self.consumed())
	}

	fn expected_min_repeats(&self, min: uint, got: uint) -> ScanError {
		OtherScanError(format!("expected at least {} repeats, got {}", min, got), self.consumed())
	}
}

#[deriving(Clone, Eq, PartialEq)]
pub struct Cursor<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> {
	slice: &'a str,
	offset: uint,
	tc: Tok,
	sp: Sp,
	cs: Cs,
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> Cursor<'a, Tok, Sp, Cs> {
	pub fn new<'b>(s: &'b str, tc: Tok, sp: Sp, cs: Cs) -> Cursor<'b, Tok, Sp, Cs> {
		Cursor {
			slice: s,
			offset: 0,
			tc: tc,
			sp: sp,
			cs: cs,
		}
	}
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> Show for Cursor<'a, Tok, Sp, Cs> {
	fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
		try!(write!(f, "Cursor<{}, {}, {}> {{ offset: {}, .. }}", self.tc, self.sp, self.cs, self.offset));
		Ok(())
	}
}

impl<'a, Tok: Tokenizer, Sp: Whitespace, Cs: CompareStrs> ScanCursor<'a> for Cursor<'a, Tok, Sp, Cs> {
	fn expect_tok(&self, s: &str) -> Result<Cursor<'a, Tok, Sp, Cs>, ScanError> {
		debug!("{}.expect_tok({})", self, s);
		match self.pop_token() {
			Some((tok, ref cur)) if self.compare_strs(s, tok) => Ok(cur.clone()),
			_ => Err(self.expected_tok(s))
		}
	}

	fn consumed(&self) -> uint {
		self.offset
	}

	fn pop_token(&self) -> Option<(&'a str, Cursor<'a, Tok, Sp, Cs>)> {
		debug!("{}.pop_token()", self);
		// First, strip out leading whitespace.  It's up to the whitespace policy to *not* strip characters it wants to turn into a token.
		let cur = self.pop_ws();

		// Next, check to see if there is a whitespace token.  This allows the space policy to do things like ignore most whitespace, but turn line breaks into explicit tokens.  Note that unlike the regular Tokenizer, the Whitespace policy is responsible for returning the str slice itself.  This is used to do things like map all whitespace to a single `" "` token.
		match self.sp.token_len(cur.tail_str()) {
			Some((end, s)) => {
				debug!("{}.pop_token - sp token `{}`", self, s.escape_default());
				return Some((s, cur.slice_from(end)));
			},
			None => ()
		}

		// Do not assume that empty input means we can't match a token; the token class might, for example, turn end-of-input into an explicit token.
		let tail_str = cur.tail_str();
		match self.tc.token_len(tail_str) {
			Some(end) => {
				let tok = cur.str_slice_to(end);
				debug!("{}.pop_token - token `{}`", self, tok.escape_default());
				Some((tok, cur.slice_from(end)))
			},
			None => {
				// One of two things: either we have some input left and will thus return a single-character token, or there is nothing left whereby we return None.
				if cur.is_empty() {
					debug!("{}.pop_token - no token", self);
					return None;
				} else {
					let CharRange { ch: _, next } = tail_str.char_range_at(0);
					let tok = cur.str_slice_to(next);
					debug!("{}.pop_token - def token `{}`", self, tok.escape_default());
					Some((tok, cur.slice_from(next)))
				}
			},
		}
	}

	fn pop_ws(&self) -> Cursor<'a, Tok, Sp, Cs> {
		debug!("{}.pop_ws()", self);

		self.slice_from(self.sp.strip_len(self.tail_str()))
	}

	fn slice_from(&self, from: uint) -> Cursor<'a, Tok, Sp, Cs> {
		Cursor {
			offset: ::std::cmp::min(self.slice.len(), self.offset + from),
			..self.clone()
		}
	}

	fn str_slice_to(&self, to: uint) -> &'a str {
		self.tail_str().slice_to(to)
	}

	fn str_slice_to_cur(&self, to: &Cursor<'a, Tok, Sp, Cs>) -> &'a str {
		self.slice.slice(self.offset, to.offset)
	}

	fn tail_str(&self) -> &'a str {
		self.slice.slice_from(self.offset)
	}

	fn is_empty(&self) -> bool {
		self.offset == self.slice.len()
	}

	fn compare_strs(&self, a: &str, b: &str) -> bool {
		self.cs.compare_strs(a, b)
	}
}
<|endoftext|>


<commit_before>use af;
use af::{Backend};
use std::cell::Cell;
use std::sync::Arc;

pub type DeviceManager = Arc<DeviceManagerFactory>;

#[derive(Clone, Copy, PartialEq)]
pub struct Device {
  pub backend: Backend,
  pub id: i32,
}

pub struct DeviceManagerFactory {
  devices: Vec<Device>,
  current: Cell<Device>,
}

// toggle the backend and device
fn set_device(device: Device) {
  match af::set_backend(device.backend) {
    Ok(_)  => {},
    Err(e) =>  panic!("could not set backend: {:?}", e),
  };

  match af::set_device(device.id) {
    Ok(_)  => {},
    Err(e) =>  panic!("could not set device: {:?}", e),
  };
}

fn create_devices(backend: Backend) -> Vec<Device> {
  let mut buffer: Vec<Device> = Vec::new();
  af::set_backend(backend).unwrap();
  let num_devices: i32 = af::device_count().unwrap();
  for i in 0..num_devices {
    buffer.push(Device{ backend: backend, id: i });
  }
  buffer
}

impl DeviceManagerFactory {
  pub fn new() -> Arc<DeviceManagerFactory> {
    let mut devices = Vec::new();
    let available = af::get_available_backends().unwrap();
    for backend in available {
      devices.extend(create_devices(backend));
    }

    assert!(devices.len() > 0);
    let current = devices.last().unwrap().clone();
    set_device(current);

    Arc::new(DeviceManagerFactory {
      devices: devices,
      current: Cell::new(current),
    })
  }

  pub fn swap_device(&self, device: Device)
  {
    let c = self.current.get();
    if c != device
    {
      assert!(self.devices.contains(&device));
      println!("Swapping {}/{} to {}/{}", c.backend, c.id
               , device.backend, device.id);
      set_device(device);
      self.current.set(device);
    }
  }
}
<commit_msg>add debug to device<commit_after>use af;
use af::{Backend};
use std::cell::Cell;
use std::sync::Arc;

pub type DeviceManager = Arc<DeviceManagerFactory>;

#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Device {
  pub backend: Backend,
  pub id: i32,
}

pub struct DeviceManagerFactory {
  devices: Vec<Device>,
  current: Cell<Device>,
}

// toggle the backend and device
fn set_device(device: Device) {
  match af::set_backend(device.backend) {
    Ok(_)  => {},
    Err(e) =>  panic!("could not set backend: {:?}", e),
  };

  match af::set_device(device.id) {
    Ok(_)  => {},
    Err(e) =>  panic!("could not set device: {:?}", e),
  };
}

fn create_devices(backend: Backend) -> Vec<Device> {
  let mut buffer: Vec<Device> = Vec::new();
  af::set_backend(backend).unwrap();
  let num_devices: i32 = af::device_count().unwrap();
  for i in 0..num_devices {
    buffer.push(Device{ backend: backend, id: i });
  }
  buffer
}

impl DeviceManagerFactory {
  pub fn new() -> Arc<DeviceManagerFactory> {
    let mut devices = Vec::new();
    let available = af::get_available_backends().unwrap();
    for backend in available {
      devices.extend(create_devices(backend));
    }

    assert!(devices.len() > 0);
    let current = devices.last().unwrap().clone();
    set_device(current);

    Arc::new(DeviceManagerFactory {
      devices: devices,
      current: Cell::new(current),
    })
  }

  pub fn swap_device(&self, device: Device)
  {
    let c = self.current.get();
    if c != device
    {
      println!("{:?}", self.devices);
      assert!(self.devices.contains(&device));
      println!("Swapping {}/{} to {}/{}", c.backend, c.id
               , device.backend, device.id);
      set_device(device);
      self.current.set(device);
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2015, The Rust-GNOME Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

//! A widget

use ffi;

/// A widget
struct_Widget!(Widget);

impl_drop!(Widget);
impl_TraitWidget!(Widget);
<commit_msg>Remove struct Widget<commit_after><|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! udev-related methods
use std::{ffi::OsStr, fmt, path::Path};

use crate::stratis::{StratisError, StratisResult};

/// Key for a udev property
pub const FS_TYPE_KEY: &str = "ID_FS_TYPE";

/// Possible values for the "ID_FS_TYPE" udev property
pub const STRATIS_FS_TYPE: &str = "stratis";
pub const CRYPTO_FS_TYPE: &str = "crypto_LUKS";

/// Possible value for a udev subsystem designation
pub const SUBSYSTEM_BLOCK: &str = "block";

/// Make an enumerator for enumerating block devices. Return an error if there
/// was any udev-related error.
pub fn block_enumerator(context: &libudev::Context) -> libudev::Result<libudev::Enumerator> {
    let mut enumerator = libudev::Enumerator::new(context)?;
    enumerator.match_subsystem(SUBSYSTEM_BLOCK)?;
    Ok(enumerator)
}

/// Get a udev property with the given name for the given device.
/// Returns None if no udev property found for the given property name.
/// Returns an error if the value of the property can not be converted to
/// a string using the standard conversion for this OS.
pub fn get_udev_property<T: AsRef<OsStr>>(
    device: &libudev::Device,
    property_name: T,
) -> Option<StratisResult<String>>
where
    T: fmt::Display,
{
    device.property_value(&property_name).map(|value| {
        value
            .to_str()
            .ok_or_else(|| {
                StratisError::Error(format!(
                    "Unable to convert udev property value with key {} to a string, lossy value is {}",
                    property_name,
                    value.to_string_lossy()
                ))
            })
            .map(|value| value.into())
    })
}

/// Returns true if udev indicates that the device is a multipath member
/// device, else false. Returns an error on a failure to interpret the
/// value.
fn is_multipath_member(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, "DM_MULTIPATH_DEVICE_PATH") {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == "1"),
        Some(Err(err)) => Err(err),
    }
}

/// If the expression is true, then it seems that no other system is
/// known to udev to claim this device.
/// Note from mulhern: I have no idea myself why this particular expression
/// should be correct. I was told that the original source was dlehman.
fn is_unclaimed(device: &libudev::Device) -> bool {
    (get_udev_property(device, "ID_PART_TABLE_TYPE").is_none()
        || get_udev_property(device, "ID_PART_ENTRY_DISK").is_some())
        && get_udev_property(device, "ID_FS_USAGE").is_none()
}

/// Return true if the device is identified by udev as belonging to Stratis.
/// Return an error if a udev property value could not be converted.
fn is_stratis(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == STRATIS_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// Return true if the device is identified by udev as being an encrypted
/// LUKS device. Return an error if a udev property could not be converted.
fn is_luks(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == CRYPTO_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// An enum to encode udev classification of a device
#[derive(Debug, Eq, PartialEq)]
pub enum UdevOwnership {
    Luks,
    MultipathMember,
    Stratis,
    Theirs,
    Unowned,
}

impl fmt::Display for UdevOwnership {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            UdevOwnership::Luks => write!(f, "LUKS encrypted block device"),
            UdevOwnership::MultipathMember => write!(f, "member of a multipath block device"),
            UdevOwnership::Stratis => write!(f, "Stratis block device"),
            UdevOwnership::Theirs => write!(f, "block device which appears to be owned"),
            UdevOwnership::Unowned => write!(f, "block device which appears to be unowned"),
        }
    }
}

/// Decide the ownership of a device based on udev information.
///
/// Always check multipath member status first. The theory is that a multipath
/// member may also appear to belong to Stratis, but it should not be identified
/// as a Stratis device.
///
/// Note that the designation Theirs, i.e., the device already belongs to some
/// other entity is the default designation. This seems counterintuitive, but
/// it is the unclaimed designation that has a boolean expression on udev
/// properties associated with it.
pub fn decide_ownership(device: &libudev::Device) -> StratisResult<UdevOwnership> {
    || -> StratisResult<UdevOwnership> {
        // We believe that it is possible to be a multipath member and also to
        // be identified as a Stratis device. The designations are not mutually
        // exclusive, but the multipath member device must not be used by Stratis.
        if is_multipath_member(device)? {
            return Ok(UdevOwnership::MultipathMember);
        }

        // We believe that the following designations are mutually exclusive, i.e.
        // it is not possible to be a Stratis device and also to appear unowned.
        Ok(if is_stratis(device)? {
            UdevOwnership::Stratis
        } else if is_luks(device)? {
            UdevOwnership::Luks
        } else if is_unclaimed(device) {
            UdevOwnership::Unowned
        } else {
            UdevOwnership::Theirs
        })
    }()
    .map_err(|err| {
        StratisError::Error(format!(
            "Could not determine ownership of a device from a udev database entry: {}",
            err
        ))
    })
}

/// Locate a udev block device with the specified devnode and apply a function
/// to that device, returning the result.
/// This approach is necessitated by the libudev lifetimes, which do not allow
/// returning anything directly obtained from the enumerator value created in
/// the method itself.
/// Note that this does require iterating through the blockdevs in the udev
/// database, so it is essentially linear in the number of block devices.
pub fn block_device_apply<F, U>(devnode: &Path, f: F) -> StratisResult<Option<U>>
where
    F: FnOnce(&libudev::Device) -> U,
{
    let canonical = devnode.canonicalize()?;

    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| dev.is_initialized())
        .find(|x| x.devnode().map_or(false, |d| canonical == d))
        .map(|d| f(&d)))
}
<commit_msg>Remove an obsolete comment<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! udev-related methods
use std::{ffi::OsStr, fmt, path::Path};

use crate::stratis::{StratisError, StratisResult};

/// Key for a udev property
pub const FS_TYPE_KEY: &str = "ID_FS_TYPE";

/// Possible values for the "ID_FS_TYPE" udev property
pub const STRATIS_FS_TYPE: &str = "stratis";
pub const CRYPTO_FS_TYPE: &str = "crypto_LUKS";

/// Possible value for a udev subsystem designation
pub const SUBSYSTEM_BLOCK: &str = "block";

/// Make an enumerator for enumerating block devices. Return an error if there
/// was any udev-related error.
pub fn block_enumerator(context: &libudev::Context) -> libudev::Result<libudev::Enumerator> {
    let mut enumerator = libudev::Enumerator::new(context)?;
    enumerator.match_subsystem(SUBSYSTEM_BLOCK)?;
    Ok(enumerator)
}

/// Get a udev property with the given name for the given device.
/// Returns None if no udev property found for the given property name.
/// Returns an error if the value of the property can not be converted to
/// a string using the standard conversion for this OS.
pub fn get_udev_property<T: AsRef<OsStr>>(
    device: &libudev::Device,
    property_name: T,
) -> Option<StratisResult<String>>
where
    T: fmt::Display,
{
    device.property_value(&property_name).map(|value| {
        value
            .to_str()
            .ok_or_else(|| {
                StratisError::Error(format!(
                    "Unable to convert udev property value with key {} to a string, lossy value is {}",
                    property_name,
                    value.to_string_lossy()
                ))
            })
            .map(|value| value.into())
    })
}

/// Returns true if udev indicates that the device is a multipath member
/// device, else false. Returns an error on a failure to interpret the
/// value.
fn is_multipath_member(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, "DM_MULTIPATH_DEVICE_PATH") {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == "1"),
        Some(Err(err)) => Err(err),
    }
}

/// If the expression is true, then it seems that no other system is
/// known to udev to claim this device.
fn is_unclaimed(device: &libudev::Device) -> bool {
    (get_udev_property(device, "ID_PART_TABLE_TYPE").is_none()
        || get_udev_property(device, "ID_PART_ENTRY_DISK").is_some())
        && get_udev_property(device, "ID_FS_USAGE").is_none()
}

/// Return true if the device is identified by udev as belonging to Stratis.
/// Return an error if a udev property value could not be converted.
fn is_stratis(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == STRATIS_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// Return true if the device is identified by udev as being an encrypted
/// LUKS device. Return an error if a udev property could not be converted.
fn is_luks(device: &libudev::Device) -> StratisResult<bool> {
    match get_udev_property(device, FS_TYPE_KEY) {
        None => Ok(false),
        Some(Ok(value)) => Ok(value == CRYPTO_FS_TYPE),
        Some(Err(err)) => Err(err),
    }
}

/// An enum to encode udev classification of a device
#[derive(Debug, Eq, PartialEq)]
pub enum UdevOwnership {
    Luks,
    MultipathMember,
    Stratis,
    Theirs,
    Unowned,
}

impl fmt::Display for UdevOwnership {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            UdevOwnership::Luks => write!(f, "LUKS encrypted block device"),
            UdevOwnership::MultipathMember => write!(f, "member of a multipath block device"),
            UdevOwnership::Stratis => write!(f, "Stratis block device"),
            UdevOwnership::Theirs => write!(f, "block device which appears to be owned"),
            UdevOwnership::Unowned => write!(f, "block device which appears to be unowned"),
        }
    }
}

/// Decide the ownership of a device based on udev information.
///
/// Always check multipath member status first. The theory is that a multipath
/// member may also appear to belong to Stratis, but it should not be identified
/// as a Stratis device.
///
/// Note that the designation Theirs, i.e., the device already belongs to some
/// other entity is the default designation. This seems counterintuitive, but
/// it is the unclaimed designation that has a boolean expression on udev
/// properties associated with it.
pub fn decide_ownership(device: &libudev::Device) -> StratisResult<UdevOwnership> {
    || -> StratisResult<UdevOwnership> {
        // We believe that it is possible to be a multipath member and also to
        // be identified as a Stratis device. The designations are not mutually
        // exclusive, but the multipath member device must not be used by Stratis.
        if is_multipath_member(device)? {
            return Ok(UdevOwnership::MultipathMember);
        }

        // We believe that the following designations are mutually exclusive, i.e.
        // it is not possible to be a Stratis device and also to appear unowned.
        Ok(if is_stratis(device)? {
            UdevOwnership::Stratis
        } else if is_luks(device)? {
            UdevOwnership::Luks
        } else if is_unclaimed(device) {
            UdevOwnership::Unowned
        } else {
            UdevOwnership::Theirs
        })
    }()
    .map_err(|err| {
        StratisError::Error(format!(
            "Could not determine ownership of a device from a udev database entry: {}",
            err
        ))
    })
}

/// Locate a udev block device with the specified devnode and apply a function
/// to that device, returning the result.
/// This approach is necessitated by the libudev lifetimes, which do not allow
/// returning anything directly obtained from the enumerator value created in
/// the method itself.
/// Note that this does require iterating through the blockdevs in the udev
/// database, so it is essentially linear in the number of block devices.
pub fn block_device_apply<F, U>(devnode: &Path, f: F) -> StratisResult<Option<U>>
where
    F: FnOnce(&libudev::Device) -> U,
{
    let canonical = devnode.canonicalize()?;

    let context = libudev::Context::new()?;
    let mut enumerator = block_enumerator(&context)?;

    Ok(enumerator
        .scan_devices()?
        .filter(|dev| dev.is_initialized())
        .find(|x| x.devnode().map_or(false, |d| canonical == d))
        .map(|d| f(&d)))
}
<|endoftext|>