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

enum Object<'a> {
    Entity(u64),
    Literal(Literal<'a>)
}

enum Literal<'a> {
    LangLiteral(&'a str, LangTag<'a>),
    StringLiteral(&'a str),
    BooleanLiteral(bool),
    LongLiteral(i64),
    DoubleLiteral(f64),
}

struct LangTag<'a> {
    lang_tag: &'a str
}

struct Predicate<'a> {
    predicate: &'a str,
}

enum Range {
    LangLiteralRange(Literal::LangLiteral, Literal::LangLiteral),
    StringLiteralRange(Literal::StringLiteral, Literal::StringLiteral),
    LongLiteralRange(Literal::LongLiteral, Literal::LongLiteral),
    DoubleLiteralRange(Literal::DoubleLiteral, Literal::DoubleLiteral),
}

struct Statement<'a> {
    subject: Entity,
    predicate: Predicate<'a>,
    object: Object<'a>,
    context: Entity,
}

pub const A: Predicate = Predicate("_a");
pub const DEFAULT: Entity = Object::Entity(0);

struct CollectionName<'a> {
    name: &'a str
}

trait LigatureStore {
    fn read_tx() -> dyn ReadTx;
    fn write_tx() -> dyn WriteTx;
//     fn <T>compute(fun: suspend (ReadTx) -> T): T {
// val readTx = this.readTx()
// try {
// return fn(readTx)
// } finally {
// if (readTx.is_open()) {
// readTx.cancel()
// }
// }
//}

// fn write(fn: suspend (WriteTx) -> Unit) {
// val writeTx = this.writeTx()
// try {
// return fn(writeTx)
// } finally {
// if (writeTx.is_open()) {
// writeTx.commit()
// }
// }
// }

    /**
     * Close connection with the Store.
     */
    fn close();

    fn is_open() -> bool;
}

trait ReadTx {
    /**
     * Returns a Flow of all existing collections.
     */
    fn collections() -> Stream<CollectionName>;

    /**
     * Returns a Flow of all existing collections that start with the given prefix.
     */
    fn collections_prefix(prefix: CollectionName) -> Flow<CollectionName>

    /**
     * Returns a Flow of all existing collections that are within the given range.
     * `from` is inclusive and `to` is exclusive.
     */
    fn collections_range(from: CollectionName, to: CollectionName) -> Flow<CollectionName>

    /**
     * Accepts nothing but returns a Flow of all Statements in the Collection.
     */
    fn all_statements(collection: CollectionName) -> Flow<Statement>

    /**
     * Is passed a pattern and returns a seq with all matching Statements.
     */
    fn match_statements(collection: CollectionName, subject: Option<Entity>, predicate: Option<Predicate>, object: Option<Object>, context: Option<Entity>) -> Stream<Statement>

    /**
     * Is passed a pattern and returns a seq with all matching Statements.
     */
    fn match_statements_range(collection: CollectionName, subject: Option<Entity>, predicate: Option<Predicate>, range: Option<Range>, context: Option<Entity>) -> Stream<Statement>

    /**
     * Cancels this transaction.
     */
    fn cancel();

    fn is_open() -> bool;
}

trait WriteTx {
    /**
     * Creates a collection with the given name or does nothing if the collection already exists.
     * Only useful for creating an empty collection.
     */
    fn create_collection(collection: CollectionName);

    /**
     * Deletes the collection of the name given and does nothing if the collection doesn't exist.
     */
    fn delete_collection(collection: CollectionName);

    /**
     * Returns a new, unique to this collection identifier in the form _:NUMBER
     */
    fn new_entity(collection: CollectionName) -> Entity;
    fn add_statement(collection: CollectionName, statement: Statement);
    fn remove_statement(collection: CollectionName, statement: Statement);

    /**
     * Commits this transaction.
     */
    fn commit();

    /**
     * Cancels this transaction.
     */
    fn cancel();

    fn is_open() -> bool;
}

/**
 * Accepts a String representing an identifier and returns true or false depending on if it is valid.
 */
fn valid_predicate(identifier: String) -> bool {
    return "[a-zA-Z_][^\\s\\(\\)\\[\\]\\{\\}'\"`<>\\\\]*".toRegex().matches(identifier)
}

/**
 * Accepts a String representing a lang tag and returns true or false depending on if it is valid.
 */
fn valid_lang_tag(lang_tag: String) -> bool {
    return "[a-zA-Z]+(-[a-zA-Z0-9]+)*".toRegex().matches(langTag)
}

#[cfg(test)]
mod tests {
    #[test]
    fn valid_identifier_tests() {
        assert_eq(valid_predicate(""), false);
        assert_eq(valid_predicate("http://localhost/people/7"), true);
        assert_eq(valid_predicate("http://localhost(/people/7"), false);
        assert_eq(valid_predicate("http://localhost /people/7"), false);
        assert_eq(valid_predicate("hello"), true);
        assert_eq(valid_predicate("_:"), true);
        assert_eq(valid_predicate("_:valid"), true);
        assert_eq(valid_predicate("_:1"), true);
        assert_eq(valid_predicate("_:1344"), true);
    }

    #[test]
    fn valid_lang_tag_tests() {
        assert_eq(valid_lang_tag(""), false);
        assert_eq(valid_lang_tag("en"), true);
        assert_eq(valid_lang_tag("en-"), false);
        assert_eq(valid_lang_tag("en-fr"), true);
        assert_eq(valid_lang_tag("en-fr-"), false);
        assert_eq(valid_lang_tag("en-fr-sp"), true);
        assert_eq(valid_lang_tag("ennnenefnk-dkfjkjfl-dfakjelfkjalkf-fakjeflkajlkfj"), true);
        assert_eq(valid_lang_tag("en-fr-ef "), false);
    }
}
<commit_msg>fixing Flow to Stream types and fixing tests, #100<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

enum Object<'a> {
    Entity(u64),
    Literal(Literal<'a>)
}

enum Literal<'a> {
    LangLiteral(&'a str, LangTag<'a>),
    StringLiteral(&'a str),
    BooleanLiteral(bool),
    LongLiteral(i64),
    DoubleLiteral(f64),
}

struct LangTag<'a> {
    lang_tag: &'a str
}

struct Predicate<'a> {
    predicate: &'a str,
}

enum Range {
    LangLiteralRange(Literal::LangLiteral, Literal::LangLiteral),
    StringLiteralRange(Literal::StringLiteral, Literal::StringLiteral),
    LongLiteralRange(Literal::LongLiteral, Literal::LongLiteral),
    DoubleLiteralRange(Literal::DoubleLiteral, Literal::DoubleLiteral),
}

struct Statement<'a> {
    subject: Entity,
    predicate: Predicate<'a>,
    object: Object<'a>,
    context: Entity,
}

pub const A: Predicate = Predicate("_a");
pub const DEFAULT: Entity = Object::Entity(0);

struct CollectionName<'a> {
    name: &'a str
}

trait LigatureStore {
    fn read_tx() -> dyn ReadTx;
    fn write_tx() -> dyn WriteTx;
//     fn <T>compute(fun: suspend (ReadTx) -> T): T {
// val readTx = this.readTx()
// try {
// return fn(readTx)
// } finally {
// if (readTx.is_open()) {
// readTx.cancel()
// }
// }
//}

// fn write(fn: suspend (WriteTx) -> Unit) {
// val writeTx = this.writeTx()
// try {
// return fn(writeTx)
// } finally {
// if (writeTx.is_open()) {
// writeTx.commit()
// }
// }
// }

    /**
     * Close connection with the Store.
     */
    fn close();

    fn is_open() -> bool;
}

trait ReadTx {
    /**
     * Returns a Stream of all existing collections.
     */
    fn collections() -> Stream<CollectionName>;

    /**
     * Returns a Stream of all existing collections that start with the given prefix.
     */
    fn collections_prefix(prefix: CollectionName) -> Stream<CollectionName>;

    /**
     * Returns a Stream of all existing collections that are within the given range.
     * `from` is inclusive and `to` is exclusive.
     */
    fn collections_range(from: CollectionName, to: CollectionName) -> Stream<CollectionName>;

    /**
     * Accepts nothing but returns a Stream of all Statements in the Collection.
     */
    fn all_statements(collection: CollectionName) -> Stream<Statement>;

    /**
     * Is passed a pattern and returns a seq with all matching Statements.
     */
    fn match_statements(collection: CollectionName, subject: Option<Entity>, predicate: Option<Predicate>, object: Option<Object>, context: Option<Entity>) -> Stream<Statement>;

    /**
     * Is passed a pattern and returns a seq with all matching Statements.
     */
    fn match_statements_range(collection: CollectionName, subject: Option<Entity>, predicate: Option<Predicate>, range: Option<Range>, context: Option<Entity>) -> Stream<Statement>;

