<commit_before>#![feature(advanced_slice_patterns, slice_patterns)]
#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;
extern crate git2;

pub mod author;
pub mod config;
pub mod errors;
pub mod git;

use std::collections::HashMap;
use std::env;
use std::process::Command;

use author::{Author, AuthorParser};
use config::Config;
use errors::*;

const NAMESPACE: &'static str = "git-together";

fn namespaced(name: &str) -> String {
    format!("{}.{}", NAMESPACE, name)
}

pub struct GitTogether<C> {
    config: C,
    author_parser: AuthorParser,
}

impl GitTogether<git::Config> {
    pub fn new() -> Result<Self> {
        let repo = git::Repo::new();
        if let Ok(ref repo) = repo {
            let _ = repo.auto_include(&format!(".{}", NAMESPACE));
        }

        let config = repo.and_then(|r| r.config()).or_else(|_| git::Config::new())?;
        let domain = config.get(&namespaced("domain")).ok();
        let author_parser = AuthorParser { domain: domain };

        Ok(GitTogether {
            config: config,
            author_parser: author_parser,
        })
    }
}

impl<C: config::Config> GitTogether<C> {
    pub fn set_active(&mut self, inits: &[&str]) -> Result<Vec<Author>> {
        let authors = self.get_authors(inits)?;
        self.config.set(&namespaced("active"), &inits.join("+"))?;
        Ok(authors)
    }

    pub fn all_authors(&self) -> Result<HashMap<String, Author>> {
        let mut authors = HashMap::new();
        let raw = self.config.get_all(&namespaced("authors."))?;
        for (name, value) in raw {
            let initials = name.split('.').last().ok_or("")?;
            let author = self.parse_author(initials, &value)?;
            authors.insert(initials.into(), author);
        }
        Ok(authors)
    }

    pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
        let active = self.config.get(&namespaced("active"))?;
        let inits: Vec<_> = active.split('+').collect();
        let authors = self.get_authors(&inits)?;

        let (author, committer) = match *authors.as_slice() {
            [] => {
                return Err("".into());
            }
            [ref solo] => (solo, solo),
            [ref author, ref committer, _..] => (author, committer),
        };

        let cmd = cmd.env("GIT_AUTHOR_NAME", author.name.clone())
            .env("GIT_AUTHOR_EMAIL", author.email.clone())
            .env("GIT_COMMITTER_NAME", committer.name.clone())
            .env("GIT_COMMITTER_EMAIL", committer.email.clone());

        let no_signoff = env::var("GIT_TOGETHER_NO_SIGNOFF").is_ok();
        let cmd = if !no_signoff && author != committer {
            cmd.arg("--signoff")
        } else {
            cmd
        };

