<commit_before>#[macro_use]
extern crate serde_derive;
extern crate toml;
#[macro_use]
extern crate errors;
extern crate highlighting;
extern crate chrono;
extern crate globset;

use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use toml::{Value as Toml};
use chrono::Utc;
use globset::{Glob, GlobSet, GlobSetBuilder};

use errors::{Result, ResultExt};
use highlighting::THEME_SET;

mod theme;

use theme::Theme;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
    /// Base URL of the site, the only required config argument
    pub base_url: String,

    /// Theme to use
    pub theme: Option<String>,
    /// Title of the site. Defaults to None
    pub title: Option<String>,
    /// Whether to highlight all code blocks found in markdown files. Defaults to false
    pub highlight_code: Option<bool>,
    /// Which themes to use for code highlighting. See Readme for supported themes
    pub highlight_theme: Option<String>,
    /// Description of the site
    pub description: Option<String>,
    /// The language used in the site. Defaults to "en"
    pub default_language: Option<String>,
    /// Whether to generate RSS. Defaults to false
    pub generate_rss: Option<bool>,
    /// The number of articles to include in the RSS feed. Defaults to unlimited
    pub rss_limit: Option<usize>,
    /// Whether to generate tags and individual tag pages if some pages have them. Defaults to true
    pub generate_tags_pages: Option<bool>,
    /// Whether to generate categories and individual tag categories if some pages have them. Defaults to true
    pub generate_categories_pages: Option<bool>,
    /// Whether to compile the `sass` directory and output the css files into the static folder
    pub compile_sass: Option<bool>,
    /// A list of file glob patterns to ignore when processing the content folder. Defaults to none.
    /// Had to remove the PartialEq derive because GlobSet does not implement it. No impact
    /// because it's unused anyway (who wants to sort Configs?).
    pub ignored_content: Option<Vec<String>>,
    #[serde(skip_serializing, skip_deserializing)]
    pub ignored_content_globber: Option<GlobSet>,

    /// Languages list and translated strings
    pub translations: Option<HashMap<String, Toml>>,

    /// All user params set in [extra] in the config
    pub extra: Option<HashMap<String, Toml>>,

    /// Set automatically when instantiating the config. Used for cachebusting
    pub build_timestamp: Option<i64>,
}

macro_rules! set_default {
    ($key: expr, $default: expr) => {
        if $key.is_none() {
            $key = Some($default);
        }
    }
}

impl Config {
    /// Parses a string containing TOML to our Config struct
    /// Any extra parameter will end up in the extra field
    pub fn parse(content: &str) -> Result<Config> {
        let mut config: Config = match toml::from_str(content) {
            Ok(c) => c,
            Err(e) => bail!(e)
        };

        set_default!(config.default_language, "en".to_string());
        set_default!(config.highlight_code, false);
        set_default!(config.generate_rss, false);
        set_default!(config.rss_limit, 20);
        set_default!(config.generate_tags_pages, false);
        set_default!(config.generate_categories_pages, false);
        set_default!(config.compile_sass, false);
        set_default!(config.ignored_content, Vec::new());
        set_default!(config.translations, HashMap::new());
        set_default!(config.extra, HashMap::new());

        match config.highlight_theme {
            Some(ref t) => {
                if !THEME_SET.themes.contains_key(t) {
                    bail!("Theme {} not available", t)
                }
            }
            None => config.highlight_theme = Some("base16-ocean-dark".to_string())
        };

        config.build_timestamp = Some(Utc::now().timestamp());

        // Convert the file glob strings into a compiled glob set matcher. We want to do this once,
        // at program initialization, rather than for every page, for example. We arrange for the
        // globset matcher to always exist (even though it has to be an inside an Option at the
        // moment because of the TOML serializer); if the glob set is empty the `is_match` function
        // of the globber always returns false.
        let mut glob_set_builder = GlobSetBuilder::new();

        if let Some(ref v) = config.ignored_content {
            if v.len() > 0 {
                for pat in v {
                    let glob = match Glob::new(pat) {
                        Ok(g) => g,
                        Err(e) => bail!("Invalid ignored_content glob pattern: {}, error = {}", pat, e)
                    };
                    glob_set_builder.add(glob);
                }
            }
        }
        config.ignored_content_globber = Some(glob_set_builder.build().expect("Bad ignored_content in config file."));

        Ok(config)
    }

    /// Parses a config file from the given path
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
        let mut content = String::new();
        File::open(path)
            .chain_err(|| "No `config.toml` file found. Are you in the right directory?")?
            .read_to_string(&mut content)?;

        Config::parse(&content)
    }

    /// Makes a url, taking into account that the base url might have a trailing slash
    pub fn make_permalink(&self, path: &str) -> String {
        let trailing_bit = if path.ends_with('/') || path.is_empty() { "" } else { "/" };

        // Index section with a base url that has a trailing slash
        if self.base_url.ends_with('/') && path == "/" {
            self.base_url.clone()
        } else if path == "/" {
            // index section with a base url that doesn't have a trailing slash
            format!("{}/", self.base_url)
        } else if self.base_url.ends_with('/') && path.starts_with('/') {
            format!("{}{}{}", self.base_url, &path[1..], trailing_bit)
        } else if self.base_url.ends_with('/') {
            format!("{}{}{}", self.base_url, path, trailing_bit)
        } else if path.starts_with('/') {
            format!("{}{}{}", self.base_url, path, trailing_bit)
        } else {
            format!("{}/{}{}", self.base_url, path, trailing_bit)
        }
    }

    /// Merges the extra data from the theme with the config extra data
    fn add_theme_extra(&mut self, theme: &Theme) -> Result<()> {
        if let Some(ref mut config_extra) = self.extra {
            // 3 pass merging
            // 1. save config to preserve user
            let original = config_extra.clone();
            // 2. inject theme extra values
            for (key, val) in &theme.extra {
                config_extra.entry(key.to_string()).or_insert_with(|| val.clone());
            }

            // 3. overwrite with original config
            for (key, val) in &original {
                config_extra.entry(key.to_string()).or_insert_with(|| val.clone());
            }
        }

        Ok(())
    }

    /// Parse the theme.toml file and merges the extra data from the theme
    /// with the config extra data
    pub fn merge_with_theme(&mut self, path: &PathBuf) -> Result<()> {
        let theme = Theme::from_file(path)?;
        self.add_theme_extra(&theme)
    }
}

/// Exists only for testing purposes
#[doc(hidden)]
impl Default for Config {
    fn default() -> Config {
        Config {
            title: Some("".to_string()),
            theme: None,
            base_url: "http://a-website.com/".to_string(),
            highlight_code: Some(true),
            highlight_theme: Some("base16-ocean-dark".to_string()),
            description: None,
            default_language: Some("en".to_string()),
            generate_rss: Some(false),
            rss_limit: Some(10_000),
            generate_tags_pages: Some(true),
            generate_categories_pages: Some(true),
            compile_sass: Some(false),
            ignored_content: Some(Vec::new()),
            ignored_content_globber: Some(GlobSetBuilder::new().build().unwrap()),
            translations: None,
            extra: None,
            build_timestamp: Some(1),
        }
    }
}