    /**
     * Cancels this transaction.
     */
    fn cancel();

    fn is_open() -> bool;
}

trait WriteTx {
    /**
     * Creates a collection with the given name or does nothing if the collection already exists.
     * Only useful for creating an empty collection.
     */
    fn create_collection(collection: CollectionName);

    /**
     * Deletes the collection of the name given and does nothing if the collection doesn't exist.
     */
    fn delete_collection(collection: CollectionName);

    /**
     * Returns a new, unique to this collection identifier in the form _:NUMBER
     */
    fn new_entity(collection: CollectionName) -> Entity;
    fn add_statement(collection: CollectionName, statement: Statement);
    fn remove_statement(collection: CollectionName, statement: Statement);

    /**
     * Commits this transaction.
     */
    fn commit();

    /**
     * Cancels this transaction.
     */
    fn cancel();

    fn is_open() -> bool;
}

/**
 * Accepts a String representing an identifier and returns true or false depending on if it is valid.
 */
fn valid_predicate(identifier: &str) -> bool {
    return "[a-zA-Z_][^\\s\\(\\)\\[\\]\\{\\}'\"`<>\\\\]*".toRegex().matches(identifier)
}

/**
 * Accepts a String representing a lang tag and returns true or false depending on if it is valid.
 */
fn valid_lang_tag(lang_tag: &str) -> bool {
    return "[a-zA-Z]+(-[a-zA-Z0-9]+)*".toRegex().matches(langTag)
}

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

    #[test]
    fn valid_identifier_tests() {
        assert_eq!(valid_predicate(""), false);
        assert_eq!(valid_predicate("http://localhost/people/7"), true);
        assert_eq!(valid_predicate("http://localhost(/people/7"), false);
        assert_eq!(valid_predicate("http://localhost /people/7"), false);
        assert_eq!(valid_predicate("hello"), true);
        assert_eq!(valid_predicate("_:"), true);
        assert_eq!(valid_predicate("_:valid"), true);
        assert_eq!(valid_predicate("_:1"), true);
        assert_eq!(valid_predicate("_:1344"), true);
    }

    #[test]
    fn valid_lang_tag_tests() {
        assert_eq!(valid_lang_tag(""), false);
        assert_eq!(valid_lang_tag("en"), true);
        assert_eq!(valid_lang_tag("en-"), false);
        assert_eq!(valid_lang_tag("en-fr"), true);
        assert_eq!(valid_lang_tag("en-fr-"), false);
        assert_eq!(valid_lang_tag("en-fr-sp"), true);
        assert_eq!(valid_lang_tag("ennnenefnk-dkfjkjfl-dfakjelfkjalkf-fakjeflkajlkfj"), true);
        assert_eq!(valid_lang_tag("en-fr-ef "), false);
    }
}
<|endoftext|>


<commit_before>#![feature(no_std)]
#![no_std]

extern crate num;

pub mod create;
pub use create::*;

pub mod set;
pub use set::*;

pub mod mul;
pub use mul::*;

pub mod div;
pub use div::*;

pub mod length;
pub use length::*;

pub mod misc;
pub use misc::*;

pub mod transform;
pub use transform::*;
<commit_msg>updated to lastest rust nightly<commit_after>#![no_std]

extern crate num;

pub mod create;
pub use create::*;

pub mod set;
pub use set::*;

pub mod mul;
pub use mul::*;

pub mod div;
pub use div::*;

pub mod length;
pub use length::*;

pub mod misc;
pub use misc::*;

pub mod transform;
pub use transform::*;
<|endoftext|>