        Ok(cmd)
    }

    fn get_active(&self) -> Result<Vec<String>> {
        self.config
            .get(&namespaced("active"))
            .map(|active| active.split('+').map(|s| s.into()).collect())
    }

    pub fn rotate_active(&mut self) -> Result<()> {
        self.get_active().and_then(|active| {
            let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
            if !inits.is_empty() {
                let author = inits.remove(0);
                inits.push(author);
            }
            self.set_active(&inits[..]).map(|_| ())
        })
    }

    fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
        inits.iter()
            .map(|&initials| self.get_author(initials))
            .collect()
    }

    fn get_author(&self, initials: &str) -> Result<Author> {
        self.config
            .get(&namespaced(&format!("authors.{}", initials)))
            .chain_err(|| format!("author not found for '{}'", initials))
            .and_then(|raw| self.parse_author(initials, &raw))
    }

    fn parse_author(&self, initials: &str, raw: &str) -> Result<Author> {
        self.author_parser
            .parse(raw)
            .chain_err(|| {
                format!("invalid author for '{}': '{}'", initials, raw)
            })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::collections::HashMap;

    use author::{Author, AuthorParser};
    use config::Config;

    #[test]
    fn get_authors() {
        let config = MockConfig::new(&[("authors.jh", ""),
                                       ("authors.nn", "Naomi Nagata"),
                                       ("authors.ab", "Amos Burton; aburton"),
                                       ("authors.ak", "Alex Kamal; akamal"),
                                       ("authors.ca", "Chrisjen Avasarala;"),
                                       ("authors.bd",
                                        "Bobbie Draper; bdraper@mars.mil"),
                                       ("authors.jm",
                                        "Joe Miller; jmiller@starhelix.com")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        assert!(gt.get_authors(&["jh"]).is_err());
        assert!(gt.get_authors(&["nn"]).is_err());
        assert!(gt.get_authors(&["ca"]).is_err());
        assert!(gt.get_authors(&["jh", "bd"]).is_err());

        assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
                   vec![Author {
                            name: "Amos Burton".into(),
                            email: "aburton@rocinante.com".into(),
                        },
                        Author {
                            name: "Alex Kamal".into(),
                            email: "akamal@rocinante.com".into(),
                        }]);
        assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
                   vec![Author {
                            name: "Amos Burton".into(),
                            email: "aburton@rocinante.com".into(),
                        },
                        Author {
                            name: "Bobbie Draper".into(),
                            email: "bdraper@mars.mil".into(),
                        },
                        Author {
                            name: "Joe Miller".into(),
                            email: "jmiller@starhelix.com".into(),
                        }]);
    }

    #[test]
    fn set_active() {
        let config =
            MockConfig::new(&[("authors.jh", "James Holden; jholden"),
                              ("authors.nn", "Naomi Nagata; nnagata")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.set_active(&["jh"]).unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

        gt.set_active(&["jh", "nn"]).unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
    }

    #[test]
    fn rotate_active() {
        let config =
            MockConfig::new(&[("active", "jh+nn"),
                              ("authors.jh", "James Holden; jholden"),
                              ("authors.nn", "Naomi Nagata; nnagata")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.rotate_active().unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
    }

    #[test]
    fn all_authors() {
        let config = MockConfig::new(&[("active", "jh+nn"),
                                       ("authors.ab", "Amos Burton; aburton"),
                                       ("authors.bd",
                                        "Bobbie Draper; bdraper@mars.mil"),
                                       ("authors.jm",
                                        "Joe Miller; jmiller@starhelix.com")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        let all_authors = gt.all_authors().unwrap();
        assert_eq!(all_authors.len(), 3);
        assert_eq!(all_authors["ab"],
                   Author {
                       name: "Amos Burton".into(),
                       email: "aburton@rocinante.com".into(),
                   });
        assert_eq!(all_authors["bd"],
                   Author {
                       name: "Bobbie Draper".into(),
                       email: "bdraper@mars.mil".into(),
                   });
        assert_eq!(all_authors["jm"],
                   Author {
                       name: "Joe Miller".into(),
                       email: "jmiller@starhelix.com".into(),
                   });
    }

    struct MockConfig {
        data: HashMap<String, String>,
    }

    impl MockConfig {
        fn new(data: &[(&str, &str)]) -> MockConfig {
            MockConfig {
                data: data.iter()
                    .map(|&(k, v)| (namespaced(k), v.into()))
                    .collect(),
            }
        }
    }

    impl Config for MockConfig {
        fn get(&self, name: &str) -> Result<String> {
            self.data
                .get(name.into())
                .cloned()
                .ok_or(format!("name not found: '{}'", name).into())
        }

        fn get_all(&self, glob: &str) -> Result<HashMap<String, String>> {
            Ok(self.data
                .iter()
                .filter(|&(name, _)| name.contains(glob))
                .map(|(name, value)| (name.clone(), value.clone()))
                .collect())
        }

        fn add(&mut self, _: &str, _: &str) -> Result<()> {
            unimplemented!();
        }

        fn set(&mut self, name: &str, value: &str) -> Result<()> {
            self.data.insert(name.into(), value.into());
            Ok(())
        }
    }
}
<commit_msg>explicitly use git-together.* in the mock config<commit_after>#![feature(advanced_slice_patterns, slice_patterns)]
#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;
extern crate git2;

pub mod author;
pub mod config;
pub mod errors;
pub mod git;

use std::collections::HashMap;
use std::env;
use std::process::Command;

use author::{Author, AuthorParser};
use config::Config;
use errors::*;

const NAMESPACE: &'static str = "git-together";

fn namespaced(name: &str) -> String {
    format!("{}.{}", NAMESPACE, name)
}

pub struct GitTogether<C> {
    config: C,
    author_parser: AuthorParser,
}

impl GitTogether<git::Config> {
    pub fn new() -> Result<Self> {
        let repo = git::Repo::new();
        if let Ok(ref repo) = repo {
            let _ = repo.auto_include(&format!(".{}", NAMESPACE));
        }

        let config = repo.and_then(|r| r.config())
            .or_else(|_| git::Config::new())?;
        let domain = config.get(&namespaced("domain")).ok();
        let author_parser = AuthorParser { domain: domain };

        Ok(GitTogether {
            config: config,
            author_parser: author_parser,
        })
    }
}

impl<C: config::Config> GitTogether<C> {
    pub fn set_active(&mut self, inits: &[&str]) -> Result<Vec<Author>> {
        let authors = self.get_authors(inits)?;
        self.config.set(&namespaced("active"), &inits.join("+"))?;
        Ok(authors)
    }

    pub fn all_authors(&self) -> Result<HashMap<String, Author>> {
        let mut authors = HashMap::new();
        let raw = self.config.get_all(&namespaced("authors."))?;
        for (name, value) in raw {
            let initials = name.split('.').last().ok_or("")?;
            let author = self.parse_author(initials, &value)?;
            authors.insert(initials.into(), author);
        }
        Ok(authors)
    }

    pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
        let active = self.config.get(&namespaced("active"))?;
        let inits: Vec<_> = active.split('+').collect();
        let authors = self.get_authors(&inits)?;

        let (author, committer) = match *authors.as_slice() {
            [] => {
                return Err("".into());
            }
            [ref solo] => (solo, solo),
            [ref author, ref committer, _..] => (author, committer),
        };

        let cmd = cmd.env("GIT_AUTHOR_NAME", author.name.clone())
            .env("GIT_AUTHOR_EMAIL", author.email.clone())
            .env("GIT_COMMITTER_NAME", committer.name.clone())
            .env("GIT_COMMITTER_EMAIL", committer.email.clone());

        let no_signoff = env::var("GIT_TOGETHER_NO_SIGNOFF").is_ok();
        let cmd = if !no_signoff && author != committer {
            cmd.arg("--signoff")
        } else {
            cmd
        };

        Ok(cmd)
    }

    fn get_active(&self) -> Result<Vec<String>> {
        self.config
            .get(&namespaced("active"))
            .map(|active| active.split('+').map(|s| s.into()).collect())
    }

    pub fn rotate_active(&mut self) -> Result<()> {
        self.get_active().and_then(|active| {
            let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
            if !inits.is_empty() {
                let author = inits.remove(0);
                inits.push(author);
            }
            self.set_active(&inits[..]).map(|_| ())
        })
    }

    fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
        inits.iter()
            .map(|&initials| self.get_author(initials))
            .collect()
    }

    fn get_author(&self, initials: &str) -> Result<Author> {
        self.config
            .get(&namespaced(&format!("authors.{}", initials)))
            .chain_err(|| format!("author not found for '{}'", initials))
            .and_then(|raw| self.parse_author(initials, &raw))
    }

    fn parse_author(&self, initials: &str, raw: &str) -> Result<Author> {
        self.author_parser
            .parse(raw)
            .chain_err(|| {
                format!("invalid author for '{}': '{}'", initials, raw)
            })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::collections::HashMap;

    use author::{Author, AuthorParser};
    use config::Config;

    #[test]
    fn get_authors() {
        let config = MockConfig::new(&[("git-together.authors.jh", ""),
                                       ("git-together.authors.nn",
                                        "Naomi Nagata"),
                                       ("git-together.authors.ab",
                                        "Amos Burton; aburton"),
                                       ("git-together.authors.ak",
                                        "Alex Kamal; akamal"),
                                       ("git-together.authors.ca",
                                        "Chrisjen Avasarala;"),
                                       ("git-together.authors.bd",
                                        "Bobbie Draper; bdraper@mars.mil"),
                                       ("git-together.authors.jm",
                                        "Joe Miller; jmiller@starhelix.com")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        assert!(gt.get_authors(&["jh"]).is_err());
        assert!(gt.get_authors(&["nn"]).is_err());
        assert!(gt.get_authors(&["ca"]).is_err());
        assert!(gt.get_authors(&["jh", "bd"]).is_err());

        assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
                   vec![Author {
                            name: "Amos Burton".into(),
                            email: "aburton@rocinante.com".into(),
                        },
                        Author {
                            name: "Alex Kamal".into(),
                            email: "akamal@rocinante.com".into(),
                        }]);
        assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
                   vec![Author {
                            name: "Amos Burton".into(),
                            email: "aburton@rocinante.com".into(),
                        },
                        Author {
                            name: "Bobbie Draper".into(),
                            email: "bdraper@mars.mil".into(),
                        },
                        Author {
                            name: "Joe Miller".into(),
                            email: "jmiller@starhelix.com".into(),
                        }]);
    }

    #[test]
    fn set_active() {
        let config =
            MockConfig::new(&[("git-together.authors.jh", "James Holden; jholden"),
                              ("git-together.authors.nn", "Naomi Nagata; nnagata")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.set_active(&["jh"]).unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

        gt.set_active(&["jh", "nn"]).unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
    }

    #[test]
    fn rotate_active() {
        let config =
            MockConfig::new(&[("git-together.active", "jh+nn"),
                              ("git-together.authors.jh", "James Holden; jholden"),
                              ("git-together.authors.nn", "Naomi Nagata; nnagata")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.rotate_active().unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
    }

    #[test]
    fn all_authors() {
        let config = MockConfig::new(&[("git-together.active", "jh+nn"),
                                       ("git-together.authors.ab", "Amos Burton; aburton"),
                                       ("git-together.authors.bd",
                                        "Bobbie Draper; bdraper@mars.mil"),
                                       ("git-together.authors.jm",
                                        "Joe Miller; jmiller@starhelix.com")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        let all_authors = gt.all_authors().unwrap();
        assert_eq!(all_authors.len(), 3);
        assert_eq!(all_authors["ab"],
                   Author {
                       name: "Amos Burton".into(),
                       email: "aburton@rocinante.com".into(),
                   });
        assert_eq!(all_authors["bd"],
                   Author {
                       name: "Bobbie Draper".into(),
                       email: "bdraper@mars.mil".into(),
                   });
        assert_eq!(all_authors["jm"],
                   Author {
                       name: "Joe Miller".into(),
                       email: "jmiller@starhelix.com".into(),
                   });
    }

    struct MockConfig {
        data: HashMap<String, String>,
    }

    impl MockConfig {
        fn new(data: &[(&str, &str)]) -> MockConfig {
            MockConfig {
                data: data.iter()
                    .map(|&(k, v)| (k.into(), v.into()))
                    .collect(),
            }
        }
    }

    impl Config for MockConfig {
        fn get(&self, name: &str) -> Result<String> {
            self.data
                .get(name.into())
                .cloned()
                .ok_or(format!("name not found: '{}'", name).into())
        }

        fn get_all(&self, glob: &str) -> Result<HashMap<String, String>> {
            Ok(self.data
                .iter()
                .filter(|&(name, _)| name.contains(glob))
                .map(|(name, value)| (name.clone(), value.clone()))
                .collect())
        }

        fn add(&mut self, _: &str, _: &str) -> Result<()> {
            unimplemented!();
        }

        fn set(&mut self, name: &str, value: &str) -> Result<()> {
            self.data.insert(name.into(), value.into());
            Ok(())
        }
    }
}
<|endoftext|>


<commit_before>//! A library for interacting with Twitter.
//!
//! Please see [the repository][] and its enclosed examples for tips on working with this library
//! while it's still in progress.
//!
//! [the repository]: https://github.com/icesoldier/twitter-rs
//!
//! ## Getting Started
//!
//! The very first thing you'll need to do to get access to the Twitter API is to head to
//! [Twitter's Application Manager][twitter-apps] and create an app. Once you've done that, there
//! are two sets of keys immediately available to you. First are the "consumer key" and "consumer
//! secret", that are used to represent you as the application author when signing requests. These
//! keys are given to every single API call regardless of permission level. Related are an "access
//! token" and "access token secret", that can be used to skip the authentication steps if you're
//! only interacting with your own account or with no account in particular. Generally, if you want
//! to read or write to a particular user's stream, you'll need to request authorization and get an
//! access token to work on their behalf.
//!
//! [twitter-apps]: https://apps.twitter.com/
//!
//! The process to get an access token for a specific user (with this library) has three basic
//! steps:
//!
//! 1. Log your request with Twitter by getting a [request token][].
//! 2. Direct the user to grant permission to your application by sending them to an
//!    [authenticate][] or [authorize][] URL, depending on the nature of your app.
//! 3. Convert the verifier given by the permission request into an [access token][].
//!
//! [request token]: fn.request_token.html
//! [authorize]: fn.authorize_url.html
//! [authenticate]: fn.authenticate_url.html
//! [access token]: fn.access_token.html
//!
//! The process changes some smaller details depending on whether your application is web- or
//! mobile-based, as opposed to desktop-based or in another situation where redirecting users to a
//! web browser and back automatically is difficult or impossible. In the former case where URLs
//! can be used to direct the user into and out of your app, give Twitter a "callback URL" when
//! setting up your request token, and redirect the user to an [authenticate][] URL to grant your
//! app permissions. When the user accepts the request, they will be redirected back to this URL to
//! get back to your app, with the original request token and a verifier given to your app to
//! signify their acceptance.
//!
//! On the other hand, if you can't use the callback URL in this fashion, you can instead use the
//! "PIN-based Auth" version of the flow. In this version, give a "callback URL" of "oob" when
//! setting up your request token, and use an [authorize][] URL. When the user grants the
//! permissions request in this fashion, they are given a numeric PIN that can be given back to
//! your app to use as a verifier.
//!
//! Either way, when you have a "verifier" from either of these methods, you can use your Tokens
//! from earlier in the process with that verifier to request an [access token][]. This access
//! token can then be saved and cached for future use.

#![warn(missing_docs)]
#![warn(unused_extern_crates)]
#![warn(unused_qualifications)]

#[macro_use] extern crate hyper;
extern crate url;
extern crate time;
extern crate rand;
extern crate crypto;
extern crate rustc_serialize;
extern crate mime;

mod auth;
pub mod error;
pub mod user;
mod links;
mod common;

pub use auth::{Token, request_token, authorize_url, access_token};
pub use common::{Response, TwitterErrors, TwitterErrorCode, UserID};
<commit_msg>oops, forgot to export authenticate_url<commit_after>//! A library for interacting with Twitter.
//!
//! Please see [the repository][] and its enclosed examples for tips on working with this library
//! while it's still in progress.
//!
//! [the repository]: https://github.com/icesoldier/twitter-rs
//!
//! ## Getting Started
//!
//! The very first thing you'll need to do to get access to the Twitter API is to head to
//! [Twitter's Application Manager][twitter-apps] and create an app. Once you've done that, there
//! are two sets of keys immediately available to you. First are the "consumer key" and "consumer
//! secret", that are used to represent you as the application author when signing requests. These
//! keys are given to every single API call regardless of permission level. Related are an "access
//! token" and "access token secret", that can be used to skip the authentication steps if you're
//! only interacting with your own account or with no account in particular. Generally, if you want
//! to read or write to a particular user's stream, you'll need to request authorization and get an
//! access token to work on their behalf.
//!
//! [twitter-apps]: https://apps.twitter.com/
//!
//! The process to get an access token for a specific user (with this library) has three basic
//! steps:
//!
//! 1. Log your request with Twitter by getting a [request token][].
//! 2. Direct the user to grant permission to your application by sending them to an
//!    [authenticate][] or [authorize][] URL, depending on the nature of your app.
//! 3. Convert the verifier given by the permission request into an [access token][].
//!
//! [request token]: fn.request_token.html
//! [authorize]: fn.authorize_url.html
//! [authenticate]: fn.authenticate_url.html
//! [access token]: fn.access_token.html
//!
//! The process changes some smaller details depending on whether your application is web- or
//! mobile-based, as opposed to desktop-based or in another situation where redirecting users to a
//! web browser and back automatically is difficult or impossible. In the former case where URLs
//! can be used to direct the user into and out of your app, give Twitter a "callback URL" when
//! setting up your request token, and redirect the user to an [authenticate][] URL to grant your
//! app permissions. When the user accepts the request, they will be redirected back to this URL to
//! get back to your app, with the original request token and a verifier given to your app to
//! signify their acceptance.
//!
//! On the other hand, if you can't use the callback URL in this fashion, you can instead use the
//! "PIN-based Auth" version of the flow. In this version, give a "callback URL" of "oob" when
//! setting up your request token, and use an [authorize][] URL. When the user grants the
//! permissions request in this fashion, they are given a numeric PIN that can be given back to
//! your app to use as a verifier.
//!
//! Either way, when you have a "verifier" from either of these methods, you can use your Tokens
//! from earlier in the process with that verifier to request an [access token][]. This access
//! token can then be saved and cached for future use.

#![warn(missing_docs)]
#![warn(unused_extern_crates)]
#![warn(unused_qualifications)]

#[macro_use] extern crate hyper;
extern crate url;
extern crate time;
extern crate rand;
extern crate crypto;
extern crate rustc_serialize;
extern crate mime;

mod auth;
pub mod error;
pub mod user;
mod links;
mod common;

pub use auth::{Token, request_token, authorize_url, authenticate_url, access_token};
pub use common::{Response, TwitterErrors, TwitterErrorCode, UserID};
<|endoftext|>


<commit_before>#![no_std]
//!
//! You can populate [`Petnames`] with your own word lists, but the word lists
//! from upstream [petname](https://github.com/dustinkirkland/petname) are
//! included with the `default_dictionary` feature (enabled by default). See
//! [`Petnames::small`], [`Petnames::medium`], and [`Petnames::large`] to select
//! a particular built-in word list, or use the [`Default`] implementation.
//!
//! The other thing you need is a random number generator from [rand][]:
//!
//! ```rust
//! let mut rng = rand::thread_rng();
//! let pname = petname::Petnames::default().generate(&mut rng, 7, ":");
//! ```
//!
//! It may be more convenient to use the default random number generator:
//!
//! ```rust
//! let pname = petname::Petnames::default().generate_one(7, ":");
//! ```
//!
//! There's a [convenience function][petname] that'll do all of this:
//!
//! ```rust
//! let pname = petname::petname(7, ":");
//! ```
//!
//! But the most flexible approach is to create an [`Iterator`] with
//! [`iter`][`Petnames::iter`]:
//!
//! ```rust
//! let mut rng = rand::thread_rng();
//! let petnames = petname::Petnames::default();
//! let ten_thousand_names: Vec<String> =
//!   petnames.iter(&mut rng, 3, "_").take(10000).collect();
//! ```
//!
//! You can modify the word lists to, for example, only use words beginning with
//! the letter "b":
//!
//! ```rust
//! let mut petnames = petname::Petnames::default();
//! petnames.retain(|s| s.starts_with("b"));
//! petnames.generate_one(3, ".");
//! ```
//!

extern crate alloc;

use alloc::{
    string::{String, ToString},
    vec::Vec,
};

use itertools::Itertools;
use rand::seq::SliceRandom;

/// Convenience function to generate a new petname from default word lists.
#[allow(dead_code)]
#[cfg(feature = "std_rng")]
#[cfg(feature = "default_dictionary")]
pub fn petname(words: u8, separator: &str) -> String {
    Petnames::new().generate_one(words, separator)
}

/// A word list.
pub type Words<'a> = Vec<&'a str>;

/// Word lists and the logic to combine them into _petnames_.
///
/// A _petname_ with `n` words will contain, in order:
///
///   * `n - 2` adverbs when `n >= 2`, otherwise 0 adverbs.
///   * 1 adjective when `n >= 2`, otherwise 0 adjectives.
///   * 1 name / noun when `n >= 1`, otherwise 0 names.
///
#[derive(Clone, Debug, PartialEq)]
pub struct Petnames<'a> {
    pub adjectives: Words<'a>,
    pub adverbs: Words<'a>,
    pub names: Words<'a>,
}

impl<'a> Petnames<'a> {
    /// Constructs a new `Petnames` from the default (small) word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn new() -> Self {
        Self::default()
    }

    /// Constructs a new `Petnames` from the small word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn small() -> Self {
        Self::init(
            include_str!("../words/small/adjectives.txt"),
            include_str!("../words/small/adverbs.txt"),
            include_str!("../words/small/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the medium word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn medium() -> Self {
        Self::init(
            include_str!("../words/medium/adjectives.txt"),
            include_str!("../words/medium/adverbs.txt"),
            include_str!("../words/medium/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the large word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn large() -> Self {
        Self::init(
            include_str!("../words/large/adjectives.txt"),
            include_str!("../words/large/adverbs.txt"),
            include_str!("../words/large/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the given word lists.
    ///
    /// The words are extracted from the given strings by splitting on whitespace.
    pub fn init(adjectives: &'a str, adverbs: &'a str, names: &'a str) -> Self {
        Self {
            adjectives: adjectives.split_whitespace().collect(),
            adverbs: adverbs.split_whitespace().collect(),
            names: names.split_whitespace().collect(),
        }
    }

    /// Keep words matching a predicate.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(feature = "default_dictionary")]
    /// let mut petnames = petname::Petnames::default();
    /// # #[cfg(feature = "default_dictionary")]
    /// petnames.retain(|s| s.starts_with("b"));
    /// # #[cfg(feature = "default_dictionary")]
    /// # #[cfg(feature = "std_rng")]
    /// petnames.generate_one(2, ".");
    /// ```
    ///
    /// This is merely a convenience wrapper that applies the same predicate to
    /// the adjectives, adverbs, and names lists.
    ///
    pub fn retain<F>(&mut self, mut predicate: F)
    where
        F: FnMut(&str) -> bool,
    {
        self.adjectives.retain(|word| predicate(word));
        self.adverbs.retain(|word| predicate(word));
        self.names.retain(|word| predicate(word));
    }

    /// Calculate the cardinality of this `Petnames`.
    ///
    /// If this is low, names may be repeated by the generator with a higher
    /// frequency than your use-case may allow. If it is 0 (zero) the generator
    /// will panic (unless `words` is also zero).
    ///
    /// This can saturate. If the total possible combinations of words exceeds
    /// `u128::MAX` then this will return `u128::MAX`.
    pub fn cardinality(&self, words: u8) -> u128 {
        Lists(self, words)
            .map(|list| list.len() as u128)
            .fold1(u128::saturating_mul)
            .unwrap_or(0u128)
    }

    /// Generate a new petname.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut rng = rand::thread_rng();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// petname::Petnames::default().generate(&mut rng, 7, ":");
    /// ```
    ///
    /// # Notes
    ///
    /// This may return fewer words than you request if one or more of the word
    /// lists are empty. For example, if there are no adverbs, requesting 3 or
    /// more words may still yield only "doubtful-salmon".
    ///
    pub fn generate<RNG>(&self, rng: &mut RNG, words: u8, separator: &str) -> String
    where
        RNG: rand::Rng,
    {
        itertools::Itertools::intersperse(
            Lists(self, words)
                .filter_map(|list| list.choose(rng))
                .cloned(),
            separator,
        )
        .collect::<String>()
    }

    /// Generate a single new petname.
    ///
    /// This is like `generate` but uses `rand::thread_rng` as the random
    /// source. For efficiency use `generate` when creating multiple names, or
    /// when you want to use a custom source of randomness.
    #[cfg(feature = "std_rng")]
    pub fn generate_one(&self, words: u8, separator: &str) -> String {
        self.generate(&mut rand::thread_rng(), words, separator)
    }

    /// Iterator yielding petnames.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut rng = rand::thread_rng();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let petnames = petname::Petnames::default();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut iter = petnames.iter(&mut rng, 4, "_");
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// println!("name: {}", iter.next().unwrap());
    /// ```
    ///
    pub fn iter<RNG>(&self, rng: &'a mut RNG, words: u8, separator: &str) -> Names<RNG>
    where
        RNG: rand::Rng,
    {
        Names {
            petnames: self,
            rng,
            words,
            separator: separator.to_string(),
        }
    }

    /// Iterator yielding unique – i.e. non-repeating – petnames.
    pub fn iter_unique<RNG>(
        &'a self,
        rng: &'a mut RNG,
        words: u8,
        separator: &str,
    ) -> NamesUnique<
        'a,
        core::iter::Cycle<core::iter::Chain<alloc::vec::IntoIter<&'a str>, core::iter::Once<&str>>>,
    >
    where
        RNG: rand::Rng,
    {
        NamesUnique {
            iters: Lists(self, words)
                .cloned()
                .map(|mut list| {
                    list.shuffle(rng);
                    // The empty string is the cycle marker, i.e. at this point
                    // the next iterator needs to be advanced.
                    (list.into_iter().chain(core::iter::once("")).cycle(), None)
                })
                .collect(),
            separator: separator.to_string(),
        }
    }
}

#[cfg(feature = "default_dictionary")]
impl<'a> Default for Petnames<'a> {
    fn default() -> Self {
        Self::small()
    }
}

/// Iterator over a `Petnames`' word lists.
///
/// This yields the appropriate lists from which to select a word when
/// constructing a petname of `n` words. For example, if you want 3 words in
/// your petname, this will first yield the adverbs word list, then adjectives,
/// then names.
struct Lists<'a>(&'a Petnames<'a>, u8);

impl<'a> Iterator for Lists<'a> {
    type Item = &'a Words<'a>;

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, Some(self.1 as usize))
    }

    fn next(&mut self) -> Option<Self::Item> {
        let Self(petnames, ref mut word) = self;
        match word {
            0 => None,
            1 => {
                *word -= 1;
                Some(&petnames.names)
            }
            2 => {
                *word -= 1;
                Some(&petnames.adjectives)
            }
            _ => {
                *word -= 1;
                Some(&petnames.adverbs)
            }
        }
    }
}

/// Iterator yielding petnames.
pub struct Names<'a, RNG>
where
    RNG: rand::Rng,
{
    petnames: &'a Petnames<'a>,
    rng: &'a mut RNG,
    words: u8,
    separator: String,
}

impl<'a, RNG> Names<'a, RNG>
where
    RNG: rand::Rng,
{
    /// Calculate the cardinality of this iterator; see `Petnames::cardinality`.
    #[allow(dead_code)]
    pub fn cardinality(&self) -> u128 {
        self.petnames.cardinality(self.words)
    }
}

impl<'a, RNG> Iterator for Names<'a, RNG>
where
    RNG: rand::Rng,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        Some(
            self.petnames
                .generate(self.rng, self.words, &self.separator),
        )
    }
}

/// Iterator yielding unique petnames.
pub struct NamesUnique<'a, ITERATOR>
where
    ITERATOR: Iterator<Item = &'a str>,
{
    iters: Vec<(ITERATOR, Option<&'a str>)>,
    separator: String,
}

impl<'a, ITERATOR> Iterator for NamesUnique<'a, ITERATOR>
where
    ITERATOR: Iterator<Item = &'a str>,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        let mut bump = true; // Request advance of next iterator.
        for (iter, word) in self.iters.iter_mut() {
            if bump || word.is_none() {
                match iter.next() {
                    None => {
                        // This shouldn't happen because we expect the iterators
                        // to cycle. However, if it does, we're definitely done.
                        return None;
                    }
                    Some("") => {
                        // This is the cycle end marker. We want to get another
                        // new word from this iterator, and advance the *next*
                        // iterator too.
                        *word = iter.next();
                        bump = true
                    }
                    Some(s) => {
                        // We have a new word from this iterator, so we do not
                        // yet need to advance the next iterator.
                        *word = Some(s);
                        bump = false
                    }
                }
            }
        }
        if bump {
            // We reached the end of the last iterator, hence we're done.
            None
        } else {
            // We can construct a word!
            Some(
                itertools::Itertools::intersperse(
                    self.iters.iter().filter_map(|(_, w)| *w),
                    &self.separator,
                )
                .collect(),
            )
        }
    }
}
<commit_msg>Guard against broken expectations<commit_after>#![no_std]
//!
//! You can populate [`Petnames`] with your own word lists, but the word lists
//! from upstream [petname](https://github.com/dustinkirkland/petname) are
//! included with the `default_dictionary` feature (enabled by default). See
//! [`Petnames::small`], [`Petnames::medium`], and [`Petnames::large`] to select
//! a particular built-in word list, or use the [`Default`] implementation.
//!
//! The other thing you need is a random number generator from [rand][]:
//!
//! ```rust
//! let mut rng = rand::thread_rng();
//! let pname = petname::Petnames::default().generate(&mut rng, 7, ":");
//! ```
//!
//! It may be more convenient to use the default random number generator:
//!
//! ```rust
//! let pname = petname::Petnames::default().generate_one(7, ":");
//! ```
//!
//! There's a [convenience function][petname] that'll do all of this:
//!
//! ```rust
//! let pname = petname::petname(7, ":");
//! ```
//!
//! But the most flexible approach is to create an [`Iterator`] with
//! [`iter`][`Petnames::iter`]:
//!
//! ```rust
//! let mut rng = rand::thread_rng();
//! let petnames = petname::Petnames::default();
//! let ten_thousand_names: Vec<String> =
//!   petnames.iter(&mut rng, 3, "_").take(10000).collect();
//! ```
//!
//! You can modify the word lists to, for example, only use words beginning with
//! the letter "b":
//!
//! ```rust
//! let mut petnames = petname::Petnames::default();
//! petnames.retain(|s| s.starts_with("b"));
//! petnames.generate_one(3, ".");
//! ```
//!

extern crate alloc;

use alloc::{
    string::{String, ToString},
    vec::Vec,
};

use itertools::Itertools;
use rand::seq::SliceRandom;

/// Convenience function to generate a new petname from default word lists.
#[allow(dead_code)]
#[cfg(feature = "std_rng")]
#[cfg(feature = "default_dictionary")]
pub fn petname(words: u8, separator: &str) -> String {
    Petnames::new().generate_one(words, separator)
}

/// A word list.
pub type Words<'a> = Vec<&'a str>;

/// Word lists and the logic to combine them into _petnames_.
///
/// A _petname_ with `n` words will contain, in order:
///
///   * `n - 2` adverbs when `n >= 2`, otherwise 0 adverbs.
///   * 1 adjective when `n >= 2`, otherwise 0 adjectives.
///   * 1 name / noun when `n >= 1`, otherwise 0 names.
///
#[derive(Clone, Debug, PartialEq)]
pub struct Petnames<'a> {
    pub adjectives: Words<'a>,
    pub adverbs: Words<'a>,
    pub names: Words<'a>,
}

impl<'a> Petnames<'a> {
    /// Constructs a new `Petnames` from the default (small) word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn new() -> Self {
        Self::default()
    }

    /// Constructs a new `Petnames` from the small word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn small() -> Self {
        Self::init(
            include_str!("../words/small/adjectives.txt"),
            include_str!("../words/small/adverbs.txt"),
            include_str!("../words/small/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the medium word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn medium() -> Self {
        Self::init(
            include_str!("../words/medium/adjectives.txt"),
            include_str!("../words/medium/adverbs.txt"),
            include_str!("../words/medium/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the large word lists.
    #[cfg(feature = "default_dictionary")]
    pub fn large() -> Self {
        Self::init(
            include_str!("../words/large/adjectives.txt"),
            include_str!("../words/large/adverbs.txt"),
            include_str!("../words/large/names.txt"),
        )
    }

    /// Constructs a new `Petnames` from the given word lists.
    ///
    /// The words are extracted from the given strings by splitting on whitespace.
    pub fn init(adjectives: &'a str, adverbs: &'a str, names: &'a str) -> Self {
        Self {
            adjectives: adjectives.split_whitespace().collect(),
            adverbs: adverbs.split_whitespace().collect(),
            names: names.split_whitespace().collect(),
        }
    }

    /// Keep words matching a predicate.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(feature = "default_dictionary")]
    /// let mut petnames = petname::Petnames::default();
    /// # #[cfg(feature = "default_dictionary")]
    /// petnames.retain(|s| s.starts_with("b"));
    /// # #[cfg(feature = "default_dictionary")]
    /// # #[cfg(feature = "std_rng")]
    /// petnames.generate_one(2, ".");
    /// ```
    ///
    /// This is merely a convenience wrapper that applies the same predicate to
    /// the adjectives, adverbs, and names lists.
    ///
    pub fn retain<F>(&mut self, mut predicate: F)
    where
        F: FnMut(&str) -> bool,
    {
        self.adjectives.retain(|word| predicate(word));
        self.adverbs.retain(|word| predicate(word));
        self.names.retain(|word| predicate(word));
    }

    /// Calculate the cardinality of this `Petnames`.
    ///
    /// If this is low, names may be repeated by the generator with a higher
    /// frequency than your use-case may allow. If it is 0 (zero) the generator
    /// will panic (unless `words` is also zero).
    ///
    /// This can saturate. If the total possible combinations of words exceeds
    /// `u128::MAX` then this will return `u128::MAX`.
    pub fn cardinality(&self, words: u8) -> u128 {
        Lists(self, words)
            .map(|list| list.len() as u128)
            .fold1(u128::saturating_mul)
            .unwrap_or(0u128)
    }

    /// Generate a new petname.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut rng = rand::thread_rng();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// petname::Petnames::default().generate(&mut rng, 7, ":");
    /// ```
    ///
    /// # Notes
    ///
    /// This may return fewer words than you request if one or more of the word
    /// lists are empty. For example, if there are no adverbs, requesting 3 or
    /// more words may still yield only "doubtful-salmon".
    ///
    pub fn generate<RNG>(&self, rng: &mut RNG, words: u8, separator: &str) -> String
    where
        RNG: rand::Rng,
    {
        itertools::Itertools::intersperse(
            Lists(self, words)
                .filter_map(|list| list.choose(rng))
                .cloned(),
            separator,
        )
        .collect::<String>()
    }

    /// Generate a single new petname.
    ///
    /// This is like `generate` but uses `rand::thread_rng` as the random
    /// source. For efficiency use `generate` when creating multiple names, or
    /// when you want to use a custom source of randomness.
    #[cfg(feature = "std_rng")]
    pub fn generate_one(&self, words: u8, separator: &str) -> String {
        self.generate(&mut rand::thread_rng(), words, separator)
    }

    /// Iterator yielding petnames.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut rng = rand::thread_rng();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let petnames = petname::Petnames::default();
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// let mut iter = petnames.iter(&mut rng, 4, "_");
    /// # #[cfg(all(feature = "std_rng", feature = "default_dictionary"))]
    /// println!("name: {}", iter.next().unwrap());
    /// ```
    ///
    pub fn iter<RNG>(&self, rng: &'a mut RNG, words: u8, separator: &str) -> Names<RNG>
    where
        RNG: rand::Rng,
    {
        Names {
            petnames: self,
            rng,
            words,
            separator: separator.to_string(),
        }
    }

    /// Iterator yielding unique – i.e. non-repeating – petnames.
    pub fn iter_unique<RNG>(
        &'a self,
        rng: &'a mut RNG,
        words: u8,
        separator: &str,
    ) -> NamesUnique<
        'a,
        core::iter::Cycle<core::iter::Chain<alloc::vec::IntoIter<&'a str>, core::iter::Once<&str>>>,
    >
    where
        RNG: rand::Rng,
    {
        NamesUnique {
            iters: Lists(self, words)
                .cloned()
                .map(|mut list| {
                    list.shuffle(rng);
                    // The empty string is the cycle marker, i.e. at this point
                    // the next iterator needs to be advanced.
                    (list.into_iter().chain(core::iter::once("")).cycle(), None)
                })
                .collect(),
            separator: separator.to_string(),
        }
    }
}

#[cfg(feature = "default_dictionary")]
impl<'a> Default for Petnames<'a> {
    fn default() -> Self {
        Self::small()
    }
}

/// Iterator over a `Petnames`' word lists.
///
/// This yields the appropriate lists from which to select a word when
/// constructing a petname of `n` words. For example, if you want 3 words in
/// your petname, this will first yield the adverbs word list, then adjectives,
/// then names.
struct Lists<'a>(&'a Petnames<'a>, u8);

impl<'a> Iterator for Lists<'a> {
    type Item = &'a Words<'a>;

    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, Some(self.1 as usize))
    }

    fn next(&mut self) -> Option<Self::Item> {
        let Self(petnames, ref mut word) = self;
        match word {
            0 => None,
            1 => {
                *word -= 1;
                Some(&petnames.names)
            }
            2 => {
                *word -= 1;
                Some(&petnames.adjectives)
            }
            _ => {
                *word -= 1;
                Some(&petnames.adverbs)
            }
        }
    }
}

/// Iterator yielding petnames.
pub struct Names<'a, RNG>
where
    RNG: rand::Rng,
{
    petnames: &'a Petnames<'a>,
    rng: &'a mut RNG,
    words: u8,
    separator: String,
}

impl<'a, RNG> Names<'a, RNG>
where
    RNG: rand::Rng,
{
    /// Calculate the cardinality of this iterator; see `Petnames::cardinality`.
    #[allow(dead_code)]
    pub fn cardinality(&self) -> u128 {
        self.petnames.cardinality(self.words)
    }
}

impl<'a, RNG> Iterator for Names<'a, RNG>
where
    RNG: rand::Rng,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        Some(
            self.petnames
                .generate(self.rng, self.words, &self.separator),
        )
    }
}

/// Iterator yielding unique petnames.
pub struct NamesUnique<'a, ITERATOR>
where
    ITERATOR: Iterator<Item = &'a str>,
{
    iters: Vec<(ITERATOR, Option<&'a str>)>,
    separator: String,
}

impl<'a, ITERATOR> Iterator for NamesUnique<'a, ITERATOR>
where
    ITERATOR: Iterator<Item = &'a str>,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        let mut bump = true; // Request advance of next iterator.
        for (iter, word) in self.iters.iter_mut() {
            if bump || word.is_none() {
                match iter.next() {
                    None => {
                        // This shouldn't happen because we expect the iterators
                        // to cycle. However, if it does, we're definitely done.
                        return None;
                    }
                    Some("") => {
                        // This is the cycle end marker. We want to get another
                        // new word from this iterator, and advance the *next*
                        // iterator too.
                        match iter.next() {
                            None => return None,
                            Some("") => return None,
                            Some(s) => *word = Some(s),
                        }
                        bump = true
                    }
                    Some(s) => {
                        // We have a new word from this iterator, so we do not
                        // yet need to advance the next iterator.
                        *word = Some(s);
                        bump = false
                    }
                }
            }
        }
        if bump {
            // We reached the end of the last iterator, hence we're done.
            None
        } else {
            // We can construct a word!
            Some(
                itertools::Itertools::intersperse(
                    self.iters.iter().filter_map(|(_, w)| *w),
                    &self.separator,
                )
                .collect(),
            )
        }
    }
}
<|endoftext|>


<commit_before>//#![warn(missing_doc)]
#![forbid(non_camel_case_types, non_uppercase_statics)]
#![feature(macro_rules, struct_variant)]

//! This crate currently provides almost XML 1.0/1.1-compliant pull parser.

extern crate core;

pub use reader::EventReader;

pub mod macros;
pub mod common;
pub mod namespace;
pub mod reader;
pub mod writer;

<commit_msg>Remove forbid non_uppercase_statics. Fixes #13<commit_after>//#![warn(missing_doc)]
#![forbid(non_camel_case_types)]
#![feature(macro_rules, struct_variant)]

//! This crate currently provides almost XML 1.0/1.1-compliant pull parser.

extern crate core;

pub use reader::EventReader;

pub mod macros;
pub mod common;
pub mod namespace;
pub mod reader;
pub mod writer;

<|endoftext|>


<commit_before>/// Implementation of Stochastic universal sampling
/// https://en.wikipedia.org/wiki/Stochastic_universal_sampling
/// Runtime: O(n)
/// Memory Usage: O(n) or O(1) (in place variant)

extern crate rand;

use self::rand::{thread_rng, Rng};

pub struct RandomChoice;

impl RandomChoice {
    /// Chooses n samples by their weights. The greater their weights the more likely they get chosen.
    ///
    /// @invariant sum of weights must not overflow.
    /// @param samples The to be selected samples
    /// @param weights Weights that get chosen by their weight/probability. One weight can be greater 1.
    /// @param n Number of randomly chosen samples by weight.
    /// @return randomly selected samples by their weights
    pub fn random_choice_f64<'a, T>(samples: &'a [T], weights: &[f64], n: usize) -> Vec<&'a T> {
        if weights.len() == 0 || n == 0 {
            return Vec::new();
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut i: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut choices: Vec<&T> = Vec::with_capacity(n);
        let mut current_spoke: f64 = spin;

        for _ in 0..n {
            while accumulated_weights < current_spoke {
                i += 1;
                accumulated_weights += weights[i];
            }
            choices.push(&samples[i]);
            current_spoke += spoke_gap;
        }

        choices
    }

    /// Chooses n samples by their weights. The greater their weights the more likely they get chosen.
    /// The result gets saved directly in the samples argument.
    /// @invariant sum of weights must not overflow.
    /// @param samples The to be selected samples
    /// @param weights Weights that get chosen by their weight/probability. One weight can be greater 1.
    pub fn random_choice_in_place_f64<T: Clone>(samples: &mut [T], weights: &[f64]) {
        if weights.len() < 2 {
            return;
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let n: usize = weights.len();
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut j: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut current_spoke: f64 = spin;

        for i in 0..n {
            while accumulated_weights < current_spoke {
                j += 1;
                accumulated_weights += weights[j];
            }
            samples[i] = samples[j].clone();
            current_spoke += spoke_gap;
        }
    }

    pub fn random_choice_f32<'a, T>(samples: &'a [T], weights: &[f32], n: usize) -> Vec<&'a T> {
        if weights.len() == 0 || n == 0 {
            return Vec::new();
        }

        let sum: f32 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let spoke_gap: f32 = sum / n as f32;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f32() * spoke_gap;

        let mut i: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut choices: Vec<&T> = Vec::with_capacity(n);
        let mut current_spoke: f32 = spin;

        println!("sum: {}, gap: {}", sum, spoke_gap);

        for j in 0..n {
            while accumulated_weights < current_spoke {
                println!("accumulated_weights: {}, current_spoke: {}, spoke number: {}, i: {}", accumulated_weights, current_spoke, j, i);
                i += 1;
                accumulated_weights += weights[i];
            }
            choices.push(&samples[i]);
            current_spoke += spoke_gap;
        }

        choices
    }

    /*pub fn random_choice_f32<'a, T>(samples: &'a [T], weights: &[f32], n: usize) -> Vec<&'a T> {
        if weights.len() == 0 || n == 0 {
            return Vec::new();
        }

        let sum: f32 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let spoke_gap: f32 = sum / n as f32;

        // next_f32() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f32() * spoke_gap;

        let mut i: usize = 0;
        let mut interval_begin: f32 = 0.0;
        let mut interval_end = weights[0];
        let mut choices: Vec<&T> = Vec::with_capacity(n);
        let mut current_spoke: f32 = spin;

        for j in 0..n {
            current_spoke %= sum;

            while interval_end < current_spoke || interval_begin > current_spoke {
                i += 1;
                i %= weights.len();
                println!("interval_begin: {}, interval_end: {}, current_spoke: {}, spoke number: {}, i: {}",
                interval_begin, interval_end, current_spoke, j, i);
                println!("sum: {}, gap: {}", sum, spoke_gap);
                interval_end += weights[i];
                interval_end %= sum;
                interval_begin = interval_end - weights[i];
            }
            choices.push(&samples[i]);
            current_spoke += spoke_gap;
        }

        choices
    }*/

    pub fn random_choice_in_place_f32<T: Clone>(samples: &mut [T], weights: &[f32]) {
        if weights.len() < 2 {
            return;
        }

        let sum: f32 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let n: usize = weights.len();
        let spoke_gap: f32 = sum / n as f32;

        // next_f32() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f32() * spoke_gap;

        let mut j: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut current_spoke: f32 = spin;

        for i in 0..n {
            while accumulated_weights < current_spoke {
                j += 1;
                accumulated_weights += weights[j];
            }
            samples[i] = samples[j].clone();
            current_spoke += spoke_gap;
        }
    }
}<commit_msg>Fixing Array Out Of Bounds<commit_after>/// Implementation of Stochastic universal sampling
/// https://en.wikipedia.org/wiki/Stochastic_universal_sampling
/// Runtime: O(n)
/// Memory Usage: O(n) or O(1) (in place variant)

extern crate rand;

use self::rand::{thread_rng, Rng};

pub struct RandomChoice;

impl RandomChoice {
    /// Chooses n samples by their weights. The greater their weights the more likely they get chosen.
    ///
    /// @invariant sum of weights must not overflow.
    /// @param samples The to be selected samples
    /// @param weights Weights that get chosen by their weight/probability. One weight can be greater 1.
    /// @param n Number of randomly chosen samples by weight.
    /// @return randomly selected samples by their weights
    pub fn random_choice_f64<'a, T>(samples: &'a [T], weights: &[f64], n: usize) -> Vec<&'a T> {
        if weights.len() == 0 || n == 0 {
            return Vec::new();
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut i: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut choices: Vec<&T> = Vec::with_capacity(n);
        let mut current_spoke: f64 = spin;

        for _ in 0..n {
            while i < weights.len() && accumulated_weights < current_spoke {
                i += 1;
                accumulated_weights += weights[i];
            }
            choices.push(&samples[i]);
            current_spoke += spoke_gap;
        }

        choices
    }

    /// Chooses n samples by their weights. The greater their weights the more likely they get chosen.
    /// The result gets saved directly in the samples argument.
    /// @invariant sum of weights must not overflow.
    /// @param samples The to be selected samples
    /// @param weights Weights that get chosen by their weight/probability. One weight can be greater 1.
    pub fn random_choice_in_place_f64<T: Clone>(samples: &mut [T], weights: &[f64]) {
        if weights.len() < 2 {
            return;
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i);
        let n: usize = weights.len();
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut j: usize = 0;
        let mut accumulated_weights = weights[0];
        let mut current_spoke: f64 = spin;

        for i in 0..n {
            while j < weights.len() && accumulated_weights < current_spoke {
                j += 1;
                accumulated_weights += weights[j];
            }
            samples[i] = samples[j].clone();
            current_spoke += spoke_gap;
        }
    }

    pub fn random_choice_f32<'a, T>(samples: &'a [T], weights: &[f32], n: usize) -> Vec<&'a T> {
        if weights.len() == 0 || n == 0 {
            return Vec::new();
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i as f64);
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut i: usize = 0;
        let mut accumulated_weights = weights[0] as f64;
        let mut choices: Vec<&T> = Vec::with_capacity(n);
        let mut current_spoke: f64 = spin;

        for _ in 0..n {
            // add condition i < weights.len(), because float leads to inaccurate
            // calculations which can lead to i >= weights.len()
            while i < weights.len() && accumulated_weights < current_spoke {
                i += 1;
                accumulated_weights += weights[i] as f64;
            }
            choices.push(&samples[i]);
            current_spoke += spoke_gap;
        }

        choices
    }

    pub fn random_choice_in_place_f32<T: Clone>(samples: &mut [T], weights: &[f32]) {
        if weights.len() < 2 {
            return;
        }

        let sum: f64 = weights.iter().fold(0.0, |acc, &i| acc + i as f64);
        let n: usize = weights.len();
        let spoke_gap: f64 = sum / n as f64;

        // next_f64() ∈ [0.0, 1.0)
        let spin = thread_rng().next_f64() * spoke_gap;

        let mut j: usize = 0;
        let mut accumulated_weights = weights[0] as f64;
        let mut current_spoke: f64 = spin;

        for i in 0..n {
            while j < weights.len() && accumulated_weights < current_spoke {
                j += 1;
                accumulated_weights += weights[j] as f64;
            }
            samples[i] = samples[j].clone();
            current_spoke += spoke_gap;
        }
    }
}<|endoftext|>


<commit_before>extern crate rand;

use rand::{Rng, SeedableRng, XorShiftRng};
use std::cmp;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;

pub struct Corpus {
    seed: [u32; 4],
    max_context: usize,
    words: Vec<String>,
    table: HashMap<Vec<String>, BTreeMap<String, f64>>,
}

pub struct Chain<'a> {
    rng: XorShiftRng,
    history: VecDeque<String>,
    corpus: &'a Corpus,
}

impl Corpus {
    pub fn new(path: &Path, max_context: usize) -> io::Result<Corpus> {
        let mut f = try!(File::open(path));
        let mut buffer = String::new();
        try!(f.read_to_string(&mut buffer));

        let words: Vec<String> = buffer.split_whitespace()
            .map(|s| s.to_lowercase())
            .collect();

        let mut builder = HashMap::new();
        let mut history = VecDeque::new();
        history.push_back(words[0].clone());
        for word in &words[1..] {
            let hist_len = history.len();
            for skip in 0..cmp::min(hist_len, max_context) {
                let key: Vec<String> = history.iter()
                    .skip(skip)
                    .cloned()
                    .collect();
                let word_list = builder.entry(key).or_insert(vec![]);
                word_list.push(word.clone());
            }

            history.push_back(word.clone());
            if history.len() > max_context {
                history.pop_front();
            }
        }
        let mut table = HashMap::new();
        for (history, words) in builder.drain() {
            let mut word_count = HashMap::new();
            let total_words = words.len() as u32;
            for word in words {
                let count = word_count.entry(word).or_insert(0u32);
                *count += 1;
            }
            let mut word_probs = BTreeMap::new();
            for (word, count) in word_count.drain() {
                word_probs.insert(word, f64::from(count) / f64::from(total_words));
            }
            table.insert(history.into_iter().collect(), word_probs);
        }

        let mut rng = rand::thread_rng();
        let seed = [rng.gen(), rng.gen(), rng.gen(), rng.gen()];

        Ok(Corpus {
            seed: seed,
            max_context: max_context,
            words: words,
            table: table,
        })
    }

    pub fn seed(&mut self, seed: [u32; 4]) {
        self.seed = seed;
    }

    pub fn get_seed(&self) -> [u32; 4] {
        self.seed
    }

    pub fn words(&self, start: &[&str]) -> Chain {
        Chain {
            rng: SeedableRng::from_seed(self.seed),
            history: start.iter()
                .map(|&s| s.to_lowercase())
                .collect(),
            corpus: self,
        }
    }
}

impl<'a> Iterator for Chain<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        while self.history.len() > self.corpus.max_context {
            self.history.pop_front();
        }

        let hist_len = self.history.len();
        if hist_len > 0 {
            for skip in 0..hist_len {
                let key: Vec<String> = self.history.iter()
                    .skip(skip)
                    .cloned()
                    .collect();
                match self.corpus.table.get(&key) {
                    Some(word_probs) => {
                        let r = self.rng.gen::<f64>();
                        let mut acc = 0.0;
                        for (word, prob) in word_probs {
                            acc += *prob;
                            if acc > r {
                                self.history.push_back(word.clone());
                                return Some(word)
                            }
                        }
                        unreachable!("failed to pick a word")
                    }
                    None => {}
                }
            }
            unreachable!("failed to find a key")
        } else {
            let word = self.rng.choose(&self.corpus.words)
                .expect("corpus words shouldn't be empty");
            self.history.push_back(word.clone());
            Some(word)
        }
    }
}
<commit_msg>Fixed fallthrough bug<commit_after>extern crate rand;