/// Get and parse the config.
/// If it doesn't succeed, exit
pub fn get_config(path: &Path, filename: &str) -> Config {
    match Config::from_file(path.join(filename)) {
        Ok(c) => c,
        Err(e) => {
            println!("Failed to load {}", filename);
            println!("Error: {}", e);
            ::std::process::exit(1);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Config, Theme};

    #[test]
    fn can_import_valid_config() {
        let config = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"
        "#;

        let config = Config::parse(config).unwrap();
        assert_eq!(config.title.unwrap(), "My site".to_string());
    }

    #[test]
    fn errors_when_invalid_type() {
        let config = r#"
title = 1
base_url = "https://replace-this-with-your-url.com"
        "#;

        let config = Config::parse(config);
        assert!(config.is_err());
    }

    #[test]
    fn errors_when_missing_required_field() {
        // base_url is required
        let config = r#"
title = ""
        "#;

        let config = Config::parse(config);
        assert!(config.is_err());
    }

    #[test]
    fn can_add_extra_values() {
        let config = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"

[extra]
hello = "world"
        "#;

        let config = Config::parse(config);
        assert!(config.is_ok());
        assert_eq!(config.unwrap().extra.unwrap().get("hello").unwrap().as_str().unwrap(), "world");
    }

    #[test]
    fn can_make_url_index_page_with_non_trailing_slash_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is".to_string();
        assert_eq!(config.make_permalink(""), "http://vincent.is/");
    }

    #[test]
    fn can_make_url_index_page_with_railing_slash_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is/".to_string();
        assert_eq!(config.make_permalink(""), "http://vincent.is/");
    }

    #[test]
    fn can_make_url_with_non_trailing_slash_base_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is".to_string();
        assert_eq!(config.make_permalink("hello"), "http://vincent.is/hello/");
    }

    #[test]
    fn can_make_url_with_trailing_slash_path() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is/".to_string();
        assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/");
    }

    #[test]
    fn can_make_url_with_localhost() {
        let mut config = Config::default();
        config.base_url = "http://127.0.0.1:1111".to_string();
        assert_eq!(config.make_permalink("/tags/rust"), "http://127.0.0.1:1111/tags/rust/");
    }

    #[test]
    fn can_merge_with_theme_data_and_preserve_config_value() {
        let config_str = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"

[extra]
hello = "world"
        "#;
        let mut config = Config::parse(config_str).unwrap();
        let theme_str = r#"
[extra]
hello = "foo"
a_value = 10
        "#;
        let theme = Theme::parse(theme_str).unwrap();
        assert!(config.add_theme_extra(&theme).is_ok());
        let extra = config.extra.unwrap();
        assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
        assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
    }

    #[test]
    fn can_use_language_configuration() {
        let config = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"

[translations]
[translations.fr]
title = "Un titre"

[translations.en]
title = "A title"

        "#;

        let config = Config::parse(config);
        assert!(config.is_ok());
        let translations = config.unwrap().translations.unwrap();
        assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre");
        assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title");
    }

    #[test]
    fn missing_ignored_content_results_in_empty_vector_and_empty_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
        "#;

        let config = Config::parse(config_str).unwrap();
        let v = config.ignored_content.unwrap();
        assert_eq!(v.len(), 0);
        assert!(config.ignored_content_globber.unwrap().is_empty());
    }

    #[test]
    fn empty_ignored_content_results_in_empty_vector_and_empty_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_content = []
        "#;

        let config = Config::parse(config_str).unwrap();
        assert_eq!(config.ignored_content.unwrap().len(), 0);
        assert!(config.ignored_content_globber.unwrap().is_empty());
    }

    #[test]
    fn non_empty_ignored_content_results_in_vector_of_patterns_and_configured_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_content = ["*.{graphml,iso}", "*.py?"]
        "#;

        let config = Config::parse(config_str).unwrap();
        let v = config.ignored_content.unwrap();
        assert_eq!(v, vec!["*.{graphml,iso}", "*.py?"]);

        let g = config.ignored_content_globber.unwrap();
        assert_eq!(g.len(), 2);
        assert!(g.is_match("foo.graphml"));
        assert!(g.is_match("foo.iso"));
        assert!(!g.is_match("foo.png"));
        assert!(g.is_match("foo.py2"));
        assert!(g.is_match("foo.py3"));
        assert!(!g.is_match("foo.py"));
    }
}
<commit_msg>Remove unnecessary if when compiling the glob set.<commit_after>#[macro_use]
extern crate serde_derive;
extern crate toml;
#[macro_use]
extern crate errors;
extern crate highlighting;
extern crate chrono;
extern crate globset;

use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

use toml::{Value as Toml};
use chrono::Utc;
use globset::{Glob, GlobSet, GlobSetBuilder};

use errors::{Result, ResultExt};
use highlighting::THEME_SET;

mod theme;

use theme::Theme;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
    /// Base URL of the site, the only required config argument
    pub base_url: String,

    /// Theme to use
    pub theme: Option<String>,
    /// Title of the site. Defaults to None
    pub title: Option<String>,
    /// Whether to highlight all code blocks found in markdown files. Defaults to false
    pub highlight_code: Option<bool>,
    /// Which themes to use for code highlighting. See Readme for supported themes
    pub highlight_theme: Option<String>,
    /// Description of the site
    pub description: Option<String>,
    /// The language used in the site. Defaults to "en"
    pub default_language: Option<String>,
    /// Whether to generate RSS. Defaults to false
    pub generate_rss: Option<bool>,
    /// The number of articles to include in the RSS feed. Defaults to unlimited
    pub rss_limit: Option<usize>,
    /// Whether to generate tags and individual tag pages if some pages have them. Defaults to true
    pub generate_tags_pages: Option<bool>,
    /// Whether to generate categories and individual tag categories if some pages have them. Defaults to true
    pub generate_categories_pages: Option<bool>,
    /// Whether to compile the `sass` directory and output the css files into the static folder
    pub compile_sass: Option<bool>,
    /// A list of file glob patterns to ignore when processing the content folder. Defaults to none.
    /// Had to remove the PartialEq derive because GlobSet does not implement it. No impact
    /// because it's unused anyway (who wants to sort Configs?).
    pub ignored_content: Option<Vec<String>>,
    #[serde(skip_serializing, skip_deserializing)]
    pub ignored_content_globber: Option<GlobSet>,

    /// Languages list and translated strings
    pub translations: Option<HashMap<String, Toml>>,

    /// All user params set in [extra] in the config
    pub extra: Option<HashMap<String, Toml>>,

    /// Set automatically when instantiating the config. Used for cachebusting
    pub build_timestamp: Option<i64>,
}

macro_rules! set_default {
    ($key: expr, $default: expr) => {
        if $key.is_none() {
            $key = Some($default);
        }
    }
}

impl Config {
    /// Parses a string containing TOML to our Config struct
    /// Any extra parameter will end up in the extra field
    pub fn parse(content: &str) -> Result<Config> {
        let mut config: Config = match toml::from_str(content) {
            Ok(c) => c,
            Err(e) => bail!(e)
        };

        set_default!(config.default_language, "en".to_string());
        set_default!(config.highlight_code, false);
        set_default!(config.generate_rss, false);
        set_default!(config.rss_limit, 20);
        set_default!(config.generate_tags_pages, false);
        set_default!(config.generate_categories_pages, false);
        set_default!(config.compile_sass, false);
        set_default!(config.ignored_content, Vec::new());
        set_default!(config.translations, HashMap::new());
        set_default!(config.extra, HashMap::new());

        match config.highlight_theme {
            Some(ref t) => {
                if !THEME_SET.themes.contains_key(t) {
                    bail!("Theme {} not available", t)
                }
            }
            None => config.highlight_theme = Some("base16-ocean-dark".to_string())
        };

        config.build_timestamp = Some(Utc::now().timestamp());

        // Convert the file glob strings into a compiled glob set matcher. We want to do this once,
        // at program initialization, rather than for every page, for example. We arrange for the
        // globset matcher to always exist (even though it has to be an inside an Option at the
        // moment because of the TOML serializer); if the glob set is empty the `is_match` function
        // of the globber always returns false.
        let mut glob_set_builder = GlobSetBuilder::new();

        if let Some(ref v) = config.ignored_content {
            for pat in v {
                let glob = match Glob::new(pat) {
                    Ok(g) => g,
                    Err(e) => bail!("Invalid ignored_content glob pattern: {}, error = {}", pat, e)
                };
                glob_set_builder.add(glob);
            }
        }
        config.ignored_content_globber = Some(glob_set_builder.build().expect("Bad ignored_content in config file."));

        Ok(config)
    }

    /// Parses a config file from the given path
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
        let mut content = String::new();
        File::open(path)
            .chain_err(|| "No `config.toml` file found. Are you in the right directory?")?
            .read_to_string(&mut content)?;

        Config::parse(&content)
    }

    /// Makes a url, taking into account that the base url might have a trailing slash
    pub fn make_permalink(&self, path: &str) -> String {
        let trailing_bit = if path.ends_with('/') || path.is_empty() { "" } else { "/" };

        // Index section with a base url that has a trailing slash
        if self.base_url.ends_with('/') && path == "/" {
            self.base_url.clone()
        } else if path == "/" {
            // index section with a base url that doesn't have a trailing slash
            format!("{}/", self.base_url)
        } else if self.base_url.ends_with('/') && path.starts_with('/') {
            format!("{}{}{}", self.base_url, &path[1..], trailing_bit)
        } else if self.base_url.ends_with('/') {
            format!("{}{}{}", self.base_url, path, trailing_bit)
        } else if path.starts_with('/') {
            format!("{}{}{}", self.base_url, path, trailing_bit)
        } else {
            format!("{}/{}{}", self.base_url, path, trailing_bit)
        }
    }

    /// Merges the extra data from the theme with the config extra data
    fn add_theme_extra(&mut self, theme: &Theme) -> Result<()> {
        if let Some(ref mut config_extra) = self.extra {
            // 3 pass merging
            // 1. save config to preserve user
            let original = config_extra.clone();
            // 2. inject theme extra values
            for (key, val) in &theme.extra {
                config_extra.entry(key.to_string()).or_insert_with(|| val.clone());
            }

            // 3. overwrite with original config
            for (key, val) in &original {
                config_extra.entry(key.to_string()).or_insert_with(|| val.clone());
            }
        }

        Ok(())
    }

    /// Parse the theme.toml file and merges the extra data from the theme
    /// with the config extra data
    pub fn merge_with_theme(&mut self, path: &PathBuf) -> Result<()> {
        let theme = Theme::from_file(path)?;
        self.add_theme_extra(&theme)
    }
}