<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(&[("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(())
        }
    }
}
<commit_msg>add support for user.name/email<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("+"))?;

        self.save_original_user()?;
        if let Some(author) = authors.iter().next() {
            self.set_user(&author.name, &author.email)?;
        }

        Ok(authors)
    }

    fn save_original_user(&mut self) -> Result<()> {
        if let Ok(name) = self.config.get("user.name") {
            let key = namespaced("user.name");
            self.config
                .get(&key)
                .map(|_| ())
                .or_else(|_| self.config.set(&key, &name))?;
        }

        if let Ok(email) = self.config.get("user.email") {
            let key = namespaced("user.email");
            self.config
                .get(&key)
                .map(|_| ())
                .or_else(|_| self.config.set(&key, &email))?;
        }

        Ok(())
    }

    fn set_user(&mut self, name: &str, email: &str) -> Result<()> {
        self.config.set("user.name", name)?;
        self.config.set("user.email", email)?;

        Ok(())
    }

    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 std::ops::Index;

    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_solo() {
        let config = MockConfig::new(&[("git-together.authors.jh",
                                        "James Holden; jholden"),
                                       ("git-together.authors.nn",
                                        "Naomi Nagata; nnagata"),
                                       ("user.name", "Bobbie Draper"),
                                       ("user.email", "bdraper@mars.mil")]);
        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"]);
        assert_eq!(gt.config["user.name"], "James Holden");
        assert_eq!(gt.config["user.email"], "jholden@rocinante.com");
        assert_eq!(gt.config["git-together.user.name"], "Bobbie Draper");
        assert_eq!(gt.config["git-together.user.email"], "bdraper@mars.mil");
    }

    #[test]
    fn set_active_pair() {
        let config = MockConfig::new(&[("git-together.authors.jh",
                                        "James Holden; jholden"),
                                       ("git-together.authors.nn",
                                        "Naomi Nagata; nnagata"),
                                       ("user.name", "Bobbie Draper"),
                                       ("user.email", "bdraper@mars.mil")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.set_active(&["nn", "jh"]).unwrap();
        assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
        assert_eq!(gt.config["user.name"], "Naomi Nagata");
        assert_eq!(gt.config["user.email"], "nnagata@rocinante.com");
        assert_eq!(gt.config["git-together.user.name"], "Bobbie Draper");
        assert_eq!(gt.config["git-together.user.email"], "bdraper@mars.mil");
    }

    #[test]
    fn multiple_set_active() {
        let config = MockConfig::new(&[("git-together.authors.jh",
                                        "James Holden; jholden"),
                                       ("git-together.authors.nn",
                                        "Naomi Nagata; nnagata"),
                                       ("user.name", "Bobbie Draper"),
                                       ("user.email", "bdraper@mars.mil")]);
        let author_parser =
            AuthorParser { domain: Some("rocinante.com".into()) };
        let mut gt = GitTogether {
            config: config,
            author_parser: author_parser,
        };

        gt.set_active(&["nn"]).unwrap();
        gt.set_active(&["jh"]).unwrap();
        assert_eq!(gt.config["git-together.user.name"], "Bobbie Draper");
        assert_eq!(gt.config["git-together.user.email"], "bdraper@mars.mil");
    }

    #[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<'a> Index<&'a str> for MockConfig {
        type Output = String;

        fn index(&self, key: &'a str) -> &String {
            self.data.index(key)
        }
    }

    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>extern crate redis;

use std::time::Duration;

use redis::{Connection, RedisError, cmd};

fn duration_to_millis(d: &Duration) -> u64 {
    (d.subsec_nanos() / 1_000_000) as u64 + d.as_secs()
}

macro_rules! option_arg {
    ($disque: expr, $name: expr, $param: expr) => (
        match $param {
            Some(u) => $disque.arg($name).arg(u),
            None => &mut $disque,
        };
    )
}
pub struct Disque {
    connection: Connection,
}

impl Disque {
    pub fn open(url: &str) -> Result<Disque, RedisError> {
        let client = try!(redis::Client::open(url));
        let connection = try!(client.get_connection());
        Ok(Disque { connection: connection })
    }

    pub fn hello(&self) -> Result<Vec<Vec<u8>>, RedisError> {
        cmd("HELLO").query(&self.connection)
    }

    pub fn addjob(&self, queue_name: &[u8], job: &[u8], timeout: Duration,
            replicate: Option<usize>, delay: Option<Duration>,
            retry: Option<Duration>, ttl: Option<Duration>,
            maxlen: Option<usize>, async: bool,
            ) -> Result<String, RedisError> {
        let mut c = cmd("ADDJOB");
        c
            .arg(queue_name)
            .arg(job)
            .arg(duration_to_millis(&timeout));
        option_arg!(c, "REPLICATE", replicate);
        option_arg!(c, "DELAY", delay.map(|x| x.as_secs()));
        option_arg!(c, "RETRY", retry.map(|x| x.as_secs()));
        option_arg!(c, "TTL", ttl.map(|x| x.as_secs()));
        option_arg!(c, "MAXLEN", maxlen);
        if async { c.arg("ASYNC"); }
        c.query(&self.connection)
    }

    pub fn getjob_count(&self, nohang: bool, timeout: Option<Duration>,
            count: usize, withcounters: bool, queues: &[&[u8]]
            ) -> Result<Vec<Vec<Vec<u8>>>, RedisError> {
        let mut c = cmd("GETJOB");
        if nohang { c.arg("NOHANG"); }
        option_arg!(c, "TIMEOUT", timeout.map(|t| duration_to_millis(&t)));
        c.arg("COUNT").arg(count);
        if withcounters { c.arg("WITHCOUNTERS"); }
        c.arg("FROM");
        for queue in queues { c.arg(*queue); }
        c.query(&self.connection)
    }

    pub fn getjob(&self, nohang: bool, timeout: Option<Duration>,
            withcounters: bool, queues: &[&[u8]]
            ) -> Result<Option<Vec<Vec<u8>>>, RedisError> {
        let mut jobs = try!(self.getjob_count(nohang, timeout, 1, withcounters,
                    queues));
        Ok(jobs.pop())
    }

    pub fn ackjob(&self, jobids: &[&[u8]]) -> Result<bool, RedisError> {
        let mut c = cmd("ACKJOB");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn fastack(&self, jobids: &[&[u8]]) -> Result<usize, RedisError> {
        let mut c = cmd("FASTACK");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn working(&self, jobid: &[u8]) -> Result<Duration, RedisError> {
        let retry = try!(cmd("WORKING").arg(jobid).query(&self.connection));
        Ok(Duration::from_secs(retry))
    }

    pub fn nack(&self, jobids: &[&[u8]]) -> Result<usize, RedisError> {
        let mut c = cmd("NACK");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn info(&self) -> Result<Vec<u8>, RedisError> {
        cmd("INFO").query(&self.connection)
    }

    pub fn qlen(&self, queue_name: &[u8]) -> Result<usize, RedisError> {
        cmd("QLEN").arg(queue_name).query(&self.connection)
    }
}

#[cfg(test)]
fn conn() -> Disque {
    Disque::open("redis://127.0.0.1:7711/").unwrap()
}

#[test]
fn can_connect() {
    conn();
}

#[test]
fn hello() {
    let disque = conn();
    disque.hello().unwrap();
}

#[test]
fn addjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue", b"job", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(jobid.len(), 48);
    assert_eq!(&jobid[..2], "DI");
    assert_eq!(&jobid[46..], "SQ");
}

#[test]
fn getjob_count() {
    let disque = conn();
    let j1 = disque.addjob(b"queue1", b"job1", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j2 = disque.addjob(b"queue2", b"job2", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue3", b"job3", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let jobs = disque.getjob_count(false, None, 3, true, &[b"queue1", b"queue2"]).unwrap();
    assert_eq!(jobs.len(), 2);
    assert_eq!(jobs[0][0], b"queue1");
    assert_eq!(jobs[0][1], j1.into_bytes());
    assert_eq!(jobs[0][2], b"job1");
    assert_eq!(jobs[1][0], b"queue2");
    assert_eq!(jobs[1][1], j2.into_bytes());
    assert_eq!(jobs[1][2], b"job2");
}

#[test]
fn getjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue4", b"job4", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let job = disque.getjob(false, None, true, &[b"queue4", b"queue5"]).unwrap().unwrap();
    assert_eq!(job[0], b"queue4");
    assert_eq!(job[1], jobid.into_bytes());
    assert_eq!(job[2], b"job4");
}

#[test]
fn ackjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue6", b"job6", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.ackjob(&[jobid.as_bytes()]).unwrap());
    assert!(!disque.ackjob(&[jobid.as_bytes()]).unwrap());
    assert!(!disque.ackjob(&[jobid.as_bytes()]).unwrap());
}

#[test]
fn fastack() {
    let disque = conn();
    let jobid = disque.addjob(b"queue7", b"job7", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 1);
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 0);
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 0);
}

#[test]
fn working() {
    let disque = conn();
    let jobid = disque.addjob(b"queue8", b"job8", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.working(jobid.as_bytes()).unwrap().as_secs() > 0);
}

#[test]
fn nack() {
    let disque = conn();
    let j1 = disque.addjob(b"queue9", b"job9.1", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j2 = disque.addjob(b"queue9", b"job9.2", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j3 = disque.addjob(b"queue9", b"job9.3", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue9"]).unwrap().len(), 3);
    assert_eq!(disque.nack(&[j1.as_bytes(), j2.as_bytes(), j3.as_bytes()]).unwrap(), 3);
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue9"]).unwrap().len(), 3);
}

#[test]
fn info() {
    let disque = conn();
    disque.info().unwrap();
}

#[test]
fn qlen() {
    let disque = conn();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(disque.qlen(b"queue10").unwrap(), 3);
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue10"]).unwrap().len(), 3);
}
<commit_msg>Add QPEEK<commit_after>extern crate redis;

use std::time::Duration;

use redis::{Connection, RedisError, cmd};

fn duration_to_millis(d: &Duration) -> u64 {
    (d.subsec_nanos() / 1_000_000) as u64 + d.as_secs()
}

macro_rules! option_arg {
    ($disque: expr, $name: expr, $param: expr) => (
        match $param {
            Some(u) => $disque.arg($name).arg(u),
            None => &mut $disque,
        };
    )
}
pub struct Disque {
    connection: Connection,
}

impl Disque {
    pub fn open(url: &str) -> Result<Disque, RedisError> {
        let client = try!(redis::Client::open(url));
        let connection = try!(client.get_connection());
        Ok(Disque { connection: connection })
    }

    pub fn hello(&self) -> Result<Vec<Vec<u8>>, RedisError> {
        cmd("HELLO").query(&self.connection)
    }

    pub fn addjob(&self, queue_name: &[u8], job: &[u8], timeout: Duration,
            replicate: Option<usize>, delay: Option<Duration>,
            retry: Option<Duration>, ttl: Option<Duration>,
            maxlen: Option<usize>, async: bool,
            ) -> Result<String, RedisError> {
        let mut c = cmd("ADDJOB");
        c
            .arg(queue_name)
            .arg(job)
            .arg(duration_to_millis(&timeout));
        option_arg!(c, "REPLICATE", replicate);
        option_arg!(c, "DELAY", delay.map(|x| x.as_secs()));
        option_arg!(c, "RETRY", retry.map(|x| x.as_secs()));
        option_arg!(c, "TTL", ttl.map(|x| x.as_secs()));
        option_arg!(c, "MAXLEN", maxlen);
        if async { c.arg("ASYNC"); }
        c.query(&self.connection)
    }

    pub fn getjob_count(&self, nohang: bool, timeout: Option<Duration>,
            count: usize, withcounters: bool, queues: &[&[u8]]
            ) -> Result<Vec<Vec<Vec<u8>>>, RedisError> {
        let mut c = cmd("GETJOB");
        if nohang { c.arg("NOHANG"); }
        option_arg!(c, "TIMEOUT", timeout.map(|t| duration_to_millis(&t)));
        c.arg("COUNT").arg(count);
        if withcounters { c.arg("WITHCOUNTERS"); }
        c.arg("FROM");
        for queue in queues { c.arg(*queue); }
        c.query(&self.connection)
    }

    pub fn getjob(&self, nohang: bool, timeout: Option<Duration>,
            withcounters: bool, queues: &[&[u8]]
            ) -> Result<Option<Vec<Vec<u8>>>, RedisError> {
        let mut jobs = try!(self.getjob_count(nohang, timeout, 1, withcounters,
                    queues));
        Ok(jobs.pop())
    }

    pub fn ackjob(&self, jobids: &[&[u8]]) -> Result<bool, RedisError> {
        let mut c = cmd("ACKJOB");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn fastack(&self, jobids: &[&[u8]]) -> Result<usize, RedisError> {
        let mut c = cmd("FASTACK");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn working(&self, jobid: &[u8]) -> Result<Duration, RedisError> {
        let retry = try!(cmd("WORKING").arg(jobid).query(&self.connection));
        Ok(Duration::from_secs(retry))
    }

    pub fn nack(&self, jobids: &[&[u8]]) -> Result<usize, RedisError> {
        let mut c = cmd("NACK");
        for jobid in jobids { c.arg(*jobid); }
        c.query(&self.connection)
    }

    pub fn info(&self) -> Result<Vec<u8>, RedisError> {
        cmd("INFO").query(&self.connection)
    }

    pub fn qlen(&self, queue_name: &[u8]) -> Result<usize, RedisError> {
        cmd("QLEN").arg(queue_name).query(&self.connection)
    }

    pub fn qpeek(&self, queue_name: &[u8], count: i64) -> Result<Vec<Vec<Vec<u8>>>, RedisError> {
        cmd("QPEEK").arg(queue_name).arg(count).query(&self.connection)
    }
}

#[cfg(test)]
fn conn() -> Disque {
    Disque::open("redis://127.0.0.1:7711/").unwrap()
}

#[test]
fn can_connect() {
    conn();
}

#[test]
fn hello() {
    let disque = conn();
    disque.hello().unwrap();
}

#[test]
fn addjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue", b"job", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(jobid.len(), 48);
    assert_eq!(&jobid[..2], "DI");
    assert_eq!(&jobid[46..], "SQ");
}

#[test]
fn getjob_count() {
    let disque = conn();
    let j1 = disque.addjob(b"queue1", b"job1", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j2 = disque.addjob(b"queue2", b"job2", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue3", b"job3", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let jobs = disque.getjob_count(false, None, 3, true, &[b"queue1", b"queue2"]).unwrap();
    assert_eq!(jobs.len(), 2);
    assert_eq!(jobs[0][0], b"queue1");
    assert_eq!(jobs[0][1], j1.into_bytes());
    assert_eq!(jobs[0][2], b"job1");
    assert_eq!(jobs[1][0], b"queue2");
    assert_eq!(jobs[1][1], j2.into_bytes());
    assert_eq!(jobs[1][2], b"job2");
}

#[test]
fn getjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue4", b"job4", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let job = disque.getjob(false, None, true, &[b"queue4", b"queue5"]).unwrap().unwrap();
    assert_eq!(job[0], b"queue4");
    assert_eq!(job[1], jobid.into_bytes());
    assert_eq!(job[2], b"job4");
}

#[test]
fn ackjob() {
    let disque = conn();
    let jobid = disque.addjob(b"queue6", b"job6", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.ackjob(&[jobid.as_bytes()]).unwrap());
    assert!(!disque.ackjob(&[jobid.as_bytes()]).unwrap());
    assert!(!disque.ackjob(&[jobid.as_bytes()]).unwrap());
}

#[test]
fn fastack() {
    let disque = conn();
    let jobid = disque.addjob(b"queue7", b"job7", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 1);
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 0);
    assert!(disque.fastack(&[jobid.as_bytes()]).unwrap() == 0);
}

#[test]
fn working() {
    let disque = conn();
    let jobid = disque.addjob(b"queue8", b"job8", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert!(disque.working(jobid.as_bytes()).unwrap().as_secs() > 0);
}

#[test]
fn nack() {
    let disque = conn();
    let j1 = disque.addjob(b"queue9", b"job9.1", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j2 = disque.addjob(b"queue9", b"job9.2", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j3 = disque.addjob(b"queue9", b"job9.3", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue9"]).unwrap().len(), 3);
    assert_eq!(disque.nack(&[j1.as_bytes(), j2.as_bytes(), j3.as_bytes()]).unwrap(), 3);
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue9"]).unwrap().len(), 3);
}

#[test]
fn info() {
    let disque = conn();
    disque.info().unwrap();
}

#[test]
fn qlen() {
    let disque = conn();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    disque.addjob(b"queue10", b"job10", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(disque.qlen(b"queue10").unwrap(), 3);
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue10"]).unwrap().len(), 3);
}

#[test]
fn qpeek() {
    let disque = conn();
    let j1 = disque.addjob(b"queue11", b"job11.1", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    let j2 = disque.addjob(b"queue11", b"job11.2", Duration::from_secs(10), None, None, None, None, None, false).unwrap();
    assert_eq!(disque.qpeek(b"queue11", 10).unwrap(), vec![
                vec![
                b"queue11".to_vec(),
                j1.as_bytes().to_vec(),
                b"job11.1".to_vec(),
                ],
                vec![
                b"queue11".to_vec(),
                j2.as_bytes().to_vec(),
                b"job11.2".to_vec(),
                ],
            ]);
    assert_eq!(disque.qpeek(b"queue11", -10).unwrap(), vec![
                vec![
                b"queue11".to_vec(),
                j2.as_bytes().to_vec(),
                b"job11.2".to_vec(),
                ],
                vec![
                b"queue11".to_vec(),
                j1.as_bytes().to_vec(),
                b"job11.1".to_vec(),
                ],
            ]);
    assert_eq!(disque.getjob_count(false, None, 100, true, &[b"queue11"]).unwrap().len(), 2);
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

pub fn puzzle(input: &str) -> usize {
    0
}

#[derive(PartialEq, Debug)]
pub struct Bot {
    chip1: Option<usize>,
    chip2: Option<usize>,
    commands: Vec<(usize, usize)>
}

impl Bot {
    pub fn new() -> Bot {
        Bot { chip1: None, chip2: None, commands: vec![], }
    }

    pub fn has_two_chips(&self) -> bool {
        match (self.chip1, self.chip2) {
            (Some(_), Some(_)) => true,
            _ => false,
        }
    }

    pub fn receive_chip(&mut self, chip: usize) {
        if self.chip1.is_none() {
            self.chip1 = Some(chip);
        } else if self.chip2.is_none() {
            self.chip2 = Some(chip);
        } else {
            panic!("Cannot hold more than two chips");
        }
    }

    pub fn receive_command(&mut self, low_bot: usize, high_bot: usize) {
        self.commands.push((low_bot, high_bot));
    }
}

pub struct BotRouter {
    bots: HashMap<usize, Bot>,
}

impl BotRouter {
    pub fn exec_command(&mut self, input: &str) {
        let pieces: Vec<&str> = input.split_whitespace().collect();

        if pieces[0] == "value" {
            self.value_to_bot(
                pieces[5].parse().expect("can't parse bot key"),
                pieces[1].parse().expect("can't parse chip value")
            );
        } else if pieces[0] == "bot" {
            self.command_to_bot(
                pieces[1].parse().expect("can't parse bot key"),
                pieces[6].parse().expect("can't parse low bot key"),
                pieces[11].parse().expect("can't parse high bot key"),
            );
        }
    }

    pub fn value_to_bot(&mut self, bot_key: usize, value: usize) {
        let bot = self.bots.entry(bot_key).or_insert(Bot::new());
        bot.receive_chip(value);
    }

    pub fn command_to_bot(&mut self, bot_key: usize,
                          low_bot_key: usize, high_bot_key: usize) {
        let bot = self.bots.entry(bot_key).or_insert(Bot::new());
        bot.receive_command(low_bot_key, high_bot_key);
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn new_bot_does_not_have_two_chips() {
        let bot = Bot::new();
        assert!( ! bot.has_two_chips() );
    }

    #[test]
    fn bot_gets_a_chip_still_not_two() {
        let mut bot = Bot::new();
        bot.receive_chip(1);
        assert!( ! bot.has_two_chips() );
    }

    #[test]
    fn bot_gets_two_chips() {
        let mut bot = Bot::new();
        bot.receive_chip(1);
        bot.receive_chip(4);
        assert!(bot.has_two_chips());
    }

    #[test]
    fn router_can_give_values() {
        let mut br = BotRouter { bots: HashMap::new() };
        br.exec_command("value 5 goes to bot 2");
        assert_eq!(
            br.bots.get(&2),
            Some(&Bot { chip1: Some(5), chip2: None, commands: vec![] })
        );
    }

    #[test]
    fn router_can_give_commands_that_get_stored() {
        let mut br = BotRouter { bots: HashMap::new() };
        br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
        assert_eq!(
            br.bots.get(&2),
            Some(&Bot {
                chip1: None,
                chip2: None,
                commands: vec![(1, 0)]
            })
        );
    }

    // #[test]
    // fn router_adds_two_values_so_commands_get_executed() {
    //     let mut br = BotRouter { bots: HashMap::new() };
    //     br.exec_command("value 5 goes to bot 2");
    //     br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
    //     br.exec_command("value 2 goes to bot 2");
    //
    //     assert_eq!(
    //         br.bots.get(&2),
    //         Some(&Bot {
    //             chip1: None,
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    //
    //     assert_eq!(
    //         br.bots.get(&1),
    //         Some(&Bot {
    //             chip1: Some(2),
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    //
    //     assert_eq!(
    //         br.bots.get(&0),
    //         Some(&Bot {
    //             chip1: Some(5),
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    // }

    // #[test]
    // fn test_sample() {
    //     let mut br = BotRouter { bots: HashMap::new() };
    //     br.exec_command("value 5 goes to bot 2");
    //     br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
    //     br.exec_command("value 3 goes to bot 1");
    //     br.exec_command("bot 1 gives low to output 1 and high to bot 0");
    //     br.exec_command("bot 0 gives low to output 2 and high to output 0");
    //     br.exec_command("value 2 goes to bot 2");
    // }
}
<commit_msg>Make an assumption that a bot won't have more than one command<commit_after>use std::collections::HashMap;

pub fn puzzle(input: &str) -> usize {
    0
}

#[derive(PartialEq, Debug)]
pub struct Bot {
    chip1: Option<usize>,
    chip2: Option<usize>,
    low: usize,
    high: usize,
}

impl Bot {
    pub fn new() -> Bot {
        Bot { chip1: None, chip2: None, low: 0, high: 0 }
    }

    pub fn has_two_chips(&self) -> bool {
        match (self.chip1, self.chip2) {
            (Some(_), Some(_)) => true,
            _ => false,
        }
    }

    pub fn receive_chip(&mut self, chip: usize) {
        if self.chip1.is_none() {
            self.chip1 = Some(chip);
        } else if self.chip2.is_none() {
            self.chip2 = Some(chip);
        } else {
            panic!("Cannot hold more than two chips");
        }
    }

    pub fn receive_command(&mut self, low_bot: usize, high_bot: usize) {
        self.low = low_bot;
        self.high = high_bot;
    }
}

pub struct BotRouter {
    bots: HashMap<usize, Bot>,
}

impl BotRouter {
    pub fn exec_command(&mut self, input: &str) {
        let pieces: Vec<&str> = input.split_whitespace().collect();

        if pieces[0] == "value" {
            self.value_to_bot(
                pieces[5].parse().expect("can't parse bot key"),
                pieces[1].parse().expect("can't parse chip value")
            );
        } else if pieces[0] == "bot" {
            self.command_to_bot(
                pieces[1].parse().expect("can't parse bot key"),
                pieces[6].parse().expect("can't parse low bot key"),
                pieces[11].parse().expect("can't parse high bot key"),
            );
        }
    }

    pub fn value_to_bot(&mut self, bot_key: usize, value: usize) {
        let bot = self.bots.entry(bot_key).or_insert(Bot::new());
        bot.receive_chip(value);
    }

    pub fn command_to_bot(&mut self, bot_key: usize,
                          low_bot_key: usize, high_bot_key: usize) {
        let bot = self.bots.entry(bot_key).or_insert(Bot::new());
        bot.receive_command(low_bot_key, high_bot_key);
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn new_bot_does_not_have_two_chips() {
        let bot = Bot::new();
        assert!( ! bot.has_two_chips() );
    }

    #[test]
    fn bot_gets_a_chip_still_not_two() {
        let mut bot = Bot::new();
        bot.receive_chip(1);
        assert!( ! bot.has_two_chips() );
    }

    #[test]
    fn bot_gets_two_chips() {
        let mut bot = Bot::new();
        bot.receive_chip(1);
        bot.receive_chip(4);
        assert!(bot.has_two_chips());
    }

    #[test]
    fn router_can_give_values() {
        let mut br = BotRouter { bots: HashMap::new() };
        br.exec_command("value 5 goes to bot 2");
        assert_eq!(
            br.bots.get(&2),
            Some(&Bot { chip1: Some(5), chip2: None, low: 0, high: 0 })
        );
    }

    #[test]
    fn router_can_give_commands_that_get_stored() {
        let mut br = BotRouter { bots: HashMap::new() };
        br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
        assert_eq!(
            br.bots.get(&2),
            Some(&Bot {
                chip1: None,
                chip2: None,
                low: 1,
                high: 0,
            })
        );
    }

    // #[test]
    // fn router_adds_two_values_so_commands_get_executed() {
    //     let mut br = BotRouter { bots: HashMap::new() };
    //     br.exec_command("value 5 goes to bot 2");
    //     br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
    //     br.exec_command("value 2 goes to bot 2");
    //
    //     assert_eq!(
    //         br.bots.get(&2),
    //         Some(&Bot {
    //             chip1: None,
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    //
    //     assert_eq!(
    //         br.bots.get(&1),
    //         Some(&Bot {
    //             chip1: Some(2),
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    //
    //     assert_eq!(
    //         br.bots.get(&0),
    //         Some(&Bot {
    //             chip1: Some(5),
    //             chip2: None,
    //             commands: vec![]
    //         })
    //     );
    // }

    // #[test]
    // fn test_sample() {
    //     let mut br = BotRouter { bots: HashMap::new() };
    //     br.exec_command("value 5 goes to bot 2");
    //     br.exec_command("bot 2 gives low to bot 1 and high to bot 0");
    //     br.exec_command("value 3 goes to bot 1");
    //     br.exec_command("bot 1 gives low to output 1 and high to bot 0");
    //     br.exec_command("bot 0 gives low to output 2 and high to output 0");
    //     br.exec_command("value 2 goes to bot 2");
    // }
}
<|endoftext|>


<commit_before>//! Guessing of MIME types by file extension.
//!
//! Uses a static list of file-extension : MIME type mappings.
#![feature(os, path)]

extern crate mime;

use mime::Mime;

pub use mime_types::MIME_TYPES;

use std::ascii::AsciiExt;
use std::ffi::OsStr;
use std::path::Path;

mod mime_types;

/// Guess the MIME type of the `Path` by its extension.
///
/// If the given `Path` has no extension, or its extension has no known MIME type mapping,
/// then the MIME type is assumed to be `application/octet-stream`.
///
/// ##Note
/// **Guess** is the operative word here, as there are no guarantees that the contents of the file
/// that `path` points to match the MIME type associated with the path's extension.
///
/// Take care when processing files with assumptions based on the return value of this function.
pub fn guess_mime_type(path: &Path) -> Mime {
    let ext = path.extension().and_then(OsStr::to_str).unwrap_or("");

    get_mime_type(ext)
}

/// Get the MIME type associated with a file extension.
///
/// If there is no association for the extension, or `ext` is empty,
/// `application/octet-stream` is returned.
pub fn get_mime_type(search_ext: &str) -> Mime {
    get_mime_type_str(search_ext)
        .map(|mime| mime.parse::<Mime>().unwrap())
        .unwrap_or_else(octet_stream)
}

/// Get the MIME type string associated with a file extension.
///
///
/// `search_ext` is converted to lowercase for a case-insensitive binary search.
///
/// Returns `None` if `search_ext` is empty or an associated extension was not found.
pub fn get_mime_type_str(search_ext: &str) -> Option<&'static str> {
    if search_ext.is_empty() { return None; }

    let search_ext = search_ext.to_ascii_lowercase();

    MIME_TYPES.binary_search_by(|&(ext, _)| ext.cmp(&search_ext))
        .ok().map(|idx| MIME_TYPES[idx].1)
}

/// Get the MIME type for `application/octet-stream` (generic binary stream)
pub fn octet_stream() -> Mime {
    "application/octet-stream".parse().unwrap()
}

#[cfg(test)]
mod tests {
    use mime::Mime;
    use super::{get_mime_type, MIME_TYPES};

    #[test]
    fn test_mime_type_guessing() {
        assert_eq!(get_mime_type("gif").to_string(), "image/gif".to_string());
        assert_eq!(get_mime_type("txt").to_string(), "text/plain".to_string());
        assert_eq!(get_mime_type("blahblah").to_string(), "application/octet-stream".to_string());
    }

    #[test]
    fn test_are_extensions_sorted() {
        // To make binary search work, extensions need to be sorted in ascending order.
    	for (curr, next) in MIME_TYPES.iter().zip(MIME_TYPES.iter().skip(1)) {
    		assert!(curr <= next, "MIME type mappings are not sorted! Failed assert: {:?} <= {:?}", curr, next);
    	}
    }

    #[test]
    fn test_are_mime_types_parseable() {
        for &(_, mime) in MIME_TYPES {
            mime.parse::<Mime>().unwrap();
        }
    }
}
<commit_msg>Update for new Rust (works with stable, beta and nightly)<commit_after>//! Guessing of MIME types by file extension.
//!
//! Uses a static list of file-extension : MIME type mappings.

extern crate mime;

use mime::Mime;

pub use mime_types::MIME_TYPES;

use std::ascii::AsciiExt;
use std::ffi::OsStr;
use std::path::Path;

mod mime_types;

/// Guess the MIME type of the `Path` by its extension.
///
/// If the given `Path` has no extension, or its extension has no known MIME type mapping,
/// then the MIME type is assumed to be `application/octet-stream`.
///
/// ##Note
/// **Guess** is the operative word here, as there are no guarantees that the contents of the file
/// that `path` points to match the MIME type associated with the path's extension.
///
/// Take care when processing files with assumptions based on the return value of this function.
pub fn guess_mime_type(path: &Path) -> Mime {
    let ext = path.extension().and_then(OsStr::to_str).unwrap_or("");

    get_mime_type(ext)
}

/// Get the MIME type associated with a file extension.
///
/// If there is no association for the extension, or `ext` is empty,
/// `application/octet-stream` is returned.
pub fn get_mime_type(search_ext: &str) -> Mime {
    get_mime_type_str(search_ext)
        .map(|mime| mime.parse::<Mime>().unwrap())
        .unwrap_or_else(octet_stream)
}

/// Get the MIME type string associated with a file extension.
///
///
/// `search_ext` is converted to lowercase for a case-insensitive binary search.
///
/// Returns `None` if `search_ext` is empty or an associated extension was not found.
pub fn get_mime_type_str(search_ext: &str) -> Option<&'static str> {
    if search_ext.is_empty() { return None; }

    let search_ext = search_ext.to_ascii_lowercase();

    MIME_TYPES.binary_search_by(|&(ext, _)| ext.cmp(&search_ext))
        .ok().map(|idx| MIME_TYPES[idx].1)
}

/// Get the MIME type for `application/octet-stream` (generic binary stream)
pub fn octet_stream() -> Mime {
    "application/octet-stream".parse().unwrap()
}

#[cfg(test)]
mod tests {
    use mime::Mime;
    use super::{get_mime_type, MIME_TYPES};

    #[test]
    fn test_mime_type_guessing() {
        assert_eq!(get_mime_type("gif").to_string(), "image/gif".to_string());
        assert_eq!(get_mime_type("txt").to_string(), "text/plain".to_string());
        assert_eq!(get_mime_type("blahblah").to_string(), "application/octet-stream".to_string());
    }

    #[test]
    fn test_are_extensions_sorted() {
        // To make binary search work, extensions need to be sorted in ascending order.
    	for (curr, next) in MIME_TYPES.iter().zip(MIME_TYPES.iter().skip(1)) {
    		assert!(curr <= next, "MIME type mappings are not sorted! Failed assert: {:?} <= {:?}", curr, next);
    	}
    }

    #[test]
    fn test_are_mime_types_parseable() {
        for &(_, mime) in MIME_TYPES {
            mime.parse::<Mime>().unwrap();
        }
    }
}
<|endoftext|>


<commit_before>#[test]
fn it_works() {
}
<commit_msg>starting with Error, Config, FileHandle<commit_after>#![allow(unstable)]

extern crate "libforestdb-sys" as ffi;

use std::default::Default;
use std::error;
use std::ffi::{CString, c_str_to_bytes};
use std::mem;

#[derive(Copy)]
pub struct Error {code: i32}

impl Error {
    pub fn from_code(code: i32) -> Error {
        Error {code: code}
    }
}

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

    fn detail(&self) -> Option<String> {
        unsafe { String::from_utf8(c_str_to_bytes(&ffi::fdb_error_msg(self.code)).to_vec()).ok() }
    }
}

macro_rules! lift_error {
    ($e: expr) => (lift_error!($e, ()));
    ($e: expr, $r: expr) => ({
        let t = $e;
        match t {
            ffi::FDB_RESULT_SUCCESS => Ok($r),
            _ => return Err(Error::from_code(t))
        }
    })
}

macro_rules! try_fdb {
    ($e: expr) => ({
        let t = $e;
        match t {
            ffi::FDB_RESULT_SUCCESS => (),
            _ => return Err(Error::from_code(t))
        }
    })
}

#[derive(Copy)]
pub struct Config {
    raw: ffi::fdb_config
}

impl Config {
}

impl Default for Config {
    fn default() -> Config {
        Config {
            raw: unsafe { ffi::fdb_get_default_config() }
        }
    }
}

pub fn init(config: Config) -> Result<(), Error> {
    lift_error!(unsafe {ffi::fdb_init(mem::transmute(&config.raw))})
}

pub struct FileHandle {
    raw: *mut ffi::fdb_file_handle
}

impl FileHandle {
    fn from_raw(handle: *mut ffi::fdb_file_handle) -> FileHandle {
        FileHandle { raw: handle }
    }

    pub fn open(path: &Path, config: Config) -> Result<FileHandle, Error> {
        let mut handle: *mut ffi::fdb_file_handle = unsafe { mem::zeroed() };
        let c_path = CString::from_slice(path.as_vec());

        try_fdb!(unsafe { ffi::fdb_open(mem::transmute(&mut handle),
                                        c_path.as_ptr(),
                                        mem::transmute(&config.raw)) });
        Ok(FileHandle::from_raw(handle))
    }
}

impl Drop for FileHandle {
    fn drop(&mut self) {
        println!("Dropping");
        unsafe { ffi::fdb_close(self.raw); }
    }
}

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

    use std::default::Default;
    use std::error;

    use ffi;

    #[test]
    fn test_error_msg(){
        let err = Error::from_code(ffi::FDB_RESULT_OPEN_FAIL);
        assert_eq!("error opening file", (&err as &error::Error).detail().unwrap().as_slice());
    }

    #[test]
    fn test_init() {
        assert!(super::init(Default::default()).is_ok());
    }

    #[test]
    fn test_open_file() {
        assert!(super::init(Default::default()).is_ok());
        assert!(FileHandle::open(&Path::new("test1"), Default::default()).is_ok());
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "bench", feature(test))]

#[macro_use]
mod utils;
mod page_manager;
mod compressor;
pub mod index;

#[cfg(test)]
pub mod test_utils;

#[cfg(all(feature = "bench", test))]
extern crate test;
<commit_msg>made page_manager public<commit_after>#![cfg_attr(feature = "bench", feature(test))]

#[macro_use]
mod utils;
mod compressor;
pub mod page_manager;
pub mod index;

#[cfg(test)]
pub mod test_utils;

#[cfg(all(feature = "bench", test))]
extern crate test;
<|endoftext|>


<commit_before>//! ruma-signatures provides functionality for creating digital signatures according to the
//! [Matrix](https://matrix.org/) specification.

#[macro_use]
extern crate lazy_static;
extern crate rustc_serialize;
extern crate serde_json;
extern crate sodiumoxide;

use std::error::Error;
use std::fmt::Display;

use rustc_serialize::base64::{CharacterSet, Config, Newline, ToBase64};
use serde_json::{Value, to_string};
use sodiumoxide::init;
use sodiumoxide::crypto::sign::{SecretKey, Signature, sign_detached};

lazy_static! {
    static ref _LIBSODIUM_INIT: bool = init();
}

static BASE64_CONFIG: Config = Config {
    char_set: CharacterSet::Standard,
    newline: Newline::CRLF,
    pad: false,
    line_length: None,
};

/// An error produced when signing data fails.
#[derive(Debug)]
pub struct SigningError {
    message: String,
}

impl SigningError {
    pub fn new<T>(message: T) -> Self where T: Into<String> {
        SigningError {
            message: message.into(),
        }
    }
}

impl Error for SigningError {
    fn description(&self) -> &str {
        self.message.as_ref()
    }
}

impl Display for SigningError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

/// The algorithm used for signing.
#[derive(Debug)]
pub enum SigningAlgorithm {
    Ed25519,
}

/// A signing key, consisting of an algoritm, a secret key, and a key version.
#[derive(Debug)]
pub struct SigningKey {
    algorithm: SigningAlgorithm,
    key: SecretKey,
    version: String,
}

impl SigningKey {
    /// Initialize a new signing key.
    pub fn new(algorithm: SigningAlgorithm, key: [u8; 64], version: String) -> Self {
        SigningKey {
            algorithm: algorithm,
            key: SecretKey(key),
            version: version,
        }
    }

    /// Sign a JSON object.
    pub fn sign(&self, value: &Value) -> Result<Signature, SigningError> {
        if !value.is_object() {
            return Err(SigningError::new("JSON value must be a JSON object"));
        }

        let mut owned_value = value.clone();

        {
            let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
            hash.remove("signatures");
            hash.remove("unsigned");
        }

        let json = match to_string(&owned_value) {
            Ok(json) => json,
            Err(error) => return Err(SigningError::new(error.description())),
        };

        Ok(sign_detached(json.as_bytes(), &self.key))
    }

    /// Sign and Base64 encode a JSON object.
    pub fn sign_and_base64_encode(&self, value: &Value) -> Result<String, SigningError> {
        let signature = try!(self.sign(value));

        Ok(signature.as_ref().to_base64(BASE64_CONFIG))
    }
}

#[cfg(test)]
mod test {
    use rustc_serialize::base64::FromBase64;
    use serde_json::from_str;
    use sodiumoxide::crypto::sign::{SecretKey, Seed, keypair_from_seed};

    use super::{SigningAlgorithm, SigningKey};

    #[test]
    fn empty_json() {
        let seed_vec = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1".from_base64().unwrap();
        let seed = Seed::from_slice(&seed_vec[..]).unwrap();
        let (_pubkey, seckey) = keypair_from_seed(&seed);
        let SecretKey(raw_seckey) = seckey;
        let signing_key = SigningKey::new(SigningAlgorithm::Ed25519, raw_seckey, "1".to_owned());
        let value = from_str("{}").unwrap();
        let actual = signing_key.sign_and_base64_encode(&value).unwrap();
        assert_eq!(
            actual,
            "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
        );
    }
}
<commit_msg>Use a SigningKey to produce a Signature.<commit_after>//! ruma-signatures provides functionality for creating digital signatures according to the
//! [Matrix](https://matrix.org/) specification.

#[macro_use]
extern crate lazy_static;
extern crate rustc_serialize;
extern crate serde_json;
extern crate sodiumoxide;

use std::collections::HashSet;
use std::error::Error as StdError;
use std::fmt::{Display, Formatter, Result as FmtResult};

use rustc_serialize::base64::{CharacterSet, Config, Newline, ToBase64};
use serde_json::{Value, to_string};
use sodiumoxide::init;
use sodiumoxide::crypto::sign::{SecretKey, Signature as SodiumSignature, sign_detached};

lazy_static! {
    static ref _LIBSODIUM_INIT: bool = init();
}

static BASE64_CONFIG: Config = Config {
    char_set: CharacterSet::Standard,
    newline: Newline::CRLF,
    pad: false,
    line_length: None,
};

/// An error produced when signing data fails.
#[derive(Debug)]
pub struct Error {
    message: String,
}

impl Error {
    pub fn new<T>(message: T) -> Self where T: Into<String> {
        Error {
            message: message.into(),
        }
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        &self.message
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        write!(f, "{}", self.message)
    }
}

/// A single digital signature.
///
/// Generated from `SigningKey`.
#[derive(Debug)]
pub struct Signature {
    algorithm: SigningAlgorithm,
    signature: SodiumSignature,
    version: String,
}

/// A set of signatures created by a single homeserver.
pub type SignatureSet = HashSet<Signature>;

/// The algorithm used for signing.
#[derive(Clone, Copy, Debug)]
pub enum SigningAlgorithm {
    /// The Ed25519 digital signature algorithm.
    Ed25519,
}

/// A signing key, consisting of an algorithm, a secret key, and a key version.
#[derive(Debug)]
pub struct SigningKey {
    algorithm: SigningAlgorithm,
    key: SecretKey,
    version: String,
}

impl Signature {
    /// The algorithm used to generate the signature.
    pub fn algorithm(&self) -> SigningAlgorithm {
        self.algorithm
    }

    /// The raw bytes of the signature.
    pub fn as_bytes(&self) -> &[u8] {
        self.signature.as_ref()
    }

    /// A Base64 encoding of the signature.
    pub fn base64(&self) -> String {
        self.signature.as_ref().to_base64(BASE64_CONFIG)
    }

    /// A string containing the signature algorithm and the key "version" separated by a colon.
    pub fn id(&self) -> String {
        format!("ed25519:{}", self.version())
    }

    /// The "version" of the key used for this signature.
    ///
    /// Versions are used as an identifier to distinguish signatures generated from different keys
    /// but using the same algorithm on the same homeserver.
    pub fn version(&self) -> &str {
        &self.version
    }
}

impl SigningKey {
    /// Initialize a new signing key.
    ///
    /// # Parameters
    ///
    /// * algorithm: The digital signature algorithm to use.
    /// * key: A 64-byte secret key.
    /// * version: The "version" of the key used for this signature.
    ///   Versions are used as an identifier to distinguish signatures generated from different keys
    ///   but using the same algorithm on the same homeserver.
    pub fn new(algorithm: SigningAlgorithm, key: [u8; 64], version: String) -> Self {
        SigningKey {
            algorithm: algorithm,
            key: SecretKey(key),
            version: version,
        }
    }

    /// Sign a JSON object.
    pub fn sign(&self, value: &Value) -> Result<Signature, Error> {
        if !value.is_object() {
            return Err(Error::new("JSON value must be a JSON object"));
        }

        let mut owned_value = value.clone();

        {
            let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
            hash.remove("signatures");
            hash.remove("unsigned");
        }

        let json = match to_string(&owned_value) {
            Ok(json) => json,
            Err(error) => return Err(Error::new(error.description())),
        };

        Ok(Signature {
            algorithm: self.algorithm,
            signature: sign_detached(json.as_bytes(), &self.key),
            version: self.version.clone(),
        })
    }
}

#[cfg(test)]
mod test {
    use rustc_serialize::base64::FromBase64;
    use serde_json::from_str;
    use sodiumoxide::crypto::sign::{SecretKey, Seed, keypair_from_seed};

    use super::{SigningAlgorithm, SigningKey};

    #[test]
    fn empty_json() {
        let seed_vec = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1".from_base64().unwrap();
        let seed = Seed::from_slice(&seed_vec[..]).unwrap();
        let (_pubkey, seckey) = keypair_from_seed(&seed);
        let SecretKey(raw_seckey) = seckey;
        let signing_key = SigningKey::new(SigningAlgorithm::Ed25519, raw_seckey, "1".to_owned());
        let value = from_str("{}").unwrap();
        let actual = signing_key.sign(&value).unwrap().base64();
        assert_eq!(
            actual,
            "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
        );
    }
}
<|endoftext|>


<commit_before>#![crate_name = "locale"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![feature(core, env, path, io, std_misc)]

use std::old_io::fs::PathExtensions;
use std::old_io::{IoResult, File, BufferedReader};
use std::env::var_string;
use std::num::{Int, Float};
use std::fmt::Display;

/// The directory inside which locale files are found.
///
/// For example, the set of Korean files will be in
/// `/usr/share/locale/ko`.
static LOCALE_DIR: &'static str = "/usr/share/locale";

#[derive(Debug, Clone)]
enum LocaleType {
    Numeric, Time,
}

fn find_locale_path(locale_type: LocaleType) -> Option<Path> {
    if let Ok(numeric_path) = var_string("LC_ALL") {
        let path = Path::new(numeric_path);
        if path.exists() {
            return Some(path);
        }
    }

    let file_name = match locale_type {
        LocaleType::Numeric => "LC_NUMERIC",
        LocaleType::Time    => "LC_TIME",
    };

    if let Ok(numeric_path) = var_string(file_name) {
        let path = Path::new(numeric_path);
        if path.exists() {
            return Some(path);
        }
    }

    if let Ok(lang) = var_string("LANG") {
        let path = Path::new(LOCALE_DIR).join(Path::new(lang)).join(Path::new(file_name));

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

    None
}

#[derive(Debug, Clone)]
pub struct Numeric {
    pub decimal_sep: String,
    pub thousands_sep: String,
}

impl Numeric {
    pub fn load_user_locale() -> IoResult<Numeric> {
        let path = find_locale_path(LocaleType::Numeric);

        if let Some(path) = path {
            let mut file = BufferedReader::new(File::open(&path));
            let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();

            Ok(Numeric {
                decimal_sep: lines[0].trim().to_string(),
                thousands_sep: lines[1].trim().to_string(),
            })
        }
        else {
            return Ok(Numeric::english());
        }
    }

    pub fn english() -> Numeric {
        Numeric::new(".", ",")
    }

    pub fn new(decimal_sep: &str, thousands_sep: &str) -> Numeric {
        Numeric {
            decimal_sep: decimal_sep.to_string(),
            thousands_sep: thousands_sep.to_string(),
        }
    }

    pub fn format_int<I: Int + Display>(&self, input: I) -> String {
        let s = input.to_string();
        let mut buf = String::new();

        for (i, c) in s.chars().enumerate() {
            buf.push(c);
            if (s.len() - i - 1) % 3 == 0 && i != s.len() - 1 {
                buf.push_str(&self.thousands_sep[]);
            }
        }

        buf
    }

    pub fn format_float<F: Float>(&self, input: F, decimal_places: usize) -> String {
        use std::num::strconv;
        strconv::float_to_str_common(input, 10, false, strconv::SignFormat::SignNone, strconv::SignificantDigits::DigExact(decimal_places), strconv::ExponentFormat::ExpNone, false).0.replace(".", self.decimal_sep.as_slice())
    }
}

// ---- time stuff ---

#[derive(Debug, Clone)]
pub struct Time {
    month_names: Vec<String>,
    long_month_names: Vec<String>,
    day_names: Vec<String>,
    long_day_names: Vec<String>,
}

impl Time {
    pub fn load_user_locale() -> IoResult<Time> {
        let path = find_locale_path(LocaleType::Time);

        if let Some(path) = path {
            let mut file = BufferedReader::new(File::open(&path));
            let mut iter = file.lines().map(|x| x.unwrap().trim().to_string());

            let month_names      = iter.by_ref().take(12).collect();
            let long_month_names = iter.by_ref().take(12).collect();
            let day_names        = iter.by_ref().take(7).collect();
            let long_day_names   = iter.by_ref().take(7).collect();

            Ok(Time {
                month_names:      month_names,
                long_month_names: long_month_names,
                day_names:        day_names,
                long_day_names:   long_day_names,
            })
        }
        else {
            return Ok(Time::english());
        }
    }

    pub fn english() -> Time {
        Time {
            month_names: vec![
                "Jan".to_string(),  "Feb".to_string(),  "Mar".to_string(),
                "Apr".to_string(),  "May".to_string(),  "Jun".to_string(),
                "Jul".to_string(),  "Aug".to_string(),  "Sep".to_string(),
                "Oct".to_string(),  "Nov".to_string(),  "Dec".to_string(),
            ],
            long_month_names: vec![
                "January".to_string(),    "February".to_string(),
                "March".to_string(),      "April".to_string(),
                "May".to_string(),        "June".to_string(),
                "July".to_string(),       "August".to_string(),
                "September".to_string(),  "October".to_string(),
                "November".to_string(),   "December".to_string(),
            ],
            day_names: vec![
                "Sun".to_string(),
                "Mon".to_string(),  "Tue".to_string(),  "Wed".to_string(),
                "Thu".to_string(),  "Fri".to_string(),  "Sat".to_string(),
            ],
            long_day_names: vec![
                "Sunday".to_string(),
                "Monday".to_string(),    "Tuesday".to_string(),  "Wednesday".to_string(),
                "Thursday".to_string(),  "Friday".to_string(),   "Saturday".to_string(),
            ],
        }
    }

    pub fn long_month_name(&self, months_from_january: usize) -> String {
        self.long_month_names[months_from_january].clone()
    }

    pub fn short_month_name(&self, months_from_january: usize) -> String {
        self.month_names[months_from_january].clone()
    }

    pub fn long_day_name(&self, days_from_sunday: usize) -> String {
        self.day_names[days_from_sunday].clone()
    }

    pub fn short_day_name(&self, days_from_sunday: usize) -> String {
        self.day_names[days_from_sunday].clone()
    }

}

// ---- tests ----

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

    #[test]
    fn thousands_separator() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("1=234=567".to_string(), numeric_options.format_int(1234567))
    }

    #[test]
    fn thousands_separator_2() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("123=456".to_string(), numeric_options.format_int(123456))
    }

    #[test]
    fn thousands_separator_3() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("12=345=678".to_string(), numeric_options.format_int(12345678))
    }
}
<commit_msg>Incomplete documentation<commit_after>#![crate_name = "locale"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![feature(core, env, path, io, std_misc)]

//! Localisation is hard.
//!
//! Getting your program to work well in multiple languages is a world fraught with edge-cases,
//! minor grammatical errors, and most importantly, subtle things that don't map over well that you
//! have absolutely no idea are different in other cultures.
//!
//! Many people are aware of the simpler ones, such as whether to use decimal points or decimal
//! commas, or that the names of the months are different in other languages. But there are also
//! different ways to format dates and times, or variations on what day the week begins. It's
//! perfectly possible to write your program unaware of how these things have to be changed at all,
//! and that's why it's so hard.

use std::old_io::fs::PathExtensions;
use std::old_io::{IoResult, File, BufferedReader};
use std::env::var_string;
use std::num::{Int, Float};
use std::fmt::Display;

/// The directory inside which locale files are found.
///
/// For example, the set of Korean files will be in
/// `/usr/share/locale/ko`.
static LOCALE_DIR: &'static str = "/usr/share/locale";

#[derive(Debug, Clone)]
enum LocaleType {
    Numeric, Time,
}

fn find_locale_path(locale_type: LocaleType) -> Option<Path> {
    if let Ok(numeric_path) = var_string("LC_ALL") {
        let path = Path::new(numeric_path);
        if path.exists() {
            return Some(path);
        }
    }

    let file_name = match locale_type {
        LocaleType::Numeric => "LC_NUMERIC",
        LocaleType::Time    => "LC_TIME",
    };

    if let Ok(numeric_path) = var_string(file_name) {
        let path = Path::new(numeric_path);
        if path.exists() {
            return Some(path);
        }
    }

    if let Ok(lang) = var_string("LANG") {
        let path = Path::new(LOCALE_DIR).join(Path::new(lang)).join(Path::new(file_name));

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

    None
}

/// Information on how to format numbers.
#[derive(Debug, Clone)]
pub struct Numeric {
    /// The punctuation that separates the decimal part of a non-integer number. Usually a decimal
    /// point or a decimal comma.
    pub decimal_sep: String,

    /// The punctuation that separates groups of digits in long numbers.
    pub thousands_sep: String,
}

impl Numeric {
    pub fn load_user_locale() -> IoResult<Numeric> {
        let path = find_locale_path(LocaleType::Numeric);

        if let Some(path) = path {
            let mut file = BufferedReader::new(File::open(&path));
            let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();

            Ok(Numeric {
                decimal_sep: lines[0].trim().to_string(),
                thousands_sep: lines[1].trim().to_string(),
            })
        }
        else {
            return Ok(Numeric::english());
        }
    }

    pub fn english() -> Numeric {
        Numeric::new(".", ",")
    }

    pub fn new(decimal_sep: &str, thousands_sep: &str) -> Numeric {
        Numeric {
            decimal_sep: decimal_sep.to_string(),
            thousands_sep: thousands_sep.to_string(),
        }
    }

    pub fn format_int<I: Int + Display>(&self, input: I) -> String {
        let s = input.to_string();
        let mut buf = String::new();

        for (i, c) in s.chars().enumerate() {
            buf.push(c);
            if (s.len() - i - 1) % 3 == 0 && i != s.len() - 1 {
                buf.push_str(&self.thousands_sep[]);
            }
        }

        buf
    }

    pub fn format_float<F: Float>(&self, input: F, decimal_places: usize) -> String {
        use std::num::strconv;
        strconv::float_to_str_common(input, 10, false, strconv::SignFormat::SignNone, strconv::SignificantDigits::DigExact(decimal_places), strconv::ExponentFormat::ExpNone, false).0.replace(".", self.decimal_sep.as_slice())
    }
}

// ---- time stuff ---

#[derive(Debug, Clone)]
pub struct Time {
    month_names: Vec<String>,
    long_month_names: Vec<String>,
    day_names: Vec<String>,
    long_day_names: Vec<String>,
}

impl Time {
    pub fn load_user_locale() -> IoResult<Time> {
        let path = find_locale_path(LocaleType::Time);

        if let Some(path) = path {
            let mut file = BufferedReader::new(File::open(&path));
            let mut iter = file.lines().map(|x| x.unwrap().trim().to_string());

            let month_names      = iter.by_ref().take(12).collect();
            let long_month_names = iter.by_ref().take(12).collect();
            let day_names        = iter.by_ref().take(7).collect();
            let long_day_names   = iter.by_ref().take(7).collect();

            Ok(Time {
                month_names:      month_names,
                long_month_names: long_month_names,
                day_names:        day_names,
                long_day_names:   long_day_names,
            })
        }
        else {
            return Ok(Time::english());
        }
    }

    pub fn english() -> Time {
        Time {
            month_names: vec![
                "Jan".to_string(),  "Feb".to_string(),  "Mar".to_string(),
                "Apr".to_string(),  "May".to_string(),  "Jun".to_string(),
                "Jul".to_string(),  "Aug".to_string(),  "Sep".to_string(),
                "Oct".to_string(),  "Nov".to_string(),  "Dec".to_string(),
            ],
            long_month_names: vec![
                "January".to_string(),    "February".to_string(),
                "March".to_string(),      "April".to_string(),
                "May".to_string(),        "June".to_string(),
                "July".to_string(),       "August".to_string(),
                "September".to_string(),  "October".to_string(),
                "November".to_string(),   "December".to_string(),
            ],
            day_names: vec![
                "Sun".to_string(),
                "Mon".to_string(),  "Tue".to_string(),  "Wed".to_string(),
                "Thu".to_string(),  "Fri".to_string(),  "Sat".to_string(),
            ],
            long_day_names: vec![
                "Sunday".to_string(),
                "Monday".to_string(),    "Tuesday".to_string(),  "Wednesday".to_string(),
                "Thursday".to_string(),  "Friday".to_string(),   "Saturday".to_string(),
            ],
        }
    }

    pub fn long_month_name(&self, months_from_january: usize) -> String {
        self.long_month_names[months_from_january].clone()
    }

    pub fn short_month_name(&self, months_from_january: usize) -> String {
        self.month_names[months_from_january].clone()
    }

    pub fn long_day_name(&self, days_from_sunday: usize) -> String {
        self.day_names[days_from_sunday].clone()
    }

    pub fn short_day_name(&self, days_from_sunday: usize) -> String {
        self.day_names[days_from_sunday].clone()
    }

}

// ---- tests ----

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

    #[test]
    fn thousands_separator() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("1=234=567".to_string(), numeric_options.format_int(1234567))
    }

    #[test]
    fn thousands_separator_2() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("123=456".to_string(), numeric_options.format_int(123456))
    }

    #[test]
    fn thousands_separator_3() {
        let numeric_options = Numeric::new("/", "=");
        assert_eq!("12=345=678".to_string(), numeric_options.format_int(12345678))
    }
}
<|endoftext|>