use rand::{Rng, SeedableRng, XorShiftRng};
use std::cmp;
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;

pub struct Corpus {
    seed: [u32; 4],
    max_context: usize,
    words: Vec<String>,
    table: HashMap<Vec<String>, BTreeMap<String, f64>>,
}

pub struct Chain<'a> {
    rng: XorShiftRng,
    history: VecDeque<String>,
    corpus: &'a Corpus,
}

impl Corpus {
    pub fn new(path: &Path, max_context: usize) -> io::Result<Corpus> {
        let mut f = try!(File::open(path));
        let mut buffer = String::new();
        try!(f.read_to_string(&mut buffer));

        let words: Vec<String> = buffer.split_whitespace()
            .map(|s| s.to_lowercase())
            .collect();

        let mut builder = HashMap::new();
        let mut history = VecDeque::new();
        history.push_back(words[0].clone());
        for word in &words[1..] {
            let hist_len = history.len();
            for skip in 0..cmp::min(hist_len, max_context) {
                let key: Vec<String> = history.iter()
                    .skip(skip)
                    .cloned()
                    .collect();
                let word_list = builder.entry(key).or_insert(vec![]);
                word_list.push(word.clone());
            }

            history.push_back(word.clone());
            if history.len() > max_context {
                history.pop_front();
            }
        }
        let mut table = HashMap::new();
        for (history, words) in builder.drain() {
            let mut word_count = HashMap::new();
            let total_words = words.len() as u32;
            for word in words {
                let count = word_count.entry(word).or_insert(0u32);
                *count += 1;
            }
            let mut word_probs = BTreeMap::new();
            for (word, count) in word_count.drain() {
                word_probs.insert(word, f64::from(count) / f64::from(total_words));
            }
            table.insert(history.into_iter().collect(), word_probs);
        }

        let mut rng = rand::thread_rng();
        let seed = [rng.gen(), rng.gen(), rng.gen(), rng.gen()];

        Ok(Corpus {
            seed: seed,
            max_context: max_context,
            words: words,
            table: table,
        })
    }