/// Exists only for testing purposes
#[doc(hidden)]
impl Default for Config {
    fn default() -> Config {
        Config {
            title: Some("".to_string()),
            theme: None,
            base_url: "http://a-website.com/".to_string(),
            highlight_code: Some(true),
            highlight_theme: Some("base16-ocean-dark".to_string()),
            description: None,
            default_language: Some("en".to_string()),
            generate_rss: Some(false),
            rss_limit: Some(10_000),
            generate_tags_pages: Some(true),
            generate_categories_pages: Some(true),
            compile_sass: Some(false),
            ignored_content: Some(Vec::new()),
            ignored_content_globber: Some(GlobSetBuilder::new().build().unwrap()),
            translations: None,
            extra: None,
            build_timestamp: Some(1),
        }
    }
}

/// Get and parse the config.
/// If it doesn't succeed, exit
pub fn get_config(path: &Path, filename: &str) -> Config {
    match Config::from_file(path.join(filename)) {
        Ok(c) => c,
        Err(e) => {
            println!("Failed to load {}", filename);
            println!("Error: {}", e);
            ::std::process::exit(1);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{Config, Theme};

    #[test]
    fn can_import_valid_config() {
        let config = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"
        "#;

        let config = Config::parse(config).unwrap();
        assert_eq!(config.title.unwrap(), "My site".to_string());
    }

    #[test]
    fn errors_when_invalid_type() {
        let config = r#"
title = 1
base_url = "https://replace-this-with-your-url.com"
        "#;

        let config = Config::parse(config);
        assert!(config.is_err());
    }

    #[test]
    fn errors_when_missing_required_field() {
        // base_url is required
        let config = r#"
title = ""
        "#;

        let config = Config::parse(config);
        assert!(config.is_err());
    }

    #[test]
    fn can_add_extra_values() {
        let config = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"

[extra]
hello = "world"
        "#;

        let config = Config::parse(config);
        assert!(config.is_ok());
        assert_eq!(config.unwrap().extra.unwrap().get("hello").unwrap().as_str().unwrap(), "world");
    }

    #[test]
    fn can_make_url_index_page_with_non_trailing_slash_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is".to_string();
        assert_eq!(config.make_permalink(""), "http://vincent.is/");
    }

    #[test]
    fn can_make_url_index_page_with_railing_slash_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is/".to_string();
        assert_eq!(config.make_permalink(""), "http://vincent.is/");
    }

    #[test]
    fn can_make_url_with_non_trailing_slash_base_url() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is".to_string();
        assert_eq!(config.make_permalink("hello"), "http://vincent.is/hello/");
    }

    #[test]
    fn can_make_url_with_trailing_slash_path() {
        let mut config = Config::default();
        config.base_url = "http://vincent.is/".to_string();
        assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/");
    }

    #[test]
    fn can_make_url_with_localhost() {
        let mut config = Config::default();
        config.base_url = "http://127.0.0.1:1111".to_string();
        assert_eq!(config.make_permalink("/tags/rust"), "http://127.0.0.1:1111/tags/rust/");
    }

    #[test]
    fn can_merge_with_theme_data_and_preserve_config_value() {
        let config_str = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"

[extra]
hello = "world"
        "#;
        let mut config = Config::parse(config_str).unwrap();
        let theme_str = r#"
[extra]
hello = "foo"
a_value = 10
        "#;
        let theme = Theme::parse(theme_str).unwrap();
        assert!(config.add_theme_extra(&theme).is_ok());
        let extra = config.extra.unwrap();
        assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
        assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
    }

    #[test]
    fn can_use_language_configuration() {
        let config = r#"
base_url = "https://remplace-par-ton-url.fr"
default_language = "fr"

[translations]
[translations.fr]
title = "Un titre"

[translations.en]
title = "A title"

        "#;

        let config = Config::parse(config);
        assert!(config.is_ok());
        let translations = config.unwrap().translations.unwrap();
        assert_eq!(translations["fr"]["title"].as_str().unwrap(), "Un titre");
        assert_eq!(translations["en"]["title"].as_str().unwrap(), "A title");
    }

    #[test]
    fn missing_ignored_content_results_in_empty_vector_and_empty_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
        "#;

        let config = Config::parse(config_str).unwrap();
        let v = config.ignored_content.unwrap();
        assert_eq!(v.len(), 0);
        assert!(config.ignored_content_globber.unwrap().is_empty());
    }

    #[test]
    fn empty_ignored_content_results_in_empty_vector_and_empty_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_content = []
        "#;

        let config = Config::parse(config_str).unwrap();
        assert_eq!(config.ignored_content.unwrap().len(), 0);
        assert!(config.ignored_content_globber.unwrap().is_empty());
    }

    #[test]
    fn non_empty_ignored_content_results_in_vector_of_patterns_and_configured_globber() {
        let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_content = ["*.{graphml,iso}", "*.py?"]
        "#;

        let config = Config::parse(config_str).unwrap();
        let v = config.ignored_content.unwrap();
        assert_eq!(v, vec!["*.{graphml,iso}", "*.py?"]);

        let g = config.ignored_content_globber.unwrap();
        assert_eq!(g.len(), 2);
        assert!(g.is_match("foo.graphml"));
        assert!(g.is_match("foo.iso"));
        assert!(!g.is_match("foo.png"));
        assert!(g.is_match("foo.py2"));
        assert!(g.is_match("foo.py3"));
        assert!(!g.is_match("foo.py"));
    }
}
<|endoftext|>


<commit_before>#![crate_type = "bin"]
#![feature(collections)]

extern crate gtk;
extern crate glib;
extern crate sysinfo;

use gtk::{WindowTrait, ContainerTrait, WidgetTrait, ButtonSignals, BoxTrait};
use gtk::{Orientation};
use sysinfo::*;
use gtk::signal::Inhibit;
use gtk::signal::{WidgetSignals, TreeViewSignals};
use std::collections::VecMap;
use std::str::FromStr;
use std::rc::Rc;
use std::cell::{RefCell};

struct NoteBook {
    notebook: gtk::NoteBook,
    tabs: Vec<gtk::Box>
}

impl NoteBook {
    fn new() -> NoteBook {
        NoteBook {
            notebook: gtk::NoteBook::new().unwrap(),
            tabs: Vec::new()
        }
    }

    fn create_tab<'a, Widget: gtk::WidgetTrait>(&mut self, title: &'a str, widget: &Widget) -> Option<u32> {
        let label = gtk::Label::new(title).unwrap();
        let tab = gtk::Box::new(Orientation::Horizontal, 0).unwrap();

        tab.pack_start(&label, false, false, 0);
        tab.show_all();

        let index = match self.notebook.append_page(widget, Some(&tab)) {
            Some(index) => index,
            _ => return None
        };

        self.tabs.push(tab);

        Some(index)
    }
}

struct Procs {
    left_tree: gtk::TreeView,
    scroll: gtk::ScrolledWindow,
    current_pid: Rc<RefCell<Option<i64>>>,
    kill_button: Rc<RefCell<gtk::Button>>,
    vertical_layout: gtk::Box,
    list_store: gtk::ListStore
}

impl Procs {
    pub fn new<'a>(proc_list: &VecMap<Process>) -> Procs {
        let left_tree = gtk::TreeView::new().unwrap();
        let scroll = gtk::ScrolledWindow::new(None, None).unwrap();
        let current_pid = Rc::new(RefCell::new(None));
        let kill_button = Rc::new(RefCell::new(gtk::Button::new_with_label("End task").unwrap()));
        let current_pid1 = current_pid.clone();
        let current_pid2 = current_pid.clone();
        let kill_button1 = kill_button.clone();

        scroll.set_min_content_height(800);
        scroll.set_min_content_width(600);

        let mut columns : Vec<gtk::TreeViewColumn> = Vec::new();

        append_column("pid", &mut columns);
        append_column("process name", &mut columns);
        append_column("cpu usage", &mut columns);
        append_column("memory usage (in KB)", &mut columns);

        for i in columns {
            left_tree.append_column(&i);
        }

        let mut list_store = gtk::ListStore::new(&[glib::Type::ISize, glib::Type::String, glib::Type::String, glib::Type::USize]).unwrap();
        for (_, pro) in proc_list {
            create_and_fill_model(&mut list_store, pro.pid, &pro.name, pro.cpu_usage, pro.memory);
        }

        left_tree.set_model(&list_store.get_model().unwrap());
        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();

        left_tree.connect_cursor_changed(move |tree_view| {
            match tree_view.get_selection() {
                Some(selection) => {
                    let model = tree_view.get_model().unwrap();
                    let mut iter = gtk::TreeIter::new();

                    if selection.get_selected(&model, &mut iter) {
                        let pid = Some(model.get_value(&iter, 0).get_long());
                        let mut tmp = current_pid1.borrow_mut();
                        *tmp = pid;
                    }
                    (*kill_button1.borrow_mut()).set_sensitive((*current_pid.borrow()).is_some());
                }
                None => {
                    let mut tmp = current_pid1.borrow_mut();
                    *tmp = None;
                }
            }
        });
        (*kill_button.borrow_mut()).set_sensitive(false);

        vertical_layout.add(&scroll);
        vertical_layout.add(&(*kill_button.borrow_mut()));
        Procs {
            left_tree: left_tree,
            scroll: scroll,
            current_pid: current_pid2.clone(),
            kill_button: kill_button,
            vertical_layout: vertical_layout,
            list_store: list_store
        }
    }
}

fn append_column(title: &str, v: &mut Vec<gtk::TreeViewColumn>) {
    let l = v.len();
    let renderer = gtk::CellRendererText::new().unwrap();

    v.push(gtk::TreeViewColumn::new().unwrap());
    let tmp = v.get_mut(l).unwrap();

    tmp.set_title(title);
    tmp.pack_start(&renderer, true);
    tmp.add_attribute(&renderer, "text", l as i32);
}

fn create_and_fill_model(list_store: &mut gtk::ListStore, pid: i64, name: &str, cpu: f32, memory: u64) {
    if name.len() < 1 {
        return;
    }
    let mut top_level = gtk::TreeIter::new();

    let mut val1 = glib::Value::new();
    val1.init(glib::Type::ISize);
    val1.set_long(pid);
    let mut val2 = glib::Value::new();
    val2.init(glib::Type::USize);
    val2.set_ulong(memory);
    list_store.append(&mut top_level);
    list_store.set_value(&top_level, 0, &val1);
    list_store.set_string(&top_level, 1, name);
    list_store.set_string(&top_level, 2, &format!("{:.1}", cpu));
    list_store.set_value(&top_level, 3, &val2);
}

fn update_window(w: &mut (&mut gtk::ListStore, Rc<RefCell<sysinfo::System>>, Rc<RefCell<DisplaySysInfo>>)) -> i32 {
    let model = w.0.get_model().unwrap();
    let mut iter = gtk::TreeIter::new();

    (*w.1.borrow_mut()).refresh_all();
    let mut entries : VecMap<Process> = ((*w.1.borrow()).get_process_list()).clone();
    let mut nb = model.iter_n_children(None);

    let sys = w.1.clone();
    (*w.2.borrow_mut()).update_ram_display(sys.clone());
    (*w.2.borrow_mut()).update_process_display(sys);

    let mut i = 0;
    while i < nb {
        if model.iter_nth_child(&mut iter, None, i) {
            let pid : i64 = model.get_value(&iter, 0).get_long();
            let mut to_delete = false;

            match entries.get(&(pid as usize)) {
                Some(p) => {
                    let mut val2 = glib::Value::new();
                    val2.init(glib::Type::USize);
                    val2.set_ulong(p.memory);
                    w.0.set_string(&iter, 2, &format!("{:.1}", p.cpu_usage));
                    w.0.set_value(&iter, 3, &val2);
                    to_delete = true;
                }
                None => {
                    w.0.remove(&iter);
                }
            }
            if to_delete {
                entries.remove(&(pid as usize));
                nb = model.iter_n_children(None);
                i += 1;
            }
        } else {
            i += 1;
        }
    }
    for (_, pro) in entries {
        create_and_fill_model(&mut w.0, pro.pid, &pro.name, pro.cpu_usage, pro.memory);
    }
    1
}

struct DisplaySysInfo {
    procs : Rc<RefCell<Vec<gtk::ProgressBar>>>,
    ram : Rc<RefCell<gtk::ProgressBar>>,
    swap : Rc<RefCell<gtk::ProgressBar>>,
    vertical_layout : gtk::Box
}

impl DisplaySysInfo {
    pub fn new(sys1: Rc<RefCell<sysinfo::System>>) -> DisplaySysInfo {
        let mut tmp = DisplaySysInfo {
            procs: Rc::new(RefCell::new(Vec::new())),
            ram: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            swap: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            vertical_layout: gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap()
        };

        let sys2 = sys1.clone();
        let v = tmp.procs.clone();

        (*tmp.ram.borrow_mut()).set_show_text(true);
        (*tmp.swap.borrow_mut()).set_show_text(true);

        tmp.vertical_layout.set_spacing(5);
        let mut i = 0;
        let mut total = false;

        tmp.vertical_layout.pack_start(&gtk::Label::new("Memory usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.ram.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Swap usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.swap.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Total CPU usage").unwrap(), false, false, 15);
        for pro in (*sys1.borrow()).get_processor_list() {
            if total {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];
                let l = gtk::Label::new(&format!("{}", i)).unwrap();
                let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);
                horizontal_layout.pack_start(&l, false, false, 5);
                horizontal_layout.pack_start(p, true, true, 5);
                tmp.vertical_layout.add(&horizontal_layout);
            } else {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);

                tmp.vertical_layout.add(p);
                tmp.vertical_layout.pack_start(&gtk::Label::new("Process usage").unwrap(), false, false, 15);
                total = true;
            }
            i += 1;
        }
        tmp.update_ram_display(sys2);
        tmp
    }

    pub fn update_ram_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let total = (*sys.borrow()).get_total_memory();
        let used = (*sys.borrow()).get_used_memory();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.ram.borrow_mut()).set_text(&disp);
        (*self.ram.borrow_mut()).set_fraction(used as f64 / total as f64);

        let total = (*sys.borrow()).get_total_swap();
        let used = total - (*sys.borrow()).get_used_swap();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.swap.borrow_mut()).set_text(&disp);
        (*self.swap.borrow_mut()).set_fraction(used as f64 / total as f64);
    }

    pub fn update_process_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let v = &*self.procs.borrow_mut();
        let mut i = 0;

        for pro in (*sys.borrow()).get_processor_list() {
            v[i].set_text(&format!("{:.1} %", pro.get_cpu_usage() * 100.));
            v[i].set_show_text(true);
            v[i].set_fraction(pro.get_cpu_usage() as f64);
            i += 1;
        }
    }
}

fn main() {
    gtk::init();

    let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    let sys : Rc<RefCell<sysinfo::System>> = Rc::new(RefCell::new(sysinfo::System::new()));
    let mut note = NoteBook::new();
    let mut procs = Procs::new((*sys.borrow()).get_process_list());
    let current_pid2 = procs.current_pid.clone();
    let sys1 = sys.clone();
    let sys2 = sys.clone();

    window.set_title("Process viewer");
    window.set_window_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });

    (*sys.borrow_mut()).refresh_all();
    (*procs.kill_button.borrow_mut()).connect_clicked(move |_| {
        let tmp = (*current_pid2.borrow_mut()).is_some() ;

        if tmp {
            let s = (*current_pid2.borrow()).clone();
            match (*sys.borrow_mut()).get_process(s.unwrap()) {
                Some(p) => {
                    p.kill(Signal::Kill);
                },
                None => {}
            };
        }
    });

    let display_tab = Rc::new(RefCell::new(DisplaySysInfo::new(sys2)));

    glib::timeout::add(1500, update_window, &mut (&mut procs.list_store, sys1.clone(), display_tab.clone()));
    //window.add(&vertical_layout);
    note.create_tab("Process list", &procs.vertical_layout);
    //let t = gtk::Button::new_with_label("test").unwrap();
    note.create_tab("System usage", &(*display_tab.borrow()).vertical_layout);
    window.add(&note.notebook);
    window.show_all();
    gtk::main();
}
<commit_msg>Update to last rustc version<commit_after>#![crate_type = "bin"]

extern crate gtk;
extern crate glib;
extern crate sysinfo;

use gtk::{WindowTrait, ContainerTrait, WidgetTrait, ButtonSignals, BoxTrait};
use gtk::{Orientation};
use sysinfo::*;
use gtk::signal::Inhibit;
use gtk::signal::{WidgetSignals, TreeViewSignals};
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::{RefCell};

struct NoteBook {
    notebook: gtk::NoteBook,
    tabs: Vec<gtk::Box>
}

impl NoteBook {
    fn new() -> NoteBook {
        NoteBook {
            notebook: gtk::NoteBook::new().unwrap(),
            tabs: Vec::new()
        }
    }

    fn create_tab<'a, Widget: gtk::WidgetTrait>(&mut self, title: &'a str, widget: &Widget) -> Option<u32> {
        let label = gtk::Label::new(title).unwrap();
        let tab = gtk::Box::new(Orientation::Horizontal, 0).unwrap();

        tab.pack_start(&label, false, false, 0);
        tab.show_all();

        let index = match self.notebook.append_page(widget, Some(&tab)) {
            Some(index) => index,
            _ => return None
        };

        self.tabs.push(tab);

        Some(index)
    }
}