    pub fn seed(&mut self, seed: [u32; 4]) {
        self.seed = seed;
    }

    pub fn get_seed(&self) -> [u32; 4] {
        self.seed
    }

    pub fn words(&self, start: &[&str]) -> Chain {
        Chain {
            rng: SeedableRng::from_seed(self.seed),
            history: start.iter()
                .map(|&s| s.to_lowercase())
                .collect(),
            corpus: self,
        }
    }
}

impl<'a> Iterator for Chain<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        while self.history.len() > self.corpus.max_context {
            self.history.pop_front();
        }

        let hist_len = self.history.len();
        if hist_len > 0 {
            for skip in 0..hist_len {
                let key: Vec<String> = self.history.iter()
                    .skip(skip)
                    .cloned()
                    .collect();
                match self.corpus.table.get(&key) {
                    Some(word_probs) => {
                        let r = self.rng.gen::<f64>();
                        let mut acc = 0.0;
                        for (word, prob) in word_probs {
                            acc += *prob;
                            if acc > r {
                                self.history.push_back(word.clone());
                                return Some(word)
                            }
                        }
                        unreachable!("failed to pick a word")
                    }
                    None => {}
                }
            }
        }

        let word = self.rng.choose(&self.corpus.words)
            .expect("corpus words shouldn't be empty");
        self.history.push_back(word.clone());
        Some(word)
    }
}
<|endoftext|>