struct Procs {
    left_tree: gtk::TreeView,
    scroll: gtk::ScrolledWindow,
    current_pid: Rc<RefCell<Option<i64>>>,
    kill_button: Rc<RefCell<gtk::Button>>,
    vertical_layout: gtk::Box,
    list_store: gtk::ListStore
}

impl Procs {
    pub fn new<'a>(proc_list: &HashMap<usize, Process>) -> Procs {
        let left_tree = gtk::TreeView::new().unwrap();
        let scroll = gtk::ScrolledWindow::new(None, None).unwrap();
        let current_pid = Rc::new(RefCell::new(None));
        let kill_button = Rc::new(RefCell::new(gtk::Button::new_with_label("End task").unwrap()));
        let current_pid1 = current_pid.clone();
        let current_pid2 = current_pid.clone();
        let kill_button1 = kill_button.clone();

        scroll.set_min_content_height(800);
        scroll.set_min_content_width(600);

        let mut columns : Vec<gtk::TreeViewColumn> = Vec::new();

        append_column("pid", &mut columns);
        append_column("process name", &mut columns);
        append_column("cpu usage", &mut columns);
        append_column("memory usage (in KB)", &mut columns);

        for i in columns {
            left_tree.append_column(&i);
        }

        let mut list_store = gtk::ListStore::new(&[glib::Type::ISize, glib::Type::String, glib::Type::String, glib::Type::USize]).unwrap();
        for (_, pro) in proc_list {
            create_and_fill_model(&mut list_store, pro.pid, &pro.name, pro.cpu_usage, pro.memory);
        }

        left_tree.set_model(&list_store.get_model().unwrap());
        left_tree.set_headers_visible(true);
        scroll.add(&left_tree);
        let vertical_layout = gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap();

        left_tree.connect_cursor_changed(move |tree_view| {
            match tree_view.get_selection() {
                Some(selection) => {
                    let model = tree_view.get_model().unwrap();
                    let mut iter = gtk::TreeIter::new();

                    if selection.get_selected(&model, &mut iter) {
                        let pid = Some(model.get_value(&iter, 0).get_long());
                        let mut tmp = current_pid1.borrow_mut();
                        *tmp = pid;
                    }
                    (*kill_button1.borrow_mut()).set_sensitive((*current_pid.borrow()).is_some());
                }
                None => {
                    let mut tmp = current_pid1.borrow_mut();
                    *tmp = None;
                }
            }
        });
        (*kill_button.borrow_mut()).set_sensitive(false);

        vertical_layout.add(&scroll);
        vertical_layout.add(&(*kill_button.borrow_mut()));
        Procs {
            left_tree: left_tree,
            scroll: scroll,
            current_pid: current_pid2.clone(),
            kill_button: kill_button,
            vertical_layout: vertical_layout,
            list_store: list_store
        }
    }
}

fn append_column(title: &str, v: &mut Vec<gtk::TreeViewColumn>) {
    let l = v.len();
    let renderer = gtk::CellRendererText::new().unwrap();

    v.push(gtk::TreeViewColumn::new().unwrap());
    let tmp = v.get_mut(l).unwrap();

    tmp.set_title(title);
    tmp.pack_start(&renderer, true);
    tmp.add_attribute(&renderer, "text", l as i32);
}

fn create_and_fill_model(list_store: &mut gtk::ListStore, pid: i64, name: &str, cpu: f32, memory: u64) {
    if name.len() < 1 {
        return;
    }
    let mut top_level = gtk::TreeIter::new();

    let mut val1 = glib::Value::new();
    val1.init(glib::Type::ISize);
    val1.set_long(pid);
    let mut val2 = glib::Value::new();
    val2.init(glib::Type::USize);
    val2.set_ulong(memory);
    list_store.append(&mut top_level);
    list_store.set_value(&top_level, 0, &val1);
    list_store.set_string(&top_level, 1, name);
    list_store.set_string(&top_level, 2, &format!("{:.1}", cpu));
    list_store.set_value(&top_level, 3, &val2);
}

fn update_window(list: &mut gtk::ListStore, system: Rc<RefCell<sysinfo::System>>, info: Rc<RefCell<DisplaySysInfo>>) {
    let model = list.get_model().unwrap();
    let mut iter = gtk::TreeIter::new();

    (*system.borrow_mut()).refresh_all();
    let mut entries : HashMap<usize, Process> = ((*system.borrow()).get_process_list()).clone();
    let mut nb = model.iter_n_children(None);

    let sys = system.clone();
    (*info.borrow_mut()).update_ram_display(sys.clone());
    (*info.borrow_mut()).update_process_display(sys);

    let mut i = 0;
    while i < nb {
        if model.iter_nth_child(&mut iter, None, i) {
            let pid : i64 = model.get_value(&iter, 0).get_long();
            let mut to_delete = false;

            match entries.get(&(pid as usize)) {
                Some(p) => {
                    let mut val2 = glib::Value::new();
                    val2.init(glib::Type::USize);
                    val2.set_ulong(p.memory);
                    list.set_string(&iter, 2, &format!("{:.1}", p.cpu_usage));
                    list.set_value(&iter, 3, &val2);
                    to_delete = true;
                }
                None => {
                    list.remove(&iter);
                }
            }
            if to_delete {
                entries.remove(&(pid as usize));
                nb = model.iter_n_children(None);
                i += 1;
            }
        } else {
            i += 1;
        }
    }
    for (_, pro) in entries {
        create_and_fill_model(list, pro.pid, &pro.name, pro.cpu_usage, pro.memory);
    }
}

struct DisplaySysInfo {
    procs : Rc<RefCell<Vec<gtk::ProgressBar>>>,
    ram : Rc<RefCell<gtk::ProgressBar>>,
    swap : Rc<RefCell<gtk::ProgressBar>>,
    vertical_layout : gtk::Box
}

impl DisplaySysInfo {
    pub fn new(sys1: Rc<RefCell<sysinfo::System>>) -> DisplaySysInfo {
        let mut tmp = DisplaySysInfo {
            procs: Rc::new(RefCell::new(Vec::new())),
            ram: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            swap: Rc::new(RefCell::new(gtk::ProgressBar::new().unwrap())),
            vertical_layout: gtk::Box::new(gtk::Orientation::Vertical, 0).unwrap()
        };

        let sys2 = sys1.clone();
        let v = tmp.procs.clone();

        (*tmp.ram.borrow_mut()).set_show_text(true);
        (*tmp.swap.borrow_mut()).set_show_text(true);

        tmp.vertical_layout.set_spacing(5);
        let mut i = 0;
        let mut total = false;

        tmp.vertical_layout.pack_start(&gtk::Label::new("Memory usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.ram.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Swap usage").unwrap(), false, false, 15);
        tmp.vertical_layout.add(&(*tmp.swap.borrow()));
        tmp.vertical_layout.pack_start(&gtk::Label::new("Total CPU usage").unwrap(), false, false, 15);
        for pro in (*sys1.borrow()).get_processor_list() {
            if total {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];
                let l = gtk::Label::new(&format!("{}", i)).unwrap();
                let horizontal_layout = gtk::Box::new(gtk::Orientation::Horizontal, 0).unwrap();

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);
                horizontal_layout.pack_start(&l, false, false, 5);
                horizontal_layout.pack_start(p, true, true, 5);
                tmp.vertical_layout.add(&horizontal_layout);
            } else {
                (*v.borrow_mut()).push(gtk::ProgressBar::new().unwrap());
                let p : &gtk::ProgressBar = &(*v.borrow())[i];

                p.set_text(&format!("{:.2} %", pro.get_cpu_usage() * 100.));
                p.set_show_text(true);
                p.set_fraction(pro.get_cpu_usage() as f64);

                tmp.vertical_layout.add(p);
                tmp.vertical_layout.pack_start(&gtk::Label::new("Process usage").unwrap(), false, false, 15);
                total = true;
            }
            i += 1;
        }
        tmp.update_ram_display(sys2);
        tmp
    }

    pub fn update_ram_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let total = (*sys.borrow()).get_total_memory();
        let used = (*sys.borrow()).get_used_memory();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.ram.borrow_mut()).set_text(&disp);
        (*self.ram.borrow_mut()).set_fraction(used as f64 / total as f64);

        let total = (*sys.borrow()).get_total_swap();
        let used = total - (*sys.borrow()).get_used_swap();
        let disp = if total < 100000 {
            format!("{} / {}KB", used, total)
        } else if total < 10000000 {
            format!("{} / {}MB", used / 1000, total / 1000)
        } else if total < 10000000000 {
            format!("{} / {}GB", used / 1000000, total / 1000000)
        } else {
            format!("{} / {}TB", used / 1000000000, total / 1000000000)
        };

        (*self.swap.borrow_mut()).set_text(&disp);
        (*self.swap.borrow_mut()).set_fraction(used as f64 / total as f64);
    }

    pub fn update_process_display(&mut self, sys: Rc<RefCell<sysinfo::System>>) {
        let v = &*self.procs.borrow_mut();
        let mut i = 0;

        for pro in (*sys.borrow()).get_processor_list() {
            v[i].set_text(&format!("{:.1} %", pro.get_cpu_usage() * 100.));
            v[i].set_show_text(true);
            v[i].set_fraction(pro.get_cpu_usage() as f64);
            i += 1;
        }
    }
}

fn main() {
    gtk::init();

    let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
    let sys : Rc<RefCell<sysinfo::System>> = Rc::new(RefCell::new(sysinfo::System::new()));
    let mut note = NoteBook::new();
    let mut procs = Procs::new((*sys.borrow()).get_process_list());
    let current_pid2 = procs.current_pid.clone();
    let sys1 = sys.clone();
    let sys2 = sys.clone();

    window.set_title("Process viewer");
    window.set_window_position(gtk::WindowPosition::Center);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(true)
    });

    (*sys.borrow_mut()).refresh_all();
    (*procs.kill_button.borrow_mut()).connect_clicked(move |_| {
        let tmp = (*current_pid2.borrow_mut()).is_some() ;

        if tmp {
            let s = (*current_pid2.borrow()).clone();
            match (*sys.borrow_mut()).get_process(s.unwrap()) {
                Some(p) => {
                    p.kill(Signal::Kill);
                },
                None => {}
            };
        }
    });

    let display_tab = Rc::new(RefCell::new(DisplaySysInfo::new(sys2)));

    //window.add(&vertical_layout);
    note.create_tab("Process list", &procs.vertical_layout);
    //let t = gtk::Button::new_with_label("test").unwrap();
    note.create_tab("System usage", &(*display_tab.borrow()).vertical_layout);

    glib::timeout_add(1500, move || {
        update_window(&mut procs.list_store, sys1.clone(), display_tab.clone());
        glib::Continue(true)
    });

    window.add(&note.notebook);
    window.show_all();
    gtk::main();
}
<|endoftext|>


<commit_before>extern crate curl;
use std::sync::Arc;
use mio::*;
use mio::tcp::{TcpStream, TcpSocket};
use mio::buf::ByteBuf;
use std::net::SocketAddr;
use std::net::lookup_host;
use std::collections::VecMap;
use url::Url;
use curl::http;
use eventual;

#[derive(Debug, Clone)]
pub enum HttpAction {
    Get(Arc<Url>),
}

pub struct ClientInfo {
    tcp_stream: TcpStream,
    action: HttpAction,
    complete: eventual::Complete<Box<Vec<u8>>, &'static str>,
    mut_buf: Vec<u8>
}

impl ClientInfo {

    pub fn complete(self) {
        self.complete.complete(Box::new(self.mut_buf));
    }
}

pub struct Echo {
    client_info: VecMap<ClientInfo>,
}

impl Echo {
    pub fn new() -> Echo {
        Echo {
            client_info: VecMap::new(),
        }
    }
}