<commit_before>//! Utitilty for writing nagios-style check scripts/plugins
//!
//! There are three things:
//!
//! * The `Status` enum for representing health status
//! * The `procfs` module, which contains rusty representations of some files
//!   from /proc
//! * A few scripts in the bin directory, which contain actual
//!   nagios-compatible scripts
//!
//! TODOs include
//!
//! * Nice logging, including some standard formats -- json and human would be
//!   nice
//! * Some way of easily standardizing command-line args
//! * Much of the code is hideous, and should not be

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate scan_fmt;
#[macro_use]
extern crate wrapped_enum;

extern crate libc;
extern crate regex;
extern crate rustc_serialize;

use std::process;
use std::fmt;
use std::str::FromStr;

pub mod linux;
pub mod procfs;
pub mod sys;
pub mod scripts;

/// All errors are TurbineErrors
#[derive(Debug)]
pub enum TurbineError {
    /// Represents an incorrect value passed in to a function
    UnknownValue(String),
}

/// All results are TurbineResults
pub type TurbineResult<T> = Result<T, TurbineError>;

/// Represent the nagios-ish error status of a script.
#[must_use]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, RustcDecodable)]
pub enum Status {
    /// Unexpected result
    Unknown,
    /// Everything is fine
    Ok,
    /// Something is weird, but don't necessary call anybody
    Warning,
    /// OMG CALL SOMEONE I DON'T CARE WHAT TIME IT IS
    Critical,
}

impl Status {
    /// Exit with a return code that indicates the state of the system
    pub fn exit(&self) -> ! {
        use self::Status::*;
        match *self {
            Ok => process::exit(0),
            Warning => process::exit(1),
            Critical => process::exit(2),
            Unknown => process::exit(3),
        }
    }

    /// The legal values for `from_str`
    pub fn str_values() -> [&'static str; 4] {
        ["ok", "warning", "critical", "unknown"]
    }
}

impl FromStr for Status {
    type Err = TurbineError;

    /// Primarily useful to construct from argv
    fn from_str(s: &str) -> TurbineResult<Status> {
        use Status::{Critical, Unknown, Warning};
        match s {
            "ok" => Ok(Status::Ok),
            "warning" => Ok(Warning),
            "critical" => Ok(Critical),
            "unknown" => Ok(Unknown),
            _ => Err(TurbineError::UnknownValue(
                format!("Unexpected exit status: {}", s),
            )),
        }
    }
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use Status::*;
        let msg = match *self {
            Ok => "OK",
            Unknown => "UNKNOWN",
            Warning => "WARNING",
            Critical => "CRITICAL",
        };
        write!(f, "{}", msg)
    }
}

#[test]
fn comparison_is_as_expected() {
    use Status::*;
    assert!(Ok < Critical);
    assert!(Ok < Warning);
    assert_eq!(std::cmp::max(Warning, Critical), Critical)
}
<commit_msg>Rename Turbine -> Tabin in remaining places<commit_after>//! Utitilty for writing nagios-style check scripts/plugins
//!
//! There are three things:
//!
//! * The `Status` enum for representing health status
//! * The `procfs` module, which contains rusty representations of some files
//!   from /proc
//! * A few scripts in the bin directory, which contain actual
//!   nagios-compatible scripts
//!
//! TODOs include
//!
//! * Nice logging, including some standard formats -- json and human would be
//!   nice
//! * Some way of easily standardizing command-line args
//! * Much of the code is hideous, and should not be

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate scan_fmt;
#[macro_use]
extern crate wrapped_enum;