impl Handler for Echo {
    type Timeout = usize;
    type Message = (String, eventual::Complete<Box<Vec<u8>>, &'static str>);

    fn readable(&mut self, event_loop: &mut EventLoop<Echo>, token: Token, hint: ReadHint) {
            let mut closed = false;

        if !hint.is_hup() {
            let mut buf = ByteBuf::mut_with_capacity(4096 * 16);
            let mut client_info = self.client_info.get_mut(&token.as_usize()).unwrap();
            match client_info.tcp_stream.try_read_buf(&mut buf).unwrap() {
                Some(r) => {
                    if r > 0 {
                        client_info.mut_buf.push_all(buf.flip().bytes());

                        event_loop.reregister(&client_info.tcp_stream, token,
                                              Interest::readable(),
                                              PollOpt::edge() | PollOpt::oneshot()).unwrap();
                    } else {
                        event_loop.deregister(&client_info.tcp_stream).unwrap();
                        closed = true;
                    }
                }
                _ => {
                    panic!("err!");
                }
            }

        } else {
        	            let client_info = self.client_info.remove(&token.as_usize()).unwrap();

 event_loop.deregister(&client_info.tcp_stream).unwrap();
                        closed = true;
                                }
        if closed {
                   self.client_info.remove(&token.as_usize()).unwrap().complete();
                   }

    }

    fn writable(&mut self, event_loop: &mut EventLoop<Echo>, token: Token) {
        let client_info = self.client_info.get_mut(&token.as_usize()).unwrap();
        let get_command: String = body(client_info.action.clone());
        match client_info.action {
            HttpAction::Get(_) => {
                let mut buf = ByteBuf::from_slice(get_command.as_bytes());
                match client_info.tcp_stream.try_write_buf(&mut buf) {
                    Ok(None) => {
                        panic!("client flushing buf; WOULDBLOCK");
                        //   self.buf = Some(buf);
                    }
                    Ok(Some(_)) => {}
                    Err(e) => panic!("not implemented; client err={:?}", e),
                }

                event_loop.reregister(&client_info.tcp_stream, token, Interest::readable(),
                                      PollOpt::edge() | PollOpt::oneshot()).unwrap();
            }
        }
    }
    fn notify(&mut self,
              event_loop: &mut EventLoop<Echo>,
              tuple: (String, eventual::Complete<Box<Vec<u8>>, &'static str>)) {
        let token = Token(self.client_info.len() + 1);

        let action = get_action(tuple.0);
        match action.clone() {
            HttpAction::Get(url_p) => {
                let url: Url = (*url_p).clone();
                let ip = lookup_host(url.domain().unwrap()).unwrap().next().unwrap().unwrap();
                let port = url.port_or_default().unwrap();
                let address = SocketAddr::new(ip.ip(), port);
                let (sock, _) = TcpSocket::v4().unwrap().connect(&address).unwrap();
                let client_info = ClientInfo {
                    tcp_stream: sock,
                    action: action,
                    complete: tuple.1,
                    mut_buf: Vec::new()
                };

                event_loop.register_opt(&client_info.tcp_stream, token, Interest::writable(),
                                        PollOpt::edge() | PollOpt::oneshot()).unwrap();
                self.client_info.insert(token.as_usize(), client_info);
            }
        }
    }
}

fn body(action: HttpAction) -> String {
    match action {
        HttpAction::Get(ref resource) => {
            format!(
                "GET {}  HTTP/1.1\r\nHost: {}\r\nUser-Agent: curl/7.37.1\r\nAccept */*\r\nConnection: close \r\n\r\n",
                resource.serialize_path().unwrap(), resource.domain().unwrap())
        }
    }
}
fn get_action(url_s: String) -> HttpAction {
    let htt = http::multi_handle();
    let url = Url::parse(url_s.as_str()).unwrap();
    HttpAction::Get(Arc::new(url))
}
<commit_msg>Align<commit_after>extern crate curl;
use std::sync::Arc;
use mio::*;
use mio::tcp::{TcpStream, TcpSocket};
use mio::buf::ByteBuf;
use std::net::SocketAddr;
use std::net::lookup_host;
use std::collections::VecMap;
use url::Url;
use curl::http;
use eventual;

#[derive(Debug, Clone)]
pub enum HttpAction {
    Get(Arc<Url>),
}

pub struct ClientInfo {
    tcp_stream: TcpStream,
    action: HttpAction,
    complete: eventual::Complete<Box<Vec<u8>>, &'static str>,
    mut_buf: Vec<u8>
}

impl ClientInfo {

    pub fn complete(self) {
        self.complete.complete(Box::new(self.mut_buf));
    }
}

pub struct Echo {
    client_info: VecMap<ClientInfo>,
}

impl Echo {
    pub fn new() -> Echo {
        Echo {
            client_info: VecMap::new(),
        }
    }
}

impl Handler for Echo {
    type Timeout = usize;
    type Message = (String, eventual::Complete<Box<Vec<u8>>, &'static str>);

    fn readable(&mut self, event_loop: &mut EventLoop<Echo>, token: Token, hint: ReadHint) {
        let mut closed = false;

        if !hint.is_hup() {
            let mut buf = ByteBuf::mut_with_capacity(4096 * 16);
            let mut client_info = self.client_info.get_mut(&token.as_usize()).unwrap();
            match client_info.tcp_stream.try_read_buf(&mut buf).unwrap() {
                Some(r) => {
                    if r > 0 {
                        client_info.mut_buf.push_all(buf.flip().bytes());

                        event_loop.reregister(&client_info.tcp_stream, token, Interest::readable(),
                                              PollOpt::edge() | PollOpt::oneshot()).unwrap();
                    } else {
                        event_loop.deregister(&client_info.tcp_stream).unwrap();
                        closed = true;
                    }
                }
                _ => {
                    panic!("err!");
                }
            }

        } else {
            let client_info = self.client_info.remove(&token.as_usize()).unwrap();

            event_loop.deregister(&client_info.tcp_stream).unwrap();
            closed = true;
        }
        if closed {
            self.client_info.remove(&token.as_usize()).unwrap().complete();
        }
    }

    fn writable(&mut self, event_loop: &mut EventLoop<Echo>, token: Token) {
        let client_info = self.client_info.get_mut(&token.as_usize()).unwrap();
        let get_command: String = body(client_info.action.clone());
        match client_info.action {
            HttpAction::Get(_) => {
                let mut buf = ByteBuf::from_slice(get_command.as_bytes());
                match client_info.tcp_stream.try_write_buf(&mut buf) {
                    Ok(None) => {
                        panic!("client flushing buf; WOULDBLOCK");
                        //   self.buf = Some(buf);
                    }
                    Ok(Some(_)) => {}
                    Err(e) => panic!("not implemented; client err={:?}", e),
                }

                event_loop.reregister(&client_info.tcp_stream, token, Interest::readable(),
                                      PollOpt::edge() | PollOpt::oneshot()).unwrap();
            }
        }
    }
    fn notify(&mut self,
              event_loop: &mut EventLoop<Echo>,
              tuple: (String, eventual::Complete<Box<Vec<u8>>, &'static str>)) {
        let token = Token(self.client_info.len() + 1);

        let action = get_action(tuple.0);
        match action.clone() {
            HttpAction::Get(url_p) => {
                let url: Url = (*url_p).clone();
                let ip = lookup_host(url.domain().unwrap()).unwrap().next().unwrap().unwrap();
                let port = url.port_or_default().unwrap();
                let address = SocketAddr::new(ip.ip(), port);
                let (sock, _) = TcpSocket::v4().unwrap().connect(&address).unwrap();
                let client_info = ClientInfo {
                    tcp_stream: sock,
                    action: action,
                    complete: tuple.1,
                    mut_buf: Vec::new()
                };

                event_loop.register_opt(&client_info.tcp_stream, token, Interest::writable(),
                                        PollOpt::edge() | PollOpt::oneshot()).unwrap();
                self.client_info.insert(token.as_usize(), client_info);
            }
        }
    }
}

fn body(action: HttpAction) -> String {
    match action {
        HttpAction::Get(ref resource) => {
            format!(
                "GET {}  HTTP/1.1\r\nHost: {}\r\nUser-Agent: curl/7.37.1\r\nAccept */*\r\nConnection: close \r\n\r\n",
                resource.serialize_path().unwrap(), resource.domain().unwrap())
        }
    }
}
fn get_action(url_s: String) -> HttpAction {
    let htt = http::multi_handle();
    let url = Url::parse(url_s.as_str()).unwrap();
    HttpAction::Get(Arc::new(url))
}
<|endoftext|>


<commit_before>use calx::{V2, Rect};

pub static SCREEN_W: int = 640;
pub static SCREEN_H: int = 360;

/// Visual terrain cell width.
pub static CELL_W: int = 32;

/// Draw layer for floor tiles.
pub static FLOOR_Z: f32 = 0.500f32;
/// Draw layer for wall and object tiles.
pub static BLOCK_Z: f32 = 0.400f32;

/// Transform from chart space (unit is one map cell) to view space (unit is
/// one pixel).
pub fn chart_to_view(chart_pos: V2<int>) -> V2<int> {
    V2(chart_pos.0 * CELL_W / 2 - chart_pos.1 * CELL_W / 2,
       chart_pos.0 * CELL_W / 4 + chart_pos.1 * CELL_W / 4)
}

/// Transform from view space (unit is one pixel) to chart space (unit is one
/// map cell).
pub fn view_to_chart(view_pos: V2<int>) -> V2<int> {
    let c = CELL_W as f32 / 4.0;
    let column = ((view_pos.0 as f32 + c) / (c * 2.0)).floor();
    let row = ((view_pos.1 as f32 - column * c) / (c * 2.0)).floor();
    V2((column + row) as int, row as int)
}

/// Return the chart positions for which chart_to_view is inside view_rect.
pub fn cells_in_view_rect(view_rect: Rect<int>) -> Vec<V2<int>> {
    // Add CELL_W to bottom so that the tops of lower tiles will get drawn.
    let (min, max) = (view_rect.mn(), view_rect.mx() + V2(0, CELL_W));
    let mut cells = Rect(view_to_chart(min), V2(0, 0));
    cells.grow(view_to_chart(min + V2((view_rect.1).0, 0)));
    cells.grow(view_to_chart(min + V2(0, (view_rect.1).1 + CELL_W)));
    cells.grow(view_to_chart(max));

    // XXX: Adds some points that are outside the view rectangle.
    let mut ret = vec![];
    for y in range((cells.0).1, cells.mx().1 + 1) {
        for x in range((cells.0).0, cells.mx().0 + 1) {
            ret.push(V2(x, y));
        }
    }

    ret
}

pub fn cells_on_screen() -> Vec<V2<int>> {
    cells_in_view_rect(Rect(V2(-SCREEN_W / 2, -SCREEN_H / 2), V2(SCREEN_W, SCREEN_H)))
}
<commit_msg>constant fix<commit_after>use calx::{V2, Rect};

pub static SCREEN_W: int = 640;
pub static SCREEN_H: int = 360;

/// Useful general constant for cell dimension ops.
static PIXEL_UNIT: int = 16;

/// Draw layer for floor tiles.
pub static FLOOR_Z: f32 = 0.500f32;
/// Draw layer for wall and object tiles.
pub static BLOCK_Z: f32 = 0.400f32;

/// Transform from chart space (unit is one map cell) to view space (unit is
/// one pixel).
pub fn chart_to_view(chart_pos: V2<int>) -> V2<int> {
    V2(chart_pos.0 * PIXEL_UNIT - chart_pos.1 * PIXEL_UNIT,
       chart_pos.0 * PIXEL_UNIT / 2 + chart_pos.1 * PIXEL_UNIT / 2)
}

/// Transform from view space (unit is one pixel) to chart space (unit is one
/// map cell).
pub fn view_to_chart(view_pos: V2<int>) -> V2<int> {
    let c = PIXEL_UNIT as f32 / 2.0;
    let column = ((view_pos.0 as f32 + c) / (c * 2.0)).floor();
    let row = ((view_pos.1 as f32 - column * c) / (c * 2.0)).floor();
    V2((column + row) as int, row as int)
}

/// Return the chart positions for which chart_to_view is inside view_rect.
pub fn cells_in_view_rect(view_rect: Rect<int>) -> Vec<V2<int>> {
    // Add CELL_W to bottom so that the tops of lower tiles will get drawn.
    let (min, max) = (view_rect.mn(), view_rect.mx() + V2(0, CELL_W));
    let mut cells = Rect(view_to_chart(min), V2(0, 0));
    cells.grow(view_to_chart(min + V2((view_rect.1).0, 0)));
    cells.grow(view_to_chart(min + V2(0, (view_rect.1).1 + CELL_W)));
    cells.grow(view_to_chart(max));

    // XXX: Adds some points that are outside the view rectangle.
    let mut ret = vec![];
    for y in range((cells.0).1, cells.mx().1 + 1) {
        for x in range((cells.0).0, cells.mx().0 + 1) {
            ret.push(V2(x, y));
        }
    }

    ret
}

pub fn cells_on_screen() -> Vec<V2<int>> {
    cells_in_view_rect(Rect(V2(-SCREEN_W / 2, -SCREEN_H / 2), V2(SCREEN_W, SCREEN_H)))
}
<|endoftext|>


<commit_before>use error::{Result, ErrorKind};
use std::result::Result as StdResult;
use read::{WadReadFrom, WadRead};
use rustc_serialize::{Encoder, Encodable, Decoder, Decodable};
use std::ascii::AsciiExt;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::io::Read;
use std::str::{self, FromStr};
use std::error::Error;
use std::ops::Deref;
use std::borrow::Borrow;

#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct WadName([u8; 8]);

impl Deref for WadName {
    type Target = [u8; 8];
    fn deref(&self) -> &[u8; 8] {
        &self.0
    }
}

impl WadName {
    pub fn from_bytes(value: &[u8]) -> Result<WadName> {
        let mut name = [0u8; 8];
        let mut nulled = false;
        for (dest, src) in name.iter_mut().zip(value.iter()) {
            if !src.is_ascii() {
                debug!("Bailed on non-ascii {}", src);
                return Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into());
            }

            let new_byte = match src.to_ascii_uppercase() {
                b@b'A'...b'Z' |
                b@b'0'...b'9' |
                b@b'_' |
                b@b'-' |
                b@b'[' |
                b@b']' |
                b@b'\\' => b,
                b'\0' => {
                    nulled = true;
                    break;
                }
                b => {
                    debug!("Bailed on ascii {}", b);
                    return Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into());
                }
            };
            *dest = new_byte;
        }
        if !nulled && value.len() > 8 {
            debug!("Bailed on '{:?}' {} {}",
                   str::from_utf8(value),
                   value.len(),
                   !nulled);
            Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into())
        } else {
            Ok(WadName(name))
        }
    }
}

impl FromStr for WadName {
    type Err = super::error::Error;
    fn from_str(value: &str) -> Result<WadName> {
        WadName::from_bytes(value.as_bytes())
    }
}

impl Display for WadName {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{}", str::from_utf8(&self[..]).unwrap())
    }
}
impl Debug for WadName {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter,
               "WadName({:?})",
               str::from_utf8(&self[..]).unwrap())
    }
}
impl Encodable for WadName {
    fn encode<S: Encoder>(&self, encoder: &mut S) -> StdResult<(), <S as Encoder>::Error> {
        str::from_utf8(&self[..]).unwrap().encode(encoder)
    }
}