extern crate libc;
extern crate regex;
extern crate rustc_serialize;

use std::process;
use std::fmt;
use std::str::FromStr;

pub mod linux;
pub mod procfs;
pub mod sys;
pub mod scripts;

/// All errors are TabinErrors
#[derive(Debug)]
pub enum TabinError {
    /// Represents an incorrect value passed in to a function
    UnknownValue(String),
}

impl fmt::Display for TabinError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self {
            &TabinError::UnknownValue(ref msg) => write!(f, "Unknown Value: {}", msg)
        }
    }
}

/// All results are TabinResults
pub type TabinResult<T> = Result<T, TabinError>;

/// Represent the nagios-ish error status of a script.
#[must_use]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, RustcDecodable)]
pub enum Status {
    /// Unexpected result
    Unknown,
    /// Everything is fine
    Ok,
    /// Something is weird, but don't necessary call anybody
    Warning,
    /// OMG CALL SOMEONE I DON'T CARE WHAT TIME IT IS
    Critical,
}

impl Status {
    /// Exit with a return code that indicates the state of the system
    pub fn exit(&self) -> ! {
        use self::Status::*;
        match *self {
            Ok => process::exit(0),
            Warning => process::exit(1),
            Critical => process::exit(2),
            Unknown => process::exit(3),
        }
    }

    /// The legal values for `from_str`
    pub fn str_values() -> [&'static str; 4] {
        ["ok", "warning", "critical", "unknown"]
    }
}

impl FromStr for Status {
    type Err = TabinError;

    /// Primarily useful to construct from argv
    fn from_str(s: &str) -> TabinResult<Status> {
        use Status::{Critical, Unknown, Warning};
        match s {
            "ok" => Ok(Status::Ok),
            "warning" => Ok(Warning),
            "critical" => Ok(Critical),
            "unknown" => Ok(Unknown),
            _ => Err(TabinError::UnknownValue(
                format!("Unexpected exit status: {}", s),
            )),
        }
    }
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use Status::*;
        let msg = match *self {
            Ok => "OK",
            Unknown => "UNKNOWN",
            Warning => "WARNING",
            Critical => "CRITICAL",
        };
        write!(f, "{}", msg)
    }
}

#[test]
fn comparison_is_as_expected() {
    use Status::*;
    assert!(Ok < Critical);
    assert!(Ok < Warning);
    assert_eq!(std::cmp::max(Warning, Critical), Critical)
}
<|endoftext|>


<commit_before>//! Open URLs in the web browsers available on a platform.
//!
//! Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library.
//!
//! Currently state of platform support is:
//!
//! * macos => default, as well as browsers listed under [Browser](enum.Browser.html)
//! * windows => default browser only
//! * linux => default browser only (uses $BROWSER env var, failing back to xdg-open, gvfs-open and
//! gnome-open, in that order)
//! * android => not supported right now
//! * ios => not supported right now
//!
//! Important note:
//!
//! * This library requires availability of browsers and a graphical environment during runtime
//! * `cargo test` will actually open the browser locally.
//!
//! # Examples
//!
//! ```no_run
//! use webbrowser;
//!
//! if webbrowser::open("http://github.com").is_ok() {
//!     // ...
//! }
//! ```

#[cfg(windows)]
extern crate widestring;
#[cfg(windows)]
extern crate winapi;

use std::default::Default;
use std::io::{Error, ErrorKind, Result};
use std::process::Output;
use std::str::FromStr;
use std::{error, fmt};

#[cfg(windows)]
use std::os::windows::process::ExitStatusExt;
#[cfg(windows)]
use std::process::ExitStatus;
#[cfg(windows)]
use std::ptr;

#[cfg(not(windows))]
use std::process::Command;

#[cfg(windows)]
use widestring::U16CString;

#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
/// Browser types available
pub enum Browser {
    ///Operating system's default browser
    Default,

    ///Mozilla Firefox
    Firefox,

    ///Microsoft's Internet Explorer
    InternetExplorer,

    ///Google Chrome
    Chrome,

    ///Opera
    Opera,

    ///Mac OS Safari
    Safari,
}

///The Error type for parsing a string into a Browser.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ParseBrowserError;

impl fmt::Display for ParseBrowserError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Invalid browser given")
    }
}

impl error::Error for ParseBrowserError {
    fn description(&self) -> &str {
        "invalid browser"
    }
}

impl Default for Browser {
    fn default() -> Self {
        Browser::Default
    }
}

impl fmt::Display for Browser {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Browser::Default => f.write_str("Default"),
            Browser::Firefox => f.write_str("Firefox"),
            Browser::InternetExplorer => f.write_str("Internet Explorer"),
            Browser::Chrome => f.write_str("Chrome"),
            Browser::Opera => f.write_str("Opera"),
            Browser::Safari => f.write_str("Safari"),
        }
    }
}

impl FromStr for Browser {
    type Err = ParseBrowserError;

    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        match s {
            "firefox" => Ok(Browser::Firefox),
            "default" => Ok(Browser::Default),
            "ie" | "internet explorer" | "internetexplorer" => Ok(Browser::InternetExplorer),
            "chrome" => Ok(Browser::Chrome),
            "opera" => Ok(Browser::Opera),
            "safari" => Ok(Browser::Safari),
            _ => Err(ParseBrowserError),
        }
    }
}

/// Opens the URL on the default browser of this platform
///
/// Returns Ok(..) so long as the browser invocation was successful. An Err(..) is returned only if
/// there was an error in running the command, or if the browser was not found.
///
/// Equivalent to:
/// ```no_run
/// # use webbrowser::{Browser, open_browser};
/// # let url = "http://example.com";
/// open_browser(Browser::Default, url);
/// ```
///
/// # Examples
/// ```no_run
/// use webbrowser;
///
/// if webbrowser::open("http://github.com").is_ok() {
///     // ...
/// }
/// ```
pub fn open(url: &str) -> Result<Output> {
    open_browser(Browser::Default, url)
}

/// Opens the specified URL on the specific browser (if available) requested. Return semantics are
/// the same as for [open](fn.open.html).
///
/// # Examples
/// ```no_run
/// use webbrowser::{open_browser, Browser};
///
/// if open_browser(Browser::Firefox, "http://github.com").is_ok() {
///     // ...
/// }
/// ```
pub fn open_browser(browser: Browser, url: &str) -> Result<Output> {
    open_browser_internal(browser, url)
}

/// Deal with opening of browsers on Windows, using [`ShellExecuteW`](
/// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew)
/// fucntion.
#[cfg(target_os = "windows")]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    use winapi::um::combaseapi::CoInitializeEx;
    use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
    use winapi::um::shellapi::ShellExecuteW;
    use winapi::um::winuser::SW_SHOWNORMAL;

    match browser {
        Browser::Default => {
            static OPEN: &[u16] = &['o' as u16, 'p' as u16, 'e' as u16, 'n' as u16, 0x0000];
            let url =
                U16CString::from_str(url).map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
            let code = unsafe {
                let _ = CoInitializeEx(
                    ptr::null_mut(),
                    COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
                );
                ShellExecuteW(
                    ptr::null_mut(),
                    OPEN.as_ptr(),
                    url.as_ptr(),
                    ptr::null(),
                    ptr::null(),
                    SW_SHOWNORMAL,
                ) as usize as i32
            };
            if code > 32 {
                Ok(Output {
                    status: ExitStatus::from_raw(0),
                    stdout: vec![],
                    stderr: vec![],
                })
            } else {
                Err(Error::last_os_error())
            }
        }
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "Only the default browser is supported on this platform right now",
        )),
    }
}

/// Deal with opening of browsers on Mac OS X, using `open` command
#[cfg(target_os = "macos")]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    let mut cmd = Command::new("open");
    match browser {
        Browser::Default => cmd.arg(url).output(),
        _ => {
            let app: Option<&str> = match browser {
                Browser::Firefox => Some("Firefox"),
                Browser::Chrome => Some("Google Chrome"),
                Browser::Opera => Some("Opera"),
                Browser::Safari => Some("Safari"),
                _ => None,
            };
            match app {
                Some(name) => cmd.arg("-a").arg(name).arg(url).output(),
                None => Err(Error::new(
                    ErrorKind::NotFound,
                    format!("Unsupported browser {:?}", browser),
                )),
            }
        }
    }
}

/// Deal with opening of browsers on Linux - currently supports only the default browser
///
/// The mechanism of opening the default browser is as follows:
/// 1. Attempt to use $BROWSER env var if available
/// 2. Attempt to open the url via xdg-open, gvfs-open, gnome-open, respectively, whichever works
///    first
#[cfg(target_os = "linux")]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    match browser {
        Browser::Default => open_on_linux_using_browser_env(url)
            .or_else(|_| -> Result<Output> { Command::new("xdg-open").arg(url).output() })
            .or_else(|_| -> Result<Output> { Command::new("gvfs-open").arg(url).output() })
            .or_else(|_| -> Result<Output> { Command::new("gnome-open").arg(url).output() }),
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "Only the default browser is supported on this platform right now",
        )),
    }
}

/// Open on Linux using the $BROWSER env var
#[cfg(target_os = "linux")]
fn open_on_linux_using_browser_env(url: &str) -> Result<Output> {
    let browsers = ::std::env::var("BROWSER")
        .map_err(|_| -> Error { Error::new(ErrorKind::NotFound, "BROWSER env not set") })?;
    for browser in browsers.split(':') {
        // $BROWSER can contain ':' delimited options, each representing a potential browser command line
        if !browser.is_empty() {
            // each browser command can have %s to represent URL, while %c needs to be replaced
            // with ':' and %% with '%'
            let cmdline = browser
                .replace("%s", url)
                .replace("%c", ":")
                .replace("%%", "%");
            let cmdarr: Vec<&str> = cmdline.split_whitespace().collect();
            let mut cmd = Command::new(&cmdarr[0]);
            if cmdarr.len() > 1 {
                cmd.args(&cmdarr[1..cmdarr.len()]);
            }
            if !browser.contains("%s") {
                // append the url as an argument only if it was not already set via %s
                cmd.arg(url);
            }
            if let Ok(output) = cmd.output() {
                return Ok(output);
            }
        }
    }
    Err(Error::new(
        ErrorKind::NotFound,
        "No valid command in $BROWSER",
    ))
}

#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
compile_error!("Only Windows, Mac OS and Linux are currently supported");

#[test]
fn test_open_default() {
    assert!(open("http://github.com").is_ok());
    assert!(open("http://github.com?dummy_query1=0&dummy_query2=ｎｏｎａｓｃｉｉ").is_ok());
}

#[test]
#[cfg(target_os = "macos")]
fn test_open_browser() {
    assert!(open_browser(Browser::Firefox, "http://github.com").is_ok());
}
<commit_msg>add support for the *BSD family<commit_after>//! Open URLs in the web browsers available on a platform.
//!
//! Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library.
//!
//! Currently state of platform support is:
//!
//! * macos => default, as well as browsers listed under [Browser](enum.Browser.html)
//! * windows => default browser only
//! * linux or *bsd => default browser only (uses $BROWSER env var, failing back to xdg-open, gvfs-open and
//! gnome-open, in that order)
//! * android => not supported right now
//! * ios => not supported right now
//!
//! Important note:
//!
//! * This library requires availability of browsers and a graphical environment during runtime
//! * `cargo test` will actually open the browser locally.
//!
//! # Examples
//!
//! ```no_run
//! use webbrowser;
//!
//! if webbrowser::open("http://github.com").is_ok() {
//!     // ...
//! }
//! ```

#[cfg(windows)]
extern crate widestring;
#[cfg(windows)]
extern crate winapi;

use std::default::Default;
use std::io::{Error, ErrorKind, Result};
use std::process::Output;
use std::str::FromStr;
use std::{error, fmt};

#[cfg(windows)]
use std::os::windows::process::ExitStatusExt;
#[cfg(windows)]
use std::process::ExitStatus;
#[cfg(windows)]
use std::ptr;

#[cfg(not(windows))]
use std::process::Command;

#[cfg(windows)]
use widestring::U16CString;

#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
/// Browser types available
pub enum Browser {
    ///Operating system's default browser
    Default,

    ///Mozilla Firefox
    Firefox,

    ///Microsoft's Internet Explorer
    InternetExplorer,

    ///Google Chrome
    Chrome,

    ///Opera
    Opera,

    ///Mac OS Safari
    Safari,
}

///The Error type for parsing a string into a Browser.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ParseBrowserError;

impl fmt::Display for ParseBrowserError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("Invalid browser given")
    }
}

impl error::Error for ParseBrowserError {
    fn description(&self) -> &str {
        "invalid browser"
    }
}

impl Default for Browser {
    fn default() -> Self {
        Browser::Default
    }
}

impl fmt::Display for Browser {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Browser::Default => f.write_str("Default"),
            Browser::Firefox => f.write_str("Firefox"),
            Browser::InternetExplorer => f.write_str("Internet Explorer"),
            Browser::Chrome => f.write_str("Chrome"),
            Browser::Opera => f.write_str("Opera"),
            Browser::Safari => f.write_str("Safari"),
        }
    }
}

impl FromStr for Browser {
    type Err = ParseBrowserError;

    fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
        match s {
            "firefox" => Ok(Browser::Firefox),
            "default" => Ok(Browser::Default),
            "ie" | "internet explorer" | "internetexplorer" => Ok(Browser::InternetExplorer),
            "chrome" => Ok(Browser::Chrome),
            "opera" => Ok(Browser::Opera),
            "safari" => Ok(Browser::Safari),
            _ => Err(ParseBrowserError),
        }
    }
}

/// Opens the URL on the default browser of this platform
///
/// Returns Ok(..) so long as the browser invocation was successful. An Err(..) is returned only if
/// there was an error in running the command, or if the browser was not found.
///
/// Equivalent to:
/// ```no_run
/// # use webbrowser::{Browser, open_browser};
/// # let url = "http://example.com";
/// open_browser(Browser::Default, url);
/// ```
///
/// # Examples
/// ```no_run
/// use webbrowser;
///
/// if webbrowser::open("http://github.com").is_ok() {
///     // ...
/// }
/// ```
pub fn open(url: &str) -> Result<Output> {
    open_browser(Browser::Default, url)
}

/// Opens the specified URL on the specific browser (if available) requested. Return semantics are
/// the same as for [open](fn.open.html).
///
/// # Examples
/// ```no_run
/// use webbrowser::{open_browser, Browser};
///
/// if open_browser(Browser::Firefox, "http://github.com").is_ok() {
///     // ...
/// }
/// ```
pub fn open_browser(browser: Browser, url: &str) -> Result<Output> {
    open_browser_internal(browser, url)
}

/// Deal with opening of browsers on Windows, using [`ShellExecuteW`](
/// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew)
/// fucntion.
#[cfg(target_os = "windows")]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    use winapi::um::combaseapi::CoInitializeEx;
    use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
    use winapi::um::shellapi::ShellExecuteW;
    use winapi::um::winuser::SW_SHOWNORMAL;

    match browser {
        Browser::Default => {
            static OPEN: &[u16] = &['o' as u16, 'p' as u16, 'e' as u16, 'n' as u16, 0x0000];
            let url =
                U16CString::from_str(url).map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
            let code = unsafe {
                let _ = CoInitializeEx(
                    ptr::null_mut(),
                    COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
                );
                ShellExecuteW(
                    ptr::null_mut(),
                    OPEN.as_ptr(),
                    url.as_ptr(),
                    ptr::null(),
                    ptr::null(),
                    SW_SHOWNORMAL,
                ) as usize as i32
            };
            if code > 32 {
                Ok(Output {
                    status: ExitStatus::from_raw(0),
                    stdout: vec![],
                    stderr: vec![],
                })
            } else {
                Err(Error::last_os_error())
            }
        }
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "Only the default browser is supported on this platform right now",
        )),
    }
}

/// Deal with opening of browsers on Mac OS X, using `open` command
#[cfg(target_os = "macos")]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    let mut cmd = Command::new("open");
    match browser {
        Browser::Default => cmd.arg(url).output(),
        _ => {
            let app: Option<&str> = match browser {
                Browser::Firefox => Some("Firefox"),
                Browser::Chrome => Some("Google Chrome"),
                Browser::Opera => Some("Opera"),
                Browser::Safari => Some("Safari"),
                _ => None,
            };
            match app {
                Some(name) => cmd.arg("-a").arg(name).arg(url).output(),
                None => Err(Error::new(
                    ErrorKind::NotFound,
                    format!("Unsupported browser {:?}", browser),
                )),
            }
        }
    }
}

/// Deal with opening of browsers on Linux and *BSD - currently supports only the default browser
///
/// The mechanism of opening the default browser is as follows:
/// 1. Attempt to use $BROWSER env var if available
/// 2. Attempt to open the url via xdg-open, gvfs-open, gnome-open, respectively, whichever works
///    first
#[cfg(any(
    target_os = "linux",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd"
))]
#[inline]
fn open_browser_internal(browser: Browser, url: &str) -> Result<Output> {
    match browser {
        Browser::Default => open_on_linux_using_browser_env(url)
            .or_else(|_| -> Result<Output> { Command::new("xdg-open").arg(url).output() })
            .or_else(|_| -> Result<Output> { Command::new("gvfs-open").arg(url).output() })
            .or_else(|_| -> Result<Output> { Command::new("gnome-open").arg(url).output() }),
        _ => Err(Error::new(
            ErrorKind::NotFound,
            "Only the default browser is supported on this platform right now",
        )),
    }
}
#[cfg(any(
    target_os = "linux",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd"
))]
fn open_on_linux_using_browser_env(url: &str) -> Result<Output> {
    let browsers = ::std::env::var("BROWSER")
        .map_err(|_| -> Error { Error::new(ErrorKind::NotFound, "BROWSER env not set") })?;
    for browser in browsers.split(':') {
        // $BROWSER can contain ':' delimited options, each representing a potential browser command line
        if !browser.is_empty() {
            // each browser command can have %s to represent URL, while %c needs to be replaced
            // with ':' and %% with '%'
            let cmdline = browser
                .replace("%s", url)
                .replace("%c", ":")
                .replace("%%", "%");
            let cmdarr: Vec<&str> = cmdline.split_whitespace().collect();
            let mut cmd = Command::new(&cmdarr[0]);
            if cmdarr.len() > 1 {
                cmd.args(&cmdarr[1..cmdarr.len()]);
            }
            if !browser.contains("%s") {
                // append the url as an argument only if it was not already set via %s
                cmd.arg(url);
            }
            if let Ok(output) = cmd.output() {
                return Ok(output);
            }
        }
    }
    Err(Error::new(
        ErrorKind::NotFound,
        "No valid command in $BROWSER",
    ))
}

#[cfg(not(any(
    target_os = "windows",
    target_os = "macos",
    target_os = "linux",
    target_os = "freebsd",
    target_os = "netbsd",
    target_os = "openbsd"
)))]
compile_error!("Only Windows, Mac OS, Linux and *BSD are currently supported");

#[test]
fn test_open_default() {
    assert!(open("http://github.com").is_ok());
    assert!(open("http://github.com?dummy_query1=0&dummy_query2=ｎｏｎａｓｃｉｉ").is_ok());
}

#[test]
#[cfg(target_os = "macos")]
fn test_open_browser() {
    assert!(open_browser(Browser::Firefox, "http://github.com").is_ok());
}
<|endoftext|>