impl Decodable for WadName {
    fn decode<S: Decoder>(decoder: &mut S) -> StdResult<WadName, <S as Decoder>::Error> {
        decoder.read_str()
               .and_then(|s| {
                   WadName::from_str(&s).map_err(|_| decoder.error("Could not decode WadName."))
               })
    }
}

impl WadReadFrom for WadName {
    fn wad_read_from<R: Read>(reader: &mut R) -> Result<Self> {
        let bytes = try!(reader.wad_read::<u64>());
        WadName::from_bytes(&[(bytes & 0xff) as u8,
                              ((bytes >> 8) & 0xff) as u8,
                              ((bytes >> 16) & 0xff) as u8,
                              ((bytes >> 24) & 0xff) as u8,
                              ((bytes >> 32) & 0xff) as u8,
                              ((bytes >> 40) & 0xff) as u8,
                              ((bytes >> 48) & 0xff) as u8,
                              ((bytes >> 56) & 0xff) as u8])
    }
}

impl PartialEq<[u8; 8]> for WadName {
    fn eq(&self, rhs: &[u8; 8]) -> bool {
        self.deref() == rhs
    }
}

impl Borrow<[u8; 8]> for WadName {
    fn borrow(&self) -> &[u8; 8] {
        self.deref()
    }
}

impl AsRef<str> for WadName {
    fn as_ref(&self) -> &str {
        str::from_utf8(self.deref()).ok().expect("wad name is not valid utf-8")
    }
}

#[cfg(test)]
mod test {
    use super::WadName;
    use super::error;

    #[test]
    fn test_wad_name() {
        assert_eq!(&WadName::from_str("").unwrap(), b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("\0").unwrap(), b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("\01234567").unwrap(),
                   b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("A").unwrap(), b"A\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("1234567").unwrap(), b"1234567\0");
        assert_eq!(&WadName::from_str("12345678").unwrap(), b"12345678");
        assert_eq!(&WadName::from_str("123\05678").unwrap(), b"123\0\0\0\0\0");
        assert_eq!(&WadName::from_str("SKY1").unwrap(), b"SKY1\0\0\0\0");
        assert_eq!(&WadName::from_str("-").unwrap(), b"-\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("_").unwrap(), b"_\0\0\0\0\0\0\0");

        assert!(WadName::from_bytes(b"123456789").is_err());
        assert!(WadName::from_bytes(b"1234\xfb").is_err());
        assert!(WadName::from_bytes(b"\xff123").is_err());
        assert!(WadName::from_bytes(b"$$ASDF_").is_err());
        assert!(WadName::from_bytes(b"123456789\0").is_err());
    }
}
<commit_msg>fixed another test error<commit_after>use error::{Result, ErrorKind};
use std::result::Result as StdResult;
use read::{WadReadFrom, WadRead};
use rustc_serialize::{Encoder, Encodable, Decoder, Decodable};
use std::ascii::AsciiExt;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::io::Read;
use std::str::{self, FromStr};
use std::error::Error;
use std::ops::Deref;
use std::borrow::Borrow;

#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct WadName([u8; 8]);

impl Deref for WadName {
    type Target = [u8; 8];
    fn deref(&self) -> &[u8; 8] {
        &self.0
    }
}

impl WadName {
    pub fn from_bytes(value: &[u8]) -> Result<WadName> {
        let mut name = [0u8; 8];
        let mut nulled = false;
        for (dest, src) in name.iter_mut().zip(value.iter()) {
            if !src.is_ascii() {
                debug!("Bailed on non-ascii {}", src);
                return Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into());
            }

            let new_byte = match src.to_ascii_uppercase() {
                b@b'A'...b'Z' |
                b@b'0'...b'9' |
                b@b'_' |
                b@b'-' |
                b@b'[' |
                b@b']' |
                b@b'\\' => b,
                b'\0' => {
                    nulled = true;
                    break;
                }
                b => {
                    debug!("Bailed on ascii {}", b);
                    return Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into());
                }
            };
            *dest = new_byte;
        }
        if !nulled && value.len() > 8 {
            debug!("Bailed on '{:?}' {} {}",
                   str::from_utf8(value),
                   value.len(),
                   !nulled);
            Err(ErrorKind::BadWadName(value.iter().cloned().collect()).into())
        } else {
            Ok(WadName(name))
        }
    }
}

impl FromStr for WadName {
    type Err = super::error::Error;
    fn from_str(value: &str) -> Result<WadName> {
        WadName::from_bytes(value.as_bytes())
    }
}

impl Display for WadName {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "{}", str::from_utf8(&self[..]).unwrap())
    }
}
impl Debug for WadName {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter,
               "WadName({:?})",
               str::from_utf8(&self[..]).unwrap())
    }
}
impl Encodable for WadName {
    fn encode<S: Encoder>(&self, encoder: &mut S) -> StdResult<(), <S as Encoder>::Error> {
        str::from_utf8(&self[..]).unwrap().encode(encoder)
    }
}

impl Decodable for WadName {
    fn decode<S: Decoder>(decoder: &mut S) -> StdResult<WadName, <S as Decoder>::Error> {
        decoder.read_str()
               .and_then(|s| {
                   WadName::from_str(&s).map_err(|_| decoder.error("Could not decode WadName."))
               })
    }
}

impl WadReadFrom for WadName {
    fn wad_read_from<R: Read>(reader: &mut R) -> Result<Self> {
        let bytes = try!(reader.wad_read::<u64>());
        WadName::from_bytes(&[(bytes & 0xff) as u8,
                              ((bytes >> 8) & 0xff) as u8,
                              ((bytes >> 16) & 0xff) as u8,
                              ((bytes >> 24) & 0xff) as u8,
                              ((bytes >> 32) & 0xff) as u8,
                              ((bytes >> 40) & 0xff) as u8,
                              ((bytes >> 48) & 0xff) as u8,
                              ((bytes >> 56) & 0xff) as u8])
    }
}

impl PartialEq<[u8; 8]> for WadName {
    fn eq(&self, rhs: &[u8; 8]) -> bool {
        self.deref() == rhs
    }
}

impl Borrow<[u8; 8]> for WadName {
    fn borrow(&self) -> &[u8; 8] {
        self.deref()
    }
}

impl AsRef<str> for WadName {
    fn as_ref(&self) -> &str {
        str::from_utf8(self.deref()).ok().expect("wad name is not valid utf-8")
    }
}

#[cfg(test)]
mod test {
    use super::WadName;
    use std::str::FromStr;

    #[test]
    fn test_wad_name() {
        assert_eq!(&WadName::from_str("").unwrap(), b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("\0").unwrap(), b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("\01234567").unwrap(),
                   b"\0\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("A").unwrap(), b"A\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("1234567").unwrap(), b"1234567\0");
        assert_eq!(&WadName::from_str("12345678").unwrap(), b"12345678");
        assert_eq!(&WadName::from_str("123\05678").unwrap(), b"123\0\0\0\0\0");
        assert_eq!(&WadName::from_str("SKY1").unwrap(), b"SKY1\0\0\0\0");
        assert_eq!(&WadName::from_str("-").unwrap(), b"-\0\0\0\0\0\0\0");
        assert_eq!(&WadName::from_str("_").unwrap(), b"_\0\0\0\0\0\0\0");

        assert!(WadName::from_bytes(b"123456789").is_err());
        assert!(WadName::from_bytes(b"1234\xfb").is_err());
        assert!(WadName::from_bytes(b"\xff123").is_err());
        assert!(WadName::from_bytes(b"$$ASDF_").is_err());
        assert!(WadName::from_bytes(b"123456789\0").is_err());
    }
}
<|endoftext|>