<commit_before>//! # libgit2 bindings for Rust
//!
//! This library contains bindings to the [libgit2][1] C library which is used
//! to manage git repositories. The library itself is a work in progress and is
//! likely lacking some bindings here and there, so be warned.
//!
//! [1]: https://libgit2.github.com/
//!
//! The git2-rs library strives to be as close to libgit2 as possible, but also
//! strives to make using libgit2 as safe as possible. All resource management
//! is automatic as well as adding strong types to all interfaces (including
//! `Result`)
//!
//! ## Creating a `Repository`
//!
//! The `Repository` is the source from which almost all other objects in git-rs
//! are spawned. A repository can be created through opening, initializing, or
//! cloning.
//!
//! ### Initializing a new repository
//!
//! The `init` method will create a new repository, assuming one does not
//! already exist.
//!
//! ```no_run
//! use git2::Repository;
//!
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::init(&path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to init `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ### Opening an existing repository
//!
//! ```no_run
//! use git2::Repository;
//!
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::open(&path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to open `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ### Cloning an existing repository
//!
//! ```no_run
//! use git2::Repository;
//!
//! let url = "https://github.com/alexcrichton/git2-rs";
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::clone(url, &path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to clone `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ## Working with a `Repository`
//!
//! All deriviative objects, references, etc are attached to the lifetime of the
//! source `Repository`, to ensure that they do not outlive the repository
//! itself.

#![feature(macro_rules, unsafe_destructor)]
#![deny(missing_doc)]

extern crate libc;
extern crate time;
extern crate url;
extern crate "libgit2" as raw;

use std::c_str::CString;
use std::fmt;
use std::mem;
use std::rt;
use std::str;
use std::sync::{Once, ONCE_INIT};

pub use blob::Blob;
pub use branch::{Branch, Branches};
pub use buf::Buf;
pub use commit::{Commit, Parents};
pub use config::{Config, ConfigEntry, ConfigEntries};
pub use cred::{Cred, CredentialHelper};
pub use error::Error;
pub use index::{Index, IndexEntry};
pub use object::Object;
pub use oid::Oid;
pub use push::{Push, Status};
pub use reference::{Reference, References, ReferenceNames};
pub use refspec::Refspec;
pub use remote::{Remote, Refspecs};
pub use remote_callbacks::{RemoteCallbacks, Credentials, TransferProgress};
pub use remote_callbacks::{TransportMessage};
pub use repo::Repository;
pub use revspec::Revspec;
pub use signature::Signature;
pub use string_array::{StringArray, StringArrayItems, StringArrayBytes};
pub use submodule::Submodule;
pub use tree::{Tree, TreeEntry};

/// An enumeration of possible errors that can happen when working with a git
/// repository.
#[deriving(PartialEq, Eq, Clone, Show)]
pub enum ErrorCode {
    /// Generic error
    Error,
    /// Requested object could not be found
    NotFound,
    /// Object exists preventing operation
    Exists,
    /// More than one object matches
    Ambiguous,
    /// Output buffer too short to hold data
    BufSize,
    /// Operation not allowed on bare repository
    User,
    /// Operation not allowed on bare repository
    BareRepo,
    /// HEAD refers to branch with no commits
    UnbornBranch,
    /// Merge in progress prevented operation
    Unmerged,
    /// Reference was not fast-forwardable
    NotFastForward,
    /// Name/ref spec was not in a valid format
    InvalidSpec,
    /// Merge conflicts prevented operation
    MergeConflict,
    /// Lock file prevented operation
    Locked,
    /// Reference value does not match expected
    Modified,
}

/// A listing of the possible states that a repository can be in.
#[deriving(PartialEq, Eq, Clone, Show)]
#[allow(missing_doc)]
pub enum RepositoryState {
    Clean,
    Merge,
    Revert,
    CherryPick,
    Bisect,
    Rebase,
    RebaseInteractive,
    RebaseMerge,
    ApplyMailbox,
    ApplyMailboxOrRebase,
}

/// An enumeration of the possible directions for a remote.
pub enum Direction {
    /// Data will be fetched (read) from this remote.
    Fetch,
    /// Data will be pushed (written) to this remote.
    Push,
}

/// An enumeration of the operations that can be performed for the `reset`
/// method on a `Repository`.
pub enum ResetType {
    /// Move the head to the given commit.
    Soft,
    /// Soft plus reset the index to the commit.
    Mixed,
    /// Mixed plus changes in the working tree are discarded.
    Hard,
}

/// An enumeration all possible kinds objects may have.
#[deriving(PartialEq, Eq)]
pub enum ObjectKind {
    /// An object which corresponds to a any git object
    Any,
    /// An object which corresponds to a git commit
    Commit,
    /// An object which corresponds to a git tree
    Tree,
    /// An object which corresponds to a git blob
    Blob,
    /// An object which corresponds to a git tag
    Tag,
}

/// An enumeration for the possible types of branches
#[deriving(PartialEq, Eq, Show)]
pub enum BranchType {
    /// A local branch not on a remote.
    Local,
    /// A branch for a remote.
    Remote,
}

/// An enumeration of the possible priority levels of a config file.
///
/// The levels corresponding to the escalation logic (higher to lower) when
/// searching for config entries.
#[deriving(PartialEq, Eq, Show)]
pub enum ConfigLevel {
    /// System-wide configuration file, e.g. /etc/gitconfig
    ConfigSystem,
    /// XDG-compatible configuration file, e.g. ~/.config/git/config
    ConfigXDG,
    /// User-specific configuration, e.g. ~/.gitconfig
    ConfigGlobal,
    /// Reopsitory specific config, e.g. $PWD/.git/config
    ConfigLocal,
    /// Application specific configuration file
    ConfigApp,
    /// Highest level available
    ConfigHighest,
}

bitflags! {
    #[doc = "
Types of credentials that can be requested by a credential callback.
"]
    flags CredentialType: uint {
        static UserPassPlaintext = raw::GIT_CREDTYPE_USERPASS_PLAINTEXT as uint,
        static SshKey = raw::GIT_CREDTYPE_SSH_KEY as uint,
        static SshCustom = raw::GIT_CREDTYPE_SSH_CUSTOM as uint,
        static Default = raw::GIT_CREDTYPE_DEFAULT as uint,
        static SshInteractive = raw::GIT_CREDTYPE_SSH_INTERACTIVE as uint
    }
}

mod call;

pub mod build;

mod blob;
mod branch;
mod buf;
mod commit;
mod config;
mod cred;
mod error;
mod index;
mod object;
mod oid;
mod push;
mod reference;
mod refspec;
mod remote;
mod remote_callbacks;
mod repo;
mod revspec;
mod signature;
mod string_array;
mod submodule;
mod tree;

#[cfg(test)] mod test;

fn init() {
    static mut INIT: Once = ONCE_INIT;
    unsafe {
        INIT.doit(|| {
            raw::openssl_init();
            assert!(raw::git_threads_init() == 0,
                    "couldn't initialize the libgit2 library!");
            rt::at_exit(proc() {
                raw::git_threads_shutdown();
            });
        })
    }
}

unsafe fn opt_bytes<'a, T>(_: &'a T,
                           c: *const libc::c_char) -> Option<&'a [u8]> {
    if c.is_null() {
        None
    } else {
        let s = CString::new(c, false);
        Some(mem::transmute(s.as_bytes_no_nul()))
    }
}

impl ObjectKind {
    /// Convert an object type to its string representation.
    pub fn str(&self) -> &'static str {
        unsafe {
            let ptr = call!(raw::git_object_type2string(*self));
            str::raw::c_str_to_static_slice(ptr)
        }
    }

    /// Determine if the given git_otype is a valid loose object type.
    pub fn is_loose(&self) -> bool {
        unsafe { (call!(raw::git_object_typeisloose(*self)) == 1) }
    }

    /// Convert a raw git_otype to an ObjectKind
    pub fn from_raw(raw: raw::git_otype) -> Option<ObjectKind> {
        match raw {
            raw::GIT_OBJ_ANY => Some(::Any),
            raw::GIT_OBJ_BAD => None,
            raw::GIT_OBJ__EXT1 => None,
            raw::GIT_OBJ_COMMIT => Some(::Commit),
            raw::GIT_OBJ_TREE => Some(::Tree),
            raw::GIT_OBJ_BLOB => Some(::Blob),
            raw::GIT_OBJ_TAG => Some(::Tag),
            raw::GIT_OBJ__EXT2 => None,
            raw::GIT_OBJ_OFS_DELTA => None,
            raw::GIT_OBJ_REF_DELTA => None,
        }
    }

    /// Convert this kind into its raw representation
    pub fn raw(&self) -> raw::git_otype {
        call::convert(self)
    }

    /// Convert a string object type representation to its object type.
    pub fn from_str(s: &str) -> Option<ObjectKind> {
        let raw = unsafe { call!(raw::git_object_string2type(s.to_c_str())) };
        ObjectKind::from_raw(raw)
    }
}

impl fmt::Show for ObjectKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.str().fmt(f)
    }
}

impl ConfigLevel {
    /// Converts a raw configuration level to a ConfigLevel
    pub fn from_raw(raw: raw::git_config_level_t) -> ConfigLevel {
        match raw {
            raw::GIT_CONFIG_LEVEL_SYSTEM => ::ConfigSystem,
            raw::GIT_CONFIG_LEVEL_XDG => ::ConfigXDG,
            raw::GIT_CONFIG_LEVEL_GLOBAL => ::ConfigGlobal,
            raw::GIT_CONFIG_LEVEL_LOCAL => ::ConfigLocal,
            raw::GIT_CONFIG_LEVEL_APP => ::ConfigApp,
            raw::GIT_CONFIG_HIGHEST_LEVEL => ::ConfigHighest,
        }
    }
}

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

    #[test]
    fn convert() {
        assert_eq!(::Blob.str(), "blob");
        assert_eq!(ObjectKind::from_str("blob"), Some(::Blob));
        assert!(::Blob.is_loose());
    }

}
<commit_msg>Reexport one more item<commit_after>//! # libgit2 bindings for Rust
//!
//! This library contains bindings to the [libgit2][1] C library which is used
//! to manage git repositories. The library itself is a work in progress and is
//! likely lacking some bindings here and there, so be warned.
//!
//! [1]: https://libgit2.github.com/
//!
//! The git2-rs library strives to be as close to libgit2 as possible, but also
//! strives to make using libgit2 as safe as possible. All resource management
//! is automatic as well as adding strong types to all interfaces (including
//! `Result`)
//!
//! ## Creating a `Repository`
//!
//! The `Repository` is the source from which almost all other objects in git-rs
//! are spawned. A repository can be created through opening, initializing, or
//! cloning.
//!
//! ### Initializing a new repository
//!
//! The `init` method will create a new repository, assuming one does not
//! already exist.
//!
//! ```no_run
//! use git2::Repository;
//!
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::init(&path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to init `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ### Opening an existing repository
//!
//! ```no_run
//! use git2::Repository;
//!
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::open(&path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to open `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ### Cloning an existing repository
//!
//! ```no_run
//! use git2::Repository;
//!
//! let url = "https://github.com/alexcrichton/git2-rs";
//! let path = Path::new("/path/to/a/repo");
//! let repo = match Repository::clone(url, &path) {
//!     Ok(repo) => repo,
//!     Err(e) => fail!("failed to clone `{}`: {}", path.display(), e),
//! };
//! ```
//!
//! ## Working with a `Repository`
//!
//! All deriviative objects, references, etc are attached to the lifetime of the
//! source `Repository`, to ensure that they do not outlive the repository
//! itself.

#![feature(macro_rules, unsafe_destructor)]
#![deny(missing_doc)]

extern crate libc;
extern crate time;
extern crate url;
extern crate "libgit2" as raw;

use std::c_str::CString;
use std::fmt;
use std::mem;
use std::rt;
use std::str;
use std::sync::{Once, ONCE_INIT};

pub use blob::Blob;
pub use branch::{Branch, Branches};
pub use buf::Buf;
pub use commit::{Commit, Parents};
pub use config::{Config, ConfigEntry, ConfigEntries};
pub use cred::{Cred, CredentialHelper};
pub use error::Error;
pub use index::{Index, IndexEntry};
pub use object::Object;
pub use oid::Oid;
pub use push::{Push, Status};
pub use reference::{Reference, References, ReferenceNames};
pub use refspec::Refspec;
pub use remote::{Remote, Refspecs};
pub use remote_callbacks::{RemoteCallbacks, Credentials, TransferProgress};
pub use remote_callbacks::{TransportMessage, Progress};
pub use repo::Repository;
pub use revspec::Revspec;
pub use signature::Signature;
pub use string_array::{StringArray, StringArrayItems, StringArrayBytes};
pub use submodule::Submodule;
pub use tree::{Tree, TreeEntry};

/// An enumeration of possible errors that can happen when working with a git
/// repository.
#[deriving(PartialEq, Eq, Clone, Show)]
pub enum ErrorCode {
    /// Generic error
    Error,
    /// Requested object could not be found
    NotFound,
    /// Object exists preventing operation
    Exists,
    /// More than one object matches
    Ambiguous,
    /// Output buffer too short to hold data
    BufSize,
    /// Operation not allowed on bare repository
    User,
    /// Operation not allowed on bare repository
    BareRepo,
    /// HEAD refers to branch with no commits
    UnbornBranch,
    /// Merge in progress prevented operation
    Unmerged,
    /// Reference was not fast-forwardable
    NotFastForward,
    /// Name/ref spec was not in a valid format
    InvalidSpec,
    /// Merge conflicts prevented operation
    MergeConflict,
    /// Lock file prevented operation
    Locked,
    /// Reference value does not match expected
    Modified,
}

/// A listing of the possible states that a repository can be in.
#[deriving(PartialEq, Eq, Clone, Show)]
#[allow(missing_doc)]
pub enum RepositoryState {
    Clean,
    Merge,
    Revert,
    CherryPick,
    Bisect,
    Rebase,
    RebaseInteractive,
    RebaseMerge,
    ApplyMailbox,
    ApplyMailboxOrRebase,
}

/// An enumeration of the possible directions for a remote.
pub enum Direction {
    /// Data will be fetched (read) from this remote.
    Fetch,
    /// Data will be pushed (written) to this remote.
    Push,
}

/// An enumeration of the operations that can be performed for the `reset`
/// method on a `Repository`.
pub enum ResetType {
    /// Move the head to the given commit.
    Soft,
    /// Soft plus reset the index to the commit.
    Mixed,
    /// Mixed plus changes in the working tree are discarded.
    Hard,
}

/// An enumeration all possible kinds objects may have.
#[deriving(PartialEq, Eq)]
pub enum ObjectKind {
    /// An object which corresponds to a any git object
    Any,
    /// An object which corresponds to a git commit
    Commit,
    /// An object which corresponds to a git tree
    Tree,
    /// An object which corresponds to a git blob
    Blob,
    /// An object which corresponds to a git tag
    Tag,
}

/// An enumeration for the possible types of branches
#[deriving(PartialEq, Eq, Show)]
pub enum BranchType {
    /// A local branch not on a remote.
    Local,
    /// A branch for a remote.
    Remote,
}

/// An enumeration of the possible priority levels of a config file.
///
/// The levels corresponding to the escalation logic (higher to lower) when
/// searching for config entries.
#[deriving(PartialEq, Eq, Show)]
pub enum ConfigLevel {
    /// System-wide configuration file, e.g. /etc/gitconfig
    ConfigSystem,
    /// XDG-compatible configuration file, e.g. ~/.config/git/config
    ConfigXDG,
    /// User-specific configuration, e.g. ~/.gitconfig
    ConfigGlobal,
    /// Reopsitory specific config, e.g. $PWD/.git/config
    ConfigLocal,
    /// Application specific configuration file
    ConfigApp,
    /// Highest level available
    ConfigHighest,
}

bitflags! {
    #[doc = "
Types of credentials that can be requested by a credential callback.
"]
    flags CredentialType: uint {
        static UserPassPlaintext = raw::GIT_CREDTYPE_USERPASS_PLAINTEXT as uint,
        static SshKey = raw::GIT_CREDTYPE_SSH_KEY as uint,
        static SshCustom = raw::GIT_CREDTYPE_SSH_CUSTOM as uint,
        static Default = raw::GIT_CREDTYPE_DEFAULT as uint,
        static SshInteractive = raw::GIT_CREDTYPE_SSH_INTERACTIVE as uint
    }
}

mod call;

pub mod build;

mod blob;
mod branch;
mod buf;
mod commit;
mod config;
mod cred;
mod error;
mod index;
mod object;
mod oid;
mod push;
mod reference;
mod refspec;
mod remote;
mod remote_callbacks;
mod repo;
mod revspec;
mod signature;
mod string_array;
mod submodule;
mod tree;

#[cfg(test)] mod test;

fn init() {
    static mut INIT: Once = ONCE_INIT;
    unsafe {
        INIT.doit(|| {
            raw::openssl_init();
            assert!(raw::git_threads_init() == 0,
                    "couldn't initialize the libgit2 library!");
            rt::at_exit(proc() {
                raw::git_threads_shutdown();
            });
        })
    }
}

unsafe fn opt_bytes<'a, T>(_: &'a T,
                           c: *const libc::c_char) -> Option<&'a [u8]> {
    if c.is_null() {
        None
    } else {
        let s = CString::new(c, false);
        Some(mem::transmute(s.as_bytes_no_nul()))
    }
}

impl ObjectKind {
    /// Convert an object type to its string representation.
    pub fn str(&self) -> &'static str {
        unsafe {
            let ptr = call!(raw::git_object_type2string(*self));
            str::raw::c_str_to_static_slice(ptr)
        }
    }

    /// Determine if the given git_otype is a valid loose object type.
    pub fn is_loose(&self) -> bool {
        unsafe { (call!(raw::git_object_typeisloose(*self)) == 1) }
    }

    /// Convert a raw git_otype to an ObjectKind
    pub fn from_raw(raw: raw::git_otype) -> Option<ObjectKind> {
        match raw {
            raw::GIT_OBJ_ANY => Some(::Any),
            raw::GIT_OBJ_BAD => None,
            raw::GIT_OBJ__EXT1 => None,
            raw::GIT_OBJ_COMMIT => Some(::Commit),
            raw::GIT_OBJ_TREE => Some(::Tree),
            raw::GIT_OBJ_BLOB => Some(::Blob),
            raw::GIT_OBJ_TAG => Some(::Tag),
            raw::GIT_OBJ__EXT2 => None,
            raw::GIT_OBJ_OFS_DELTA => None,
            raw::GIT_OBJ_REF_DELTA => None,
        }
    }

    /// Convert this kind into its raw representation
    pub fn raw(&self) -> raw::git_otype {
        call::convert(self)
    }

    /// Convert a string object type representation to its object type.
    pub fn from_str(s: &str) -> Option<ObjectKind> {
        let raw = unsafe { call!(raw::git_object_string2type(s.to_c_str())) };
        ObjectKind::from_raw(raw)
    }
}

impl fmt::Show for ObjectKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.str().fmt(f)
    }
}

impl ConfigLevel {
    /// Converts a raw configuration level to a ConfigLevel
    pub fn from_raw(raw: raw::git_config_level_t) -> ConfigLevel {
        match raw {
            raw::GIT_CONFIG_LEVEL_SYSTEM => ::ConfigSystem,
            raw::GIT_CONFIG_LEVEL_XDG => ::ConfigXDG,
            raw::GIT_CONFIG_LEVEL_GLOBAL => ::ConfigGlobal,
            raw::GIT_CONFIG_LEVEL_LOCAL => ::ConfigLocal,
            raw::GIT_CONFIG_LEVEL_APP => ::ConfigApp,
            raw::GIT_CONFIG_HIGHEST_LEVEL => ::ConfigHighest,
        }
    }
}

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

    #[test]
    fn convert() {
        assert_eq!(::Blob.str(), "blob");
        assert_eq!(ObjectKind::from_str("blob"), Some(::Blob));
        assert!(::Blob.is_loose());
    }

}
<|endoftext|>


<commit_before>//! TMX reading crate.
//!
//! This is a crate for reading TMX files.
//!
//! ```
//! use std::str::FromStr;
//! use tmx::*;
//!
//! let xml = "<map></<map>";
//! let map = Map::from_str(&xml);
//! ```

extern crate xml;

#[cfg(test)]
#[macro_use] extern crate assert_matches;

mod color;
mod error;
mod model;

pub use error::Error;
pub use model::*;

type Result<T> = std::result::Result<T, ::error::Error>;

<commit_msg>Reexport Color from the root<commit_after>//! TMX reading crate.
//!
//! This is a crate for reading TMX files.
//!
//! ```
//! use std::str::FromStr;
//! use tmx::*;
//!
//! let xml = "<map></<map>";
//! let map = Map::from_str(&xml);
//! ```

extern crate xml;

#[cfg(test)]
#[macro_use] extern crate assert_matches;

mod color;
mod error;
mod model;

pub use color::Color;
pub use error::Error;
pub use model::*;

type Result<T> = std::result::Result<T, ::error::Error>;

<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate mozprofile;
#[cfg(target_os = "windows")]
extern crate winreg;

pub mod runner;

pub use runner::platform::firefox_default_path;

#[cfg(test)]
mod test {
    use runner::Runner;

    use std::path::Path;

    #[test]
    fn it_works() {
        let path = Path::new("/home/jgraham/develop/gecko/obj-x86_64-unknown-linux-gnu/dist/bin/firefox/").unwrap();
        let mut fx_runner = runner::FirefoxRunner::new(path, None).unwrap();
        fx_runner.start();
    }
}
<commit_msg>Remove pointless tests<commit_after>#[macro_use]
extern crate log;
extern crate mozprofile;
#[cfg(target_os = "windows")]
extern crate winreg;

pub mod runner;

pub use runner::platform::firefox_default_path;
<|endoftext|>


<commit_before>extern crate xml;

use std::ascii::AsciiExt;
use std::default::Default;
use std::iter::IteratorExt;
use xml::{Element, ElementBuilder, Parser, Xml};

fn elem_with_text(tag_name: &'static str, chars: &str) -> Element {
    let mut elem = Element::new(tag_name, None, &[]);
    elem.text(chars);
    elem
}

trait ViaXml {
    fn to_xml(&self) -> Element;
    fn from_xml(element: Element) -> Self;
}

/// RSS version 2.0
#[derive(Default)]
pub struct Rss(pub Vec<Channel>);

impl ViaXml for Rss {
    fn to_xml(&self) -> Element {
        let mut rss = Element::new("rss", None, &[("version", None, "2.0")]);

        let &Rss(ref channels) = self;
        for channel in channels {
            rss.tag(channel.to_xml());
        }

        rss
    }

    fn from_xml(element: Element) -> Self {
        if element.name.to_ascii_lowercase() != "rss" {
            panic!("Expected <rss>, found <{}>", element.name);
        }

        let channels = element.get_children("channel", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()))
            .collect();

        Rss(channels)
    }
}

impl Rss {
    fn to_string(&self) -> String {
        let mut ret = format!("{}", Xml::PINode("xml version='1.0' encoding='UTF-8'".to_string()));
        ret.push_str(&format!("{}", self.to_xml()));
        ret
    }

    fn from_str(input: &str) -> Self {
        let mut parser = Parser::new();
        parser.feed_str(input);

        let mut builder = ElementBuilder::new();

        for event in parser {
            match builder.push_event(event) {
                Ok(Some(element)) => return ViaXml::from_xml(element),
                _ => (),
            }
        }

        panic!("RSS read error")
    }
}

#[derive(Default)]
pub struct Channel {
    pub title: String,
    pub link: String,
    pub description: String,
    pub items: Vec<Item>,
}

impl ViaXml for Channel {
    fn to_xml(&self) -> Element {
        let mut channel = Element::new("channel", None, &[]);

        channel.tag(elem_with_text("title", &self.title));
        channel.tag(elem_with_text("link", &self.link));
        channel.tag(elem_with_text("description", &self.description));

        for item in &self.items {
            channel.tag(item.to_xml());
        }

        channel
    }

    fn from_xml(element: Element) -> Self {
        let mut channel: Channel = Default::default();

        match element.get_child("title", None) {
            Some(element) => channel.title = element.content_str(),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => channel.link = element.content_str(),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => channel.description = element.content_str(),
            None => (),
        }

        channel.items = element.get_children("item", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()))
            .collect();

        channel
    }
}

#[derive(Default)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub description: Option<String>,
}

impl ViaXml for Item {
    fn to_xml(&self) -> Element {
        let mut item = Element::new("item", None, &[]);

        if let Some(ref s) = self.title {
            item.tag(elem_with_text("title", s));
        }

        if let Some(ref s) = self.link {
            item.tag(elem_with_text("link", s));
        }

        if let Some(ref s) = self.description {
            item.tag(elem_with_text("description", s));
        }

        item
    }

    fn from_xml(element: Element) -> Self {
        let mut item: Item = Default::default();

        match element.get_child("title", None) {
            Some(element) => item.title = Some(element.content_str()),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => item.link = Some(element.content_str()),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => item.description = Some(element.content_str()),
            None => (),
        }

        item
    }
}

#[test]
fn test_basic_to_string() {
    let item = Item {
        title: Some(String::from_str("My first post!")),
        link: Some(String::from_str("http://myblog.com/post1")),
        description: Some(String::from_str("This is my first post")),
    };

    let channel = Channel {
        title: String::from_str("My Blog"),
        link: String::from_str("http://myblog.com"),
        description: String::from_str("Where I write stuff"),
        items: vec![item],
    };

    let rss = Rss(vec![channel]);
    assert_eq!(rss.to_string(), "<?xml version=\'1.0\' encoding=\'UTF-8\'?><rss version=\'2.0\'><channel><title>My Blog</title><link>http://myblog.com</link><description>Where I write stuff</description><item><title>My first post!</title><link>http://myblog.com/post1</link><description>This is my first post</description></item></channel></rss>");
}

#[test]
fn test_from_str_no_channels() {
    let rss_str = "<rss></rss>";
    let Rss(channels) = Rss::from_str(rss_str);
    assert_eq!(0, channels.len());
}

#[test]
fn test_from_str_one_channel() {
    let rss_str = "<rss><channel></channel></rss>";
    let Rss(channels) = Rss::from_str(rss_str);
    assert_eq!(1, channels.len());
}

#[test]
fn test_from_str_one_channel_with_title() {
    let rss_str = "<rss><channel><title>Hello world!</title></channel></rss>";
    let Rss(channels) = Rss::from_str(rss_str);
    assert_eq!(1, channels.len());
    assert_eq!("Hello world!", channels[0].title);
}
<commit_msg>Move tests into module<commit_after>extern crate xml;

use std::ascii::AsciiExt;
use std::default::Default;
use std::iter::IteratorExt;
use xml::{Element, ElementBuilder, Parser, Xml};

fn elem_with_text(tag_name: &'static str, chars: &str) -> Element {
    let mut elem = Element::new(tag_name, None, &[]);
    elem.text(chars);
    elem
}

trait ViaXml {
    fn to_xml(&self) -> Element;
    fn from_xml(element: Element) -> Self;
}

/// RSS version 2.0
#[derive(Default)]
pub struct Rss(pub Vec<Channel>);

impl ViaXml for Rss {
    fn to_xml(&self) -> Element {
        let mut rss = Element::new("rss", None, &[("version", None, "2.0")]);

        let &Rss(ref channels) = self;
        for channel in channels {
            rss.tag(channel.to_xml());
        }

        rss
    }

    fn from_xml(element: Element) -> Self {
        if element.name.to_ascii_lowercase() != "rss" {
            panic!("Expected <rss>, found <{}>", element.name);
        }

        let channels = element.get_children("channel", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()))
            .collect();

        Rss(channels)
    }
}

impl Rss {
    fn to_string(&self) -> String {
        let mut ret = format!("{}", Xml::PINode("xml version='1.0' encoding='UTF-8'".to_string()));
        ret.push_str(&format!("{}", self.to_xml()));
        ret
    }

    fn from_str(input: &str) -> Self {
        let mut parser = Parser::new();
        parser.feed_str(input);

        let mut builder = ElementBuilder::new();

        for event in parser {
            match builder.push_event(event) {
                Ok(Some(element)) => return ViaXml::from_xml(element),
                _ => (),
            }
        }

        panic!("RSS read error")
    }
}

#[derive(Default)]
pub struct Channel {
    pub title: String,
    pub link: String,
    pub description: String,
    pub items: Vec<Item>,
}

impl ViaXml for Channel {
    fn to_xml(&self) -> Element {
        let mut channel = Element::new("channel", None, &[]);

        channel.tag(elem_with_text("title", &self.title));
        channel.tag(elem_with_text("link", &self.link));
        channel.tag(elem_with_text("description", &self.description));

        for item in &self.items {
            channel.tag(item.to_xml());
        }

        channel
    }

    fn from_xml(element: Element) -> Self {
        let mut channel: Channel = Default::default();

        match element.get_child("title", None) {
            Some(element) => channel.title = element.content_str(),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => channel.link = element.content_str(),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => channel.description = element.content_str(),
            None => (),
        }

        channel.items = element.get_children("item", None)
            .into_iter()
            .map(|e| ViaXml::from_xml(e.clone()))
            .collect();

        channel
    }
}

#[derive(Default)]
pub struct Item {
    pub title: Option<String>,
    pub link: Option<String>,
    pub description: Option<String>,
}

impl ViaXml for Item {
    fn to_xml(&self) -> Element {
        let mut item = Element::new("item", None, &[]);

        if let Some(ref s) = self.title {
            item.tag(elem_with_text("title", s));
        }

        if let Some(ref s) = self.link {
            item.tag(elem_with_text("link", s));
        }

        if let Some(ref s) = self.description {
            item.tag(elem_with_text("description", s));
        }

        item
    }

    fn from_xml(element: Element) -> Self {
        let mut item: Item = Default::default();

        match element.get_child("title", None) {
            Some(element) => item.title = Some(element.content_str()),
            None => (),
        }

        match element.get_child("link", None) {
            Some(element) => item.link = Some(element.content_str()),
            None => (),
        }

        match element.get_child("description", None) {
            Some(element) => item.description = Some(element.content_str()),
            None => (),
        }

        item
    }
}

#[cfg(test)]
mod test {
    use super::{Rss, Item, Channel};

    #[test]
    fn test_basic_to_string() {
        let item = Item {
            title: Some(String::from_str("My first post!")),
            link: Some(String::from_str("http://myblog.com/post1")),
            description: Some(String::from_str("This is my first post")),
        };

        let channel = Channel {
            title: String::from_str("My Blog"),
            link: String::from_str("http://myblog.com"),
            description: String::from_str("Where I write stuff"),
            items: vec![item],
        };

        let rss = Rss(vec![channel]);
        assert_eq!(rss.to_string(), "<?xml version=\'1.0\' encoding=\'UTF-8\'?><rss version=\'2.0\'><channel><title>My Blog</title><link>http://myblog.com</link><description>Where I write stuff</description><item><title>My first post!</title><link>http://myblog.com/post1</link><description>This is my first post</description></item></channel></rss>");
    }

    #[test]
    fn test_from_str_no_channels() {
        let rss_str = "<rss></rss>";
        let Rss(channels) = Rss::from_str(rss_str);
        assert_eq!(0, channels.len());
    }

    #[test]
    fn test_from_str_one_channel() {
        let rss_str = "<rss><channel></channel></rss>";
        let Rss(channels) = Rss::from_str(rss_str);
        assert_eq!(1, channels.len());
    }

    #[test]
    fn test_from_str_one_channel_with_title() {
        let rss_str = "<rss><channel><title>Hello world!</title></channel></rss>";
        let Rss(channels) = Rss::from_str(rss_str);
        assert_eq!(1, channels.len());
        assert_eq!("Hello world!", channels[0].title);
    }
}
<|endoftext|>


<commit_before>#![crate_name = "jsonway"]
#![crate_type = "rlib"]
#![deny(warnings)]
#![deny(bad_style)]
#![feature(collections)]
#![feature(core)]
extern crate "rustc-serialize" as serialize;
extern crate collections;

pub use mutable_json::MutableJson;
pub use object_builder::ObjectBuilder;
pub use array_builder::ArrayBuilder;
pub use serializer::{Serializer, ObjectSerializer, ObjectScopeSerializer};
pub use array_serializer::ArraySerializer;

pub mod array_builder;
pub mod object_builder;
pub mod mutable_json;
pub mod serializer;
pub mod array_serializer;

/// ```rust
/// let json = jsonway::object(|json| {
///     json.set("first_name", "Luke".to_string());
///     json.set("last_name", "Skywalker".to_string());
///
///     json.object("info", |json| {
///         json.set("homeworld", "Tatooine".to_string());
///         json.set("born", "19 BBY".to_string());
///         json.set("died", "Between 45 ABY and 137 ABY".to_string());
///     });
///
///     json.array("masters", |json| {
///         json.push("Obi-Wan Kenobi".to_string());
///         json.push("Yoda".to_string());
///         json.push("Joruus C'baoth (Briefly)".to_string());
///         json.push("Darth Sidious (Briefly)".to_string());
///     });
/// }).unwrap();
///
/// assert_eq!(json.find("first_name").unwrap().as_string().unwrap(), "Luke");
/// assert_eq!(json.find("last_name").unwrap().as_string().unwrap(), "Skywalker");
///
/// assert!(json.find("info").unwrap().is_object());
/// assert!(json.find("masters").unwrap().is_array());
/// ```

/// Create and return new ListBuilder
pub fn array<F>(builder: F) -> ArrayBuilder where F: FnOnce(&mut ArrayBuilder) {
    ArrayBuilder::build(builder)
}

/// Create and return new ObjectBuilder
pub fn object<F>(builder: F) -> ObjectBuilder where F: FnOnce(&mut ObjectBuilder) {
    ObjectBuilder::build(builder)
}
<commit_msg>refactor(rustup): remove feature(core)<commit_after>#![crate_name = "jsonway"]
#![crate_type = "rlib"]
#![deny(warnings)]
#![deny(bad_style)]
#![feature(collections)]
extern crate "rustc-serialize" as serialize;
extern crate collections;

pub use mutable_json::MutableJson;
pub use object_builder::ObjectBuilder;
pub use array_builder::ArrayBuilder;
pub use serializer::{Serializer, ObjectSerializer, ObjectScopeSerializer};
pub use array_serializer::ArraySerializer;

pub mod array_builder;
pub mod object_builder;
pub mod mutable_json;
pub mod serializer;
pub mod array_serializer;

/// ```rust
/// let json = jsonway::object(|json| {
///     json.set("first_name", "Luke".to_string());
///     json.set("last_name", "Skywalker".to_string());
///
///     json.object("info", |json| {
///         json.set("homeworld", "Tatooine".to_string());
///         json.set("born", "19 BBY".to_string());
///         json.set("died", "Between 45 ABY and 137 ABY".to_string());
///     });
///
///     json.array("masters", |json| {
///         json.push("Obi-Wan Kenobi".to_string());
///         json.push("Yoda".to_string());
///         json.push("Joruus C'baoth (Briefly)".to_string());
///         json.push("Darth Sidious (Briefly)".to_string());
///     });
/// }).unwrap();
///
/// assert_eq!(json.find("first_name").unwrap().as_string().unwrap(), "Luke");
/// assert_eq!(json.find("last_name").unwrap().as_string().unwrap(), "Skywalker");
///
/// assert!(json.find("info").unwrap().is_object());
/// assert!(json.find("masters").unwrap().is_array());
/// ```

/// Create and return new ListBuilder
pub fn array<F>(builder: F) -> ArrayBuilder where F: FnOnce(&mut ArrayBuilder) {
    ArrayBuilder::build(builder)
}

/// Create and return new ObjectBuilder
pub fn object<F>(builder: F) -> ObjectBuilder where F: FnOnce(&mut ObjectBuilder) {
    ObjectBuilder::build(builder)
}
<|endoftext|>


<commit_before>#![feature(test)]
#![feature(collections)]
#![feature(alloc)]
#![feature(box_patterns)]

#[cfg(test)]
extern crate test;

#[cfg(test)]
extern crate rand;

#[cfg(test)]
#[macro_use]
mod bench_macros;

mod map;
pub use map::*;
<commit_msg>cleanup unused feature<commit_after>#![feature(test)]
#![feature(collections)]
#![feature(box_patterns)]

#[cfg(test)]
extern crate test;

#[cfg(test)]
extern crate rand;

#[cfg(test)]
#[macro_use]
mod bench_macros;

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


<commit_before>//! Facilities for implementing chess engines.

pub mod time_manager;

use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{SystemTime, Duration};
use std::cmp::max;
use std::io;
use chesstypes::*;
use uci::*;
use search::*;
use self::time_manager::*;

/// Serves UCI commands until a "quit" command is received.
///
/// The current thread will block until the UCI session is closed.
///
/// Returns `Err` if the handshake was unsuccessful, or if an IO error
/// occurred.
pub fn run_server<S: SearchExecutor<ReportData = Vec<Variation>>>() -> io::Result<()> {
    run_engine::<Engine<S>>()
}

/// The chess starting position in Forsyth–Edwards notation (FEN).
const START_POSITION_FEN: &'static str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 1";

/// The version of the program.
const VERSION: &'static str = "0.1";

/// The name of the program.
const NAME: &'static str = "Alcibiades";

/// The author of the program.
const AUTHOR: &'static str = "Evgeni Pandurski";

/// Represents a condition for terminating the search.
enum PlayWhen {
    TimeManagement(TimeManager), // Stop when the time manager says.
    MoveTime(u64), // Stop after the given number of milliseconds.
    Nodes(u64), // Stop when the given number of nodes has been searched.
    Depth(u8), // Stop when the given search depth has been reached.
    Never, // An infinite search.
}

/// Implements `UciEngine` trait.
struct Engine<S: SearchExecutor<ReportData = Vec<Variation>>> {
    tt: Arc<S::HashTable>,
    position: S::SearchNode,
    current_depth: u8,

    // From `SearchThread`.
    status: SearchStatus,
    searcher: S,

    // Tells the engine when it must stop thinking and play the best
    // move it has found.
    play_when: PlayWhen,

    // Tells the engine how many best lines of play to calculate and
    // send to the GUI (the first move in each line will be
    // different). This is the so called "MultiPV" mode.
    variation_count: usize,

    // Tells the engine if it will be allowed to ponder. This option
    // is needed because the engine might change its time management
    // algorithm when pondering is allowed.
    pondering_is_allowed: bool,

    // `true` if the engine is thinking in pondering mode at the
    // moment.
    is_pondering: bool,

    // This helps the engine decide when to send periodic progress
    // reports.
    silent_since: SystemTime,

    // A queue for the messages send by the engine to the GUI.
    queue: VecDeque<EngineReply>,
}

impl<S: SearchExecutor<ReportData = Vec<Variation>>> UciEngine for Engine<S> {
    fn name() -> String {
        format!("{} {}", NAME, VERSION)
    }

    fn author() -> String {
        AUTHOR.to_string()
    }

    fn options() -> Vec<(String, OptionDescription)> {
        // Add up all suported options.
        let mut options = vec![
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 64 * 1024, default: 16 }),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ];
        options.extend(S::options());

        // Remove duplicated options.
        options.sort_by(|a, b| a.0.cmp(&b.0));
        let mut options_dedup = vec![];
        let mut prev_name = String::from("");
        for o in options.drain(..) {
            if o.0 == prev_name {
                continue;
            }
            prev_name = o.0.clone();
            options_dedup.push(o);
        }
        options_dedup
    }

    fn new(tt_size_mb: Option<usize>) -> Engine<S> {
        let tt = Arc::new(S::HashTable::new(tt_size_mb));
        Engine {
            tt: tt.clone(),
            position: S::SearchNode::from_history(START_POSITION_FEN, &mut vec![].into_iter())
                          .ok()
                          .unwrap(),
            current_depth: 0,
            status: SearchStatus {
                done: true,
                depth: 0,
                variations: vec![Variation {
                                     value: VALUE_MIN,
                                     bound: BOUND_LOWER,
                                     moves: vec![],
                                 }],
                searched_nodes: 0,
                started_at: SystemTime::now(),
                duration_millis: 0,
                nps: 0,
            },
            searcher: S::new(tt),
            play_when: PlayWhen::Never,
            variation_count: 1,
            pondering_is_allowed: false,
            is_pondering: false,
            silent_since: SystemTime::now(),
            queue: VecDeque::new(),
        }
    }

    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            "Ponder" => {
                self.pondering_is_allowed = value == "true";
            }
            "MultiPV" => {
                self.variation_count = max(value.parse::<usize>().unwrap_or(0), 1);
            }
            "Hash" => {
                // We do not support re-sizing of the transposition
                // table once the engine has been started.
                return;
            }
            _ => (),
        }
        S::set_option(name, value);
    }

    fn new_game(&mut self) {
        self.tt.clear();
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if let Ok(p) = S::SearchNode::from_history(fen, moves) {
            self.position = p;
        }
    }

    fn go(&mut self, params: GoParams) {
        // Note: We ignore the "mate" field.
        let GoParams { searchmoves,
                       ponder,
                       wtime,
                       btime,
                       winc,
                       binc,
                       movestogo,
                       depth,
                       nodes,
                       movetime,
                       infinite,
                       .. } = params;

        self.terminate();
        self.tt.new_search();
        self.current_depth = 0;
        self.is_pondering = ponder;
        self.play_when = if infinite {
            PlayWhen::Never
        } else if movetime.is_some() {
            PlayWhen::MoveTime(movetime.unwrap())
        } else if nodes.is_some() {
            PlayWhen::Nodes(nodes.unwrap())
        } else if depth.is_some() {
            PlayWhen::Depth(depth.unwrap() as u8)
        } else {
            PlayWhen::TimeManagement(TimeManager::new(self.position.board().to_move,
                                                      self.pondering_is_allowed,
                                                      wtime,
                                                      btime,
                                                      winc,
                                                      binc,
                                                      movestogo))
        };
        self.silent_since = SystemTime::now();
        self.start(searchmoves);
    }

    fn ponder_hit(&mut self) {
        if self.status.done {
            self.queue_best_move();
        } else {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        self.terminate();
        self.queue_best_move();
    }

    fn wait_for_reply(&mut self, duration: Duration) -> Option<EngineReply> {
        if self.queue.is_empty() {
            let done = self.status.done;

            // Wait for `search_thread` to do some work, and hopefully
            // update its status. (We must do this even when the
            // search is done -- in that case the next line will just
            // yield the CPU to another process.)
            self.wait_status_update(duration);

            if !done {
                let &SearchStatus { done, depth, searched_nodes, duration_millis, .. } =
                    &self.status;

                // Send the (multi)PV for each newly reached depth.
                if depth > self.current_depth {
                    self.current_depth = depth;
                    self.queue_pv();
                }

                // Send a progress report periodically.
                if self.silent_since.elapsed().unwrap().as_secs() > 10 {
                    self.queue_progress_report();
                }

                // Check if we must play now.
                if !self.is_pondering &&
                   match self.play_when {
                    PlayWhen::TimeManagement(ref tm) => done || tm.must_play(),
                    PlayWhen::MoveTime(t) => done || duration_millis >= t,
                    PlayWhen::Nodes(n) => done || searched_nodes >= n,
                    PlayWhen::Depth(d) => done || depth >= d,
                    PlayWhen::Never => false,
                } {
                    self.stop();
                }
            }
        }

        self.queue.pop_front()
    }

    fn exit(&mut self) {
        self.terminate();
    }
}

impl<S: SearchExecutor<ReportData = Vec<Variation>>> Engine<S> {
    /// A helper method. It it adds a progress report message to the
    /// queue.
    fn queue_progress_report(&mut self) {
        let &SearchStatus { depth, searched_nodes, nps, .. } = &self.status;
        self.queue.push_back(EngineReply::Info(vec![
            InfoItem { info_type: "depth".to_string(), data: format!("{}", depth) },
            InfoItem { info_type: "nodes".to_string(), data: format!("{}", searched_nodes) },
            InfoItem { info_type: "nps".to_string(), data: format!("{}", nps) },
        ]));
        self.silent_since = SystemTime::now();
    }

    /// A helper method. It it adds a message containing the current
    /// (multi)PV to the queue.
    fn queue_pv(&mut self) {
        let &SearchStatus { depth, ref variations, searched_nodes, duration_millis, nps, .. } =
            &self.status;
        for (i, &Variation { ref moves, value, bound }) in variations.iter().enumerate() {
            let bound_suffix = match bound {
                BOUND_EXACT => "",
                BOUND_UPPER => " upperbound",
                BOUND_LOWER => " lowerbound",
                _ => panic!("unexpected bound type"),
            };
            let mut moves_string = String::new();
            for m in moves {
                moves_string.push_str(&m.notation());
                moves_string.push(' ');
            }
            self.queue.push_back(EngineReply::Info(vec![
                InfoItem { info_type: "depth".to_string(), data: format!("{}", depth) },
                InfoItem { info_type: "multipv".to_string(), data: format!("{}", i + 1) },
                InfoItem { info_type: "score".to_string(), data: format!("cp {}{}", value, bound_suffix) },
                InfoItem { info_type: "time".to_string(), data: format!("{}", duration_millis) },
                InfoItem { info_type: "nodes".to_string(), data: format!("{}", searched_nodes) },
                InfoItem { info_type: "nps".to_string(), data: format!("{}", nps) },
                InfoItem { info_type: "pv".to_string(), data: moves_string },
            ]));
        }
        self.silent_since = SystemTime::now();
    }

    /// A helper method. It it adds a message containing the current
    /// best move to the queue.
    fn queue_best_move(&mut self) {
        let &SearchStatus { ref variations, .. } = &self.status;
        self.queue.push_back(EngineReply::BestMove {
            best_move: variations[0].moves.get(0).map_or("0000".to_string(), |m| m.notation()),
            ponder_move: variations[0].moves.get(1).map(|m| m.notation()),
        });
        self.silent_since = SystemTime::now();
    }

    /// Terminates the currently running search (if any), starts a new
    /// search, updates the status.
    fn start(&mut self, mut searchmoves: Vec<String>) {
        // Validate `searchmoves`.
        let mut moves = vec![];
        let legal_moves = self.position.legal_moves();
        if !searchmoves.is_empty() {
            searchmoves.sort();
            for m in legal_moves.iter() {
                if searchmoves.binary_search(&m.notation()).is_ok() {
                    moves.push(*m);
                }
            }
        };
        let searchmoves = if moves.is_empty() {
            legal_moves
        } else {
            moves
        };

        // Terminate the currently running search.
        self.terminate();

        // Update the status.
        self.status = SearchStatus {
            done: false,
            depth: 0,
            variations: vec![Variation {
                                 value: VALUE_MIN,
                                 bound: BOUND_LOWER,
                                 moves: searchmoves.first().map_or(vec![], |m| vec![*m]),
                             }],
            searched_nodes: 0,
            started_at: SystemTime::now(),
            duration_millis: 0,
            ..self.status
        };

        // Start a new search.
        self.searcher.start_search(SearchParams {
            search_id: 0,
            position: self.position.clone(),
            depth: DEPTH_MAX,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            searchmoves: searchmoves,
        });
    }

    /// Terminates the currently running search (if any), updates the
    /// status.
    fn terminate(&mut self) {
        self.searcher.terminate_search();
        while !self.status.done {
            self.wait_status_update(Duration::from_millis(1000));
        }
    }

    /// Waits for a search status update, timing out after a specified
    /// duration or earlier.
    fn wait_status_update(&mut self, duration: Duration) {
        self.searcher.wait_report(duration);
        while let Ok(r) = self.searcher.try_recv_report() {
            self.process_report(r)
        }
    }

    /// A helper method. It updates the current status according to
    /// the received report message.
    fn process_report(&mut self, report: SearchReport<Vec<Variation>>) {
        // Register the search status with the time manager.
        if let PlayWhen::TimeManagement(ref mut tm) = self.play_when {
            tm.update_status(&report);
        }

        let duration = self.status.started_at.elapsed().unwrap();
        self.status.duration_millis = 1000 * duration.as_secs() +
                                      (duration.subsec_nanos() / 1000000) as u64;
        self.status.searched_nodes = report.searched_nodes;
        self.status.nps = 1000 * (self.status.nps + self.status.searched_nodes) /
                          (1000 + self.status.duration_millis);
        if self.status.depth < report.depth {
            self.status.depth = report.depth;
            self.status.variations = report.data;
        }
        self.status.done = report.done;
    }
}

/// Contains information about the current progress of a search.
struct SearchStatus {
    /// `true` if the search is done, `false` otherwise.
    pub done: bool,

    /// The search depth completed so far.
    pub depth: u8,

    /// The best variations found so far, sorted by descending first
    /// move strength. The first move in each variation will be
    /// different.
    pub variations: Vec<Variation>,

    /// The starting time for the search.
    pub started_at: SystemTime,

    /// The duration of the search in milliseconds.
    pub duration_millis: u64,

    /// The number of analyzed nodes.
    pub searched_nodes: u64,

    /// Average number of analyzed nodes per second.
    pub nps: u64,
}
<commit_msg>add support for "Clear Hash"<commit_after>//! Facilities for implementing chess engines.

pub mod time_manager;

use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{SystemTime, Duration};
use std::cmp::max;
use std::io;
use chesstypes::*;
use uci::*;
use search::*;
use self::time_manager::*;

/// Serves UCI commands until a "quit" command is received.
///
/// The current thread will block until the UCI session is closed.
///
/// Returns `Err` if the handshake was unsuccessful, or if an IO error
/// occurred.
pub fn run_server<S: SearchExecutor<ReportData = Vec<Variation>>>() -> io::Result<()> {
    run_engine::<Engine<S>>()
}

/// The chess starting position in Forsyth–Edwards notation (FEN).
const START_POSITION_FEN: &'static str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w QKqk - 0 1";

/// The version of the program.
const VERSION: &'static str = "0.1";

/// The name of the program.
const NAME: &'static str = "Alcibiades";

/// The author of the program.
const AUTHOR: &'static str = "Evgeni Pandurski";

/// Represents a condition for terminating the search.
enum PlayWhen {
    TimeManagement(TimeManager), // Stop when the time manager says.
    MoveTime(u64), // Stop after the given number of milliseconds.
    Nodes(u64), // Stop when the given number of nodes has been searched.
    Depth(u8), // Stop when the given search depth has been reached.
    Never, // An infinite search.
}

/// Implements `UciEngine` trait.
struct Engine<S: SearchExecutor<ReportData = Vec<Variation>>> {
    tt: Arc<S::HashTable>,
    position: S::SearchNode,
    current_depth: u8,

    // From `SearchThread`.
    status: SearchStatus,
    searcher: S,

    // Tells the engine when it must stop thinking and play the best
    // move it has found.
    play_when: PlayWhen,

    // Tells the engine how many best lines of play to calculate and
    // send to the GUI (the first move in each line will be
    // different). This is the so called "MultiPV" mode.
    variation_count: usize,

    // Tells the engine if it will be allowed to ponder. This option
    // is needed because the engine might change its time management
    // algorithm when pondering is allowed.
    pondering_is_allowed: bool,

    // `true` if the engine is thinking in pondering mode at the
    // moment.
    is_pondering: bool,

    // This helps the engine decide when to send periodic progress
    // reports.
    silent_since: SystemTime,

    // A queue for the messages send by the engine to the GUI.
    queue: VecDeque<EngineReply>,
}

impl<S: SearchExecutor<ReportData = Vec<Variation>>> UciEngine for Engine<S> {
    fn name() -> String {
        format!("{} {}", NAME, VERSION)
    }

    fn author() -> String {
        AUTHOR.to_string()
    }

    fn options() -> Vec<(String, OptionDescription)> {
        // Add up all suported options.
        let mut options = vec![
            ("Hash".to_string(), OptionDescription::Spin { min: 1, max: 64 * 1024, default: 16 }),
            ("Clear Hash".to_string(), OptionDescription::Button),
            ("Ponder".to_string(), OptionDescription::Check { default: false }),
        ];
        options.extend(S::options());

        // Remove duplicated options.
        options.sort_by(|a, b| a.0.cmp(&b.0));
        let mut options_dedup = vec![];
        let mut prev_name = String::from("");
        for o in options.drain(..) {
            if o.0 == prev_name {
                continue;
            }
            prev_name = o.0.clone();
            options_dedup.push(o);
        }
        options_dedup
    }

    fn new(tt_size_mb: Option<usize>) -> Engine<S> {
        let tt = Arc::new(S::HashTable::new(tt_size_mb));
        Engine {
            tt: tt.clone(),
            position: S::SearchNode::from_history(START_POSITION_FEN, &mut vec![].into_iter())
                          .ok()
                          .unwrap(),
            current_depth: 0,
            status: SearchStatus {
                done: true,
                depth: 0,
                variations: vec![Variation {
                                     value: VALUE_MIN,
                                     bound: BOUND_LOWER,
                                     moves: vec![],
                                 }],
                searched_nodes: 0,
                started_at: SystemTime::now(),
                duration_millis: 0,
                nps: 0,
            },
            searcher: S::new(tt),
            play_when: PlayWhen::Never,
            variation_count: 1,
            pondering_is_allowed: false,
            is_pondering: false,
            silent_since: SystemTime::now(),
            queue: VecDeque::new(),
        }
    }

    fn set_option(&mut self, name: &str, value: &str) {
        match name {
            "Ponder" => {
                // TODO: Can we remove this?
                self.pondering_is_allowed = value == "true";
            }
            "MultiPV" => {
                // TODO: Can we remove this?
                self.variation_count = max(value.parse::<usize>().unwrap_or(0), 1);
            }
            "Hash" => {
                // We do not support re-sizing of the transposition
                // table once the engine has been started.
                return;
            }
            "Clear Hash" => {
                self.tt.clear();
            }
            _ => (),
        }
        S::set_option(name, value);
    }

    fn new_game(&mut self) {
        self.tt.clear();
    }

    fn position(&mut self, fen: &str, moves: &mut Iterator<Item = &str>) {
        if let Ok(p) = S::SearchNode::from_history(fen, moves) {
            self.position = p;
        }
    }

    fn go(&mut self, params: GoParams) {
        // Note: We ignore the "mate" field.
        let GoParams { searchmoves,
                       ponder,
                       wtime,
                       btime,
                       winc,
                       binc,
                       movestogo,
                       depth,
                       nodes,
                       movetime,
                       infinite,
                       .. } = params;

        self.terminate();
        self.tt.new_search();
        self.current_depth = 0;
        self.is_pondering = ponder;
        self.play_when = if infinite {
            PlayWhen::Never
        } else if movetime.is_some() {
            PlayWhen::MoveTime(movetime.unwrap())
        } else if nodes.is_some() {
            PlayWhen::Nodes(nodes.unwrap())
        } else if depth.is_some() {
            PlayWhen::Depth(depth.unwrap() as u8)
        } else {
            PlayWhen::TimeManagement(TimeManager::new(self.position.board().to_move,
                                                      self.pondering_is_allowed,
                                                      wtime,
                                                      btime,
                                                      winc,
                                                      binc,
                                                      movestogo))
        };
        self.silent_since = SystemTime::now();
        self.start(searchmoves);
    }

    fn ponder_hit(&mut self) {
        if self.status.done {
            self.queue_best_move();
        } else {
            self.is_pondering = false;
        }
    }

    fn stop(&mut self) {
        self.terminate();
        self.queue_best_move();
    }

    fn wait_for_reply(&mut self, duration: Duration) -> Option<EngineReply> {
        if self.queue.is_empty() {
            let done = self.status.done;

            // Wait for `search_thread` to do some work, and hopefully
            // update its status. (We must do this even when the
            // search is done -- in that case the next line will just
            // yield the CPU to another process.)
            self.wait_status_update(duration);

            if !done {
                let &SearchStatus { done, depth, searched_nodes, duration_millis, .. } =
                    &self.status;

                // Send the (multi)PV for each newly reached depth.
                if depth > self.current_depth {
                    self.current_depth = depth;
                    self.queue_pv();
                }

                // Send a progress report periodically.
                if self.silent_since.elapsed().unwrap().as_secs() > 10 {
                    self.queue_progress_report();
                }

                // Check if we must play now.
                if !self.is_pondering &&
                   match self.play_when {
                    PlayWhen::TimeManagement(ref tm) => done || tm.must_play(),
                    PlayWhen::MoveTime(t) => done || duration_millis >= t,
                    PlayWhen::Nodes(n) => done || searched_nodes >= n,
                    PlayWhen::Depth(d) => done || depth >= d,
                    PlayWhen::Never => false,
                } {
                    self.stop();
                }
            }
        }

        self.queue.pop_front()
    }

    fn exit(&mut self) {
        self.terminate();
    }
}

impl<S: SearchExecutor<ReportData = Vec<Variation>>> Engine<S> {
    /// A helper method. It it adds a progress report message to the
    /// queue.
    fn queue_progress_report(&mut self) {
        let &SearchStatus { depth, searched_nodes, nps, .. } = &self.status;
        self.queue.push_back(EngineReply::Info(vec![
            InfoItem { info_type: "depth".to_string(), data: format!("{}", depth) },
            InfoItem { info_type: "nodes".to_string(), data: format!("{}", searched_nodes) },
            InfoItem { info_type: "nps".to_string(), data: format!("{}", nps) },
        ]));
        self.silent_since = SystemTime::now();
    }

    /// A helper method. It it adds a message containing the current
    /// (multi)PV to the queue.
    fn queue_pv(&mut self) {
        let &SearchStatus { depth, ref variations, searched_nodes, duration_millis, nps, .. } =
            &self.status;
        for (i, &Variation { ref moves, value, bound }) in variations.iter().enumerate() {
            let bound_suffix = match bound {
                BOUND_EXACT => "",
                BOUND_UPPER => " upperbound",
                BOUND_LOWER => " lowerbound",
                _ => panic!("unexpected bound type"),
            };
            let mut moves_string = String::new();
            for m in moves {
                moves_string.push_str(&m.notation());
                moves_string.push(' ');
            }
            self.queue.push_back(EngineReply::Info(vec![
                InfoItem { info_type: "depth".to_string(), data: format!("{}", depth) },
                InfoItem { info_type: "multipv".to_string(), data: format!("{}", i + 1) },
                InfoItem { info_type: "score".to_string(), data: format!("cp {}{}", value, bound_suffix) },
                InfoItem { info_type: "time".to_string(), data: format!("{}", duration_millis) },
                InfoItem { info_type: "nodes".to_string(), data: format!("{}", searched_nodes) },
                InfoItem { info_type: "nps".to_string(), data: format!("{}", nps) },
                InfoItem { info_type: "pv".to_string(), data: moves_string },
            ]));
        }
        self.silent_since = SystemTime::now();
    }

    /// A helper method. It it adds a message containing the current
    /// best move to the queue.
    fn queue_best_move(&mut self) {
        let &SearchStatus { ref variations, .. } = &self.status;
        self.queue.push_back(EngineReply::BestMove {
            best_move: variations[0].moves.get(0).map_or("0000".to_string(), |m| m.notation()),
            ponder_move: variations[0].moves.get(1).map(|m| m.notation()),
        });
        self.silent_since = SystemTime::now();
    }

    /// Terminates the currently running search (if any), starts a new
    /// search, updates the status.
    fn start(&mut self, mut searchmoves: Vec<String>) {
        // Validate `searchmoves`.
        let mut moves = vec![];
        let legal_moves = self.position.legal_moves();
        if !searchmoves.is_empty() {
            searchmoves.sort();
            for m in legal_moves.iter() {
                if searchmoves.binary_search(&m.notation()).is_ok() {
                    moves.push(*m);
                }
            }
        };
        let searchmoves = if moves.is_empty() {
            legal_moves
        } else {
            moves
        };

        // Terminate the currently running search.
        self.terminate();

        // Update the status.
        self.status = SearchStatus {
            done: false,
            depth: 0,
            variations: vec![Variation {
                                 value: VALUE_MIN,
                                 bound: BOUND_LOWER,
                                 moves: searchmoves.first().map_or(vec![], |m| vec![*m]),
                             }],
            searched_nodes: 0,
            started_at: SystemTime::now(),
            duration_millis: 0,
            ..self.status
        };

        // Start a new search.
        self.searcher.start_search(SearchParams {
            search_id: 0,
            position: self.position.clone(),
            depth: DEPTH_MAX,
            lower_bound: VALUE_MIN,
            upper_bound: VALUE_MAX,
            searchmoves: searchmoves,
        });
    }

    /// Terminates the currently running search (if any), updates the
    /// status.
    fn terminate(&mut self) {
        self.searcher.terminate_search();
        while !self.status.done {
            self.wait_status_update(Duration::from_millis(1000));
        }
    }

    /// Waits for a search status update, timing out after a specified
    /// duration or earlier.
    fn wait_status_update(&mut self, duration: Duration) {
        self.searcher.wait_report(duration);
        while let Ok(r) = self.searcher.try_recv_report() {
            self.process_report(r)
        }
    }

    /// A helper method. It updates the current status according to
    /// the received report message.
    fn process_report(&mut self, report: SearchReport<Vec<Variation>>) {
        // Register the search status with the time manager.
        if let PlayWhen::TimeManagement(ref mut tm) = self.play_when {
            tm.update_status(&report);
        }

        let duration = self.status.started_at.elapsed().unwrap();
        self.status.duration_millis = 1000 * duration.as_secs() +
                                      (duration.subsec_nanos() / 1000000) as u64;
        self.status.searched_nodes = report.searched_nodes;
        self.status.nps = 1000 * (self.status.nps + self.status.searched_nodes) /
                          (1000 + self.status.duration_millis);
        if self.status.depth < report.depth {
            self.status.depth = report.depth;
            self.status.variations = report.data;
        }
        self.status.done = report.done;
    }
}

/// Contains information about the current progress of a search.
struct SearchStatus {
    /// `true` if the search is done, `false` otherwise.
    pub done: bool,

    /// The search depth completed so far.
    pub depth: u8,

    /// The best variations found so far, sorted by descending first
    /// move strength. The first move in each variation will be
    /// different.
    pub variations: Vec<Variation>,

    /// The starting time for the search.
    pub started_at: SystemTime,

    /// The duration of the search in milliseconds.
    pub duration_millis: u64,

    /// The number of analyzed nodes.
    pub searched_nodes: u64,

    /// Average number of analyzed nodes per second.
    pub nps: u64,
}
<|endoftext|>


<commit_before>use rlua::{
    FromLua, Function as LuaFunction, Lua, MultiValue as LuaMultiValue, StdLib as LuaStdLib,
    Value as LuaValue,
};
use serde::Serialize;

use gerritbot_gerrit as gerrit;

use crate::state::{User, NOTIFICATION_FLAGS};

pub const DEFAULT_FORMAT_SCRIPT: &str = include_str!("format.lua");
const LUA_FORMAT_COMMENT_ADDED: &str = "format_comment_added";
const LUA_FORMAT_REVIEWER_ADDED: &str = "format_reviewer_added";
const LUA_FORMAT_FUNCTIONS: &[&str] = &[LUA_FORMAT_COMMENT_ADDED, LUA_FORMAT_REVIEWER_ADDED];

pub struct Formatter {
    lua: Lua,
}

impl Default for Formatter {
    fn default() -> Self {
        Self {
            lua: load_format_script(DEFAULT_FORMAT_SCRIPT).unwrap(),
        }
    }
}

fn load_format_script(script_source: &str) -> Result<Lua, String> {
    let lua_std_lib = LuaStdLib::BASE | LuaStdLib::STRING | LuaStdLib::TABLE;
    let lua = Lua::new_with(lua_std_lib);
    lua.context(|context| -> Result<(), String> {
        let globals = context.globals();
        context
            .load(script_source)
            .exec()
            .map_err(|err| format!("syntax error: {}", err))?;

        // check that the required functions are present
        for function_name in LUA_FORMAT_FUNCTIONS {
            let _: LuaFunction = globals
                .get(*function_name)
                .map_err(|_| format!("{} function missing", function_name))?;
        }

        Ok(())
    })?;
    Ok(lua)
}

fn get_flags_table<'lua>(user: &User, lua: rlua::Context<'lua>) -> rlua::Result<rlua::Table<'lua>> {
    lua.create_table_from(NOTIFICATION_FLAGS.iter().cloned().filter_map(|flag| {
        if user.has_flag(flag) {
            Some((flag.to_string(), true))
        } else {
            None
        }
    }))
}

impl Formatter {
    pub fn new(format_script: &str) -> Result<Self, String> {
        Ok(Self {
            lua: load_format_script(&format_script)?,
        })
    }

    fn format_lua<'lua, T, E>(
        lua: rlua::Context<'lua>,
        function_name: &str,
        user: &User,
        event: E,
        extra_args: Vec<LuaValue<'lua>>,
    ) -> Result<T, String>
    where
        T: FromLua<'lua>,
        E: Serialize,
    {
        let globals = lua.globals();

        let format_function: LuaFunction = globals
            .get(function_name)
            .map_err(|_| format!("{} function missing", function_name))?;

        let event = rlua_serde::to_value(lua, event)
            .map_err(|e| format!("failed to serialize event: {}", e))?;
        let flags = get_flags_table(user, lua)
            .map(LuaValue::Table)
            .map_err(|err| format!("failed to create flags table: {}", err))?;

        let args = {
            let mut v = Vec::with_capacity(2 + extra_args.len());
            v.push(event);
            v.push(flags);
            let mut extra_args = extra_args;
            v.append(&mut extra_args);
            LuaMultiValue::from_vec(v)
        };

        let result = format_function
            .call::<_, LuaValue>(args)
            .map_err(|err| format!("lua formatting function failed: {}", err))?;

        T::from_lua(result, lua).map_err(|e| format!("failed to convert formatting result: {}", e))
    }

    pub fn format_comment_added(
        &self,
        user: &User,
        event: &gerrit::CommentAddedEvent,
        is_human: bool,
    ) -> Result<Option<String>, String> {
        self.lua.context(|context| {
            Formatter::format_lua(
                context,
                LUA_FORMAT_COMMENT_ADDED,
                user,
                event,
                vec![LuaValue::Boolean(is_human)],
            )
        })
    }

    pub fn format_reviewer_added(
        &self,
        user: &User,
        event: &gerrit::ReviewerAddedEvent,
    ) -> Result<String, String> {
        self.lua.context(|context| {
            Formatter::format_lua(context, LUA_FORMAT_REVIEWER_ADDED, user, event, vec![])
        })
    }

    pub fn format_status(
        &self,
        user: Option<&User>,
        enabled_user_count: usize,
    ) -> Result<String, String> {
        let enabled = user
            .map(|u| u.has_any_flag(NOTIFICATION_FLAGS))
            .unwrap_or(false);
        let enabled_user_count = enabled_user_count - (enabled as usize);

        Ok(format!(
            "Notifications for you are **{}**. I am notifying {}.",
            if enabled { "enabled" } else { "disabled" },
            match (enabled, enabled_user_count) {
                (false, 0) => format!("no users"),
                (true, 0) => format!("no other users"),
                (false, 1) => format!("one user"),
                (true, 1) => format!("another user"),
                (false, _) => format!("{} users", enabled_user_count),
                (true, _) => format!("another {} users", enabled_user_count),
            }
        ))
    }
}

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

    use gerritbot_spark as spark;

    use crate::state::State;

    use super::*;

    const EVENT_JSON : &'static str = r#"
{"author":{"name":"Approver","username":"approver","email":"approver@approvers.com"},"approvals":[{"type":"Code-Review","description":"Code-Review","value":"2","oldValue":"-1"}],"comment":"Patch Set 1: Code-Review+2\n\nJust a buggy script. FAILURE\n\nAnd more problems. FAILURE","patchSet":{"number":1,"revision":"49a65998c02eda928559f2d0b586c20bc8e37b10","parents":["fb1909b4eda306985d2bbce769310e5a50a98cf5"],"ref":"refs/changes/42/42/1","uploader":{"name":"Author","email":"author@example.com","username":"Author"},"createdOn":1494165142,"author":{"name":"Author","email":"author@example.com","username":"Author"},"isDraft":false,"kind":"REWORK","sizeInsertions":0,"sizeDeletions":0},"change":{"project":"demo-project","branch":"master","id":"Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14","number":49,"subject":"Some review.","owner":{"name":"Author","email":"author@example.com","username":"author"},"url":"http://localhost/42","commitMessage":"Some review.\n\nChange-Id: Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14\n","status":"NEW"},"project":"demo-project","refName":"refs/heads/master","changeKey":{"id":"Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14"},"type":"comment-added","eventCreatedOn":1499190282}"#;

    const CHANGE_JSON_WITH_COMMENTS : &'static str = r#"
{"project":"gerritbot-rs","branch":"master","id":"If70442f674c595a59f3e44280570e760ba3584c4","number":1,"subject":"Bump version to 0.6.0","owner":{"name":"Administrator","email":"admin@example.com","username":"admin"},"url":"http://localhost:8080/1","commitMessage":"Bump version to 0.6.0\n\nChange-Id: If70442f674c595a59f3e44280570e760ba3584c4\n","createdOn":1524584729,"lastUpdated":1524584975,"open":true,"status":"NEW","comments":[{"timestamp":1524584729,"reviewer":{"name":"Administrator","email":"admin@example.com","username":"admin"},"message":"Uploaded patch set 1."},{"timestamp":1524584975,"reviewer":{"name":"jdoe","email":"john.doe@localhost","username":"jdoe"},"message":"Patch Set 1:\n\n(1 comment)"}]}"#;

    const PATCHSET_JSON_WITH_COMMENTS : &'static str = r#"{"number":1,"revision":"3f58af760fc1e39fcc4a85b8ab6a6be032cf2ae2","parents":["578bc1e684098d2ac597e030442c3472f15ac3ad"],"ref":"refs/changes/01/1/1","uploader":{"name":"Administrator","email":"admin@example.com","username":"admin"},"createdOn":1524584729,"author":{"name":"jdoe","email":"jdoe@example.com","username":""},"isDraft":false,"kind":"REWORK","comments":[{"file":"/COMMIT_MSG","line":1,"reviewer":{"name":"jdoe","email":"john.doe@localhost","username":"jdoe"},"message":"This is a multiline\ncomment\non some change."}],"sizeInsertions":2,"sizeDeletions":-2}"#;

    fn get_event() -> gerrit::CommentAddedEvent {
        let event: Result<gerrit::Event, _> = serde_json::from_str(EVENT_JSON);
        match event.expect("failed to decode event") {
            gerrit::Event::CommentAdded(event) => event,
            event => panic!("wrong type of event: {:?}", event),
        }
    }

    fn get_change_with_comments() -> (gerrit::Change, gerrit::Patchset) {
        let change: Result<gerrit::Change, _> = serde_json::from_str(CHANGE_JSON_WITH_COMMENTS);
        assert!(change.is_ok());
        let patchset: Result<gerrit::Patchset, _> =
            serde_json::from_str(PATCHSET_JSON_WITH_COMMENTS);
        assert!(patchset.is_ok());
        (change.unwrap(), patchset.unwrap())
    }

    lazy_static! {
        static ref FORMAT_TEST_STATE: State = {
            let mut state = State::new();
            state.add_user(spark::EmailRef::new("some@example.com"));
            state
        };
        static ref FORMAT_TEST_USER: &'static User = FORMAT_TEST_STATE
            .find_user(spark::EmailRef::new("some@example.com"))
            .unwrap();
    }

    #[test]
    fn test_format_approval() {
        let event = get_event();
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 👍 +2 (Code-Review) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_unknown_labels() {
        let mut event = get_event();
        event
            .approvals
            .as_mut()
            .map(|approvals| approvals[0].approval_type = String::from("Some-New-Type"));
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 🤩 +2 (Some-New-Type) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_multiple_labels() {
        let mut event = get_event();
        event.approvals.as_mut().map(|approvals| {
            approvals.push(gerrit::Approval {
                approval_type: "Verified".to_string(),
                description: "Verified".to_string(),
                value: "1".to_string(),
                old_value: None,
                by: None,
            })
        });
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 👍 +2 (Code-Review), 🌞 +1 (Verified) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_no_approvals() {
        let mut event = get_event();
        event.approvals = None;
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(res, Ok(None));
    }

    #[test]
    fn test_format_comments() {
        let mut event = get_event();
        let (change, patchset) = get_change_with_comments();
        event.comment = "(1 comment)".to_string();
        event.change = change;
        event.patchset = patchset;

        let res = Formatter::default()
            .format_comment_added(&FORMAT_TEST_USER, &event, true)
            .expect("format failed")
            .expect("no comments");

        assert!(res.ends_with("`/COMMIT_MSG`\n\n> [Line 1](http://localhost:8080/#/c/1/1//COMMIT_MSG@1) by [jdoe](http://localhost:8080/q/reviewer:john.doe@localhost+status:open): This is a multiline\n> comment\n> on some change.\n"), "no inline comments: {:?}", res);
    }
}
<commit_msg>fix test from previous commit<commit_after>use rlua::{
    FromLua, Function as LuaFunction, Lua, MultiValue as LuaMultiValue, StdLib as LuaStdLib,
    Value as LuaValue,
};
use serde::Serialize;

use gerritbot_gerrit as gerrit;

use crate::state::{User, NOTIFICATION_FLAGS};

pub const DEFAULT_FORMAT_SCRIPT: &str = include_str!("format.lua");
const LUA_FORMAT_COMMENT_ADDED: &str = "format_comment_added";
const LUA_FORMAT_REVIEWER_ADDED: &str = "format_reviewer_added";
const LUA_FORMAT_FUNCTIONS: &[&str] = &[LUA_FORMAT_COMMENT_ADDED, LUA_FORMAT_REVIEWER_ADDED];

pub struct Formatter {
    lua: Lua,
}

impl Default for Formatter {
    fn default() -> Self {
        Self {
            lua: load_format_script(DEFAULT_FORMAT_SCRIPT).unwrap(),
        }
    }
}

fn load_format_script(script_source: &str) -> Result<Lua, String> {
    let lua_std_lib = LuaStdLib::BASE | LuaStdLib::STRING | LuaStdLib::TABLE;
    let lua = Lua::new_with(lua_std_lib);
    lua.context(|context| -> Result<(), String> {
        let globals = context.globals();
        context
            .load(script_source)
            .exec()
            .map_err(|err| format!("syntax error: {}", err))?;

        // check that the required functions are present
        for function_name in LUA_FORMAT_FUNCTIONS {
            let _: LuaFunction = globals
                .get(*function_name)
                .map_err(|_| format!("{} function missing", function_name))?;
        }

        Ok(())
    })?;
    Ok(lua)
}

fn get_flags_table<'lua>(user: &User, lua: rlua::Context<'lua>) -> rlua::Result<rlua::Table<'lua>> {
    lua.create_table_from(NOTIFICATION_FLAGS.iter().cloned().filter_map(|flag| {
        if user.has_flag(flag) {
            Some((flag.to_string(), true))
        } else {
            None
        }
    }))
}

impl Formatter {
    pub fn new(format_script: &str) -> Result<Self, String> {
        Ok(Self {
            lua: load_format_script(&format_script)?,
        })
    }

    fn format_lua<'lua, T, E>(
        lua: rlua::Context<'lua>,
        function_name: &str,
        user: &User,
        event: E,
        extra_args: Vec<LuaValue<'lua>>,
    ) -> Result<T, String>
    where
        T: FromLua<'lua>,
        E: Serialize,
    {
        let globals = lua.globals();

        let format_function: LuaFunction = globals
            .get(function_name)
            .map_err(|_| format!("{} function missing", function_name))?;

        let event = rlua_serde::to_value(lua, event)
            .map_err(|e| format!("failed to serialize event: {}", e))?;
        let flags = get_flags_table(user, lua)
            .map(LuaValue::Table)
            .map_err(|err| format!("failed to create flags table: {}", err))?;

        let args = {
            let mut v = Vec::with_capacity(2 + extra_args.len());
            v.push(event);
            v.push(flags);
            let mut extra_args = extra_args;
            v.append(&mut extra_args);
            LuaMultiValue::from_vec(v)
        };

        let result = format_function
            .call::<_, LuaValue>(args)
            .map_err(|err| format!("lua formatting function failed: {}", err))?;

        T::from_lua(result, lua).map_err(|e| format!("failed to convert formatting result: {}", e))
    }

    pub fn format_comment_added(
        &self,
        user: &User,
        event: &gerrit::CommentAddedEvent,
        is_human: bool,
    ) -> Result<Option<String>, String> {
        self.lua.context(|context| {
            Formatter::format_lua(
                context,
                LUA_FORMAT_COMMENT_ADDED,
                user,
                event,
                vec![LuaValue::Boolean(is_human)],
            )
        })
    }

    pub fn format_reviewer_added(
        &self,
        user: &User,
        event: &gerrit::ReviewerAddedEvent,
    ) -> Result<String, String> {
        self.lua.context(|context| {
            Formatter::format_lua(context, LUA_FORMAT_REVIEWER_ADDED, user, event, vec![])
        })
    }

    pub fn format_status(
        &self,
        user: Option<&User>,
        enabled_user_count: usize,
    ) -> Result<String, String> {
        let enabled = user
            .map(|u| u.has_any_flag(NOTIFICATION_FLAGS))
            .unwrap_or(false);
        let enabled_user_count = enabled_user_count - (enabled as usize);

        Ok(format!(
            "Notifications for you are **{}**. I am notifying {}.",
            if enabled { "enabled" } else { "disabled" },
            match (enabled, enabled_user_count) {
                (false, 0) => format!("no users"),
                (true, 0) => format!("no other users"),
                (false, 1) => format!("one user"),
                (true, 1) => format!("another user"),
                (false, _) => format!("{} users", enabled_user_count),
                (true, _) => format!("another {} users", enabled_user_count),
            }
        ))
    }
}

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

    use gerritbot_spark as spark;

    use crate::state::State;

    use super::*;

    const EVENT_JSON : &'static str = r#"
{"author":{"name":"Approver","username":"approver","email":"approver@approvers.com"},"approvals":[{"type":"Code-Review","description":"Code-Review","value":"2","oldValue":"-1"}],"comment":"Patch Set 1: Code-Review+2\n\nJust a buggy script. FAILURE\n\nAnd more problems. FAILURE","patchSet":{"number":1,"revision":"49a65998c02eda928559f2d0b586c20bc8e37b10","parents":["fb1909b4eda306985d2bbce769310e5a50a98cf5"],"ref":"refs/changes/42/42/1","uploader":{"name":"Author","email":"author@example.com","username":"Author"},"createdOn":1494165142,"author":{"name":"Author","email":"author@example.com","username":"Author"},"isDraft":false,"kind":"REWORK","sizeInsertions":0,"sizeDeletions":0},"change":{"project":"demo-project","branch":"master","id":"Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14","number":49,"subject":"Some review.","owner":{"name":"Author","email":"author@example.com","username":"author"},"url":"http://localhost/42","commitMessage":"Some review.\n\nChange-Id: Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14\n","status":"NEW"},"project":"demo-project","refName":"refs/heads/master","changeKey":{"id":"Ic160fa37fca005fec17a2434aadf0d9dcfbb7b14"},"type":"comment-added","eventCreatedOn":1499190282}"#;

    const CHANGE_JSON_WITH_COMMENTS : &'static str = r#"
{"project":"gerritbot-rs","branch":"master","id":"If70442f674c595a59f3e44280570e760ba3584c4","number":1,"subject":"Bump version to 0.6.0","owner":{"name":"Administrator","email":"admin@example.com","username":"admin"},"url":"http://localhost:8080/1","commitMessage":"Bump version to 0.6.0\n\nChange-Id: If70442f674c595a59f3e44280570e760ba3584c4\n","createdOn":1524584729,"lastUpdated":1524584975,"open":true,"status":"NEW","comments":[{"timestamp":1524584729,"reviewer":{"name":"Administrator","email":"admin@example.com","username":"admin"},"message":"Uploaded patch set 1."},{"timestamp":1524584975,"reviewer":{"name":"jdoe","email":"john.doe@localhost","username":"jdoe"},"message":"Patch Set 1:\n\n(1 comment)"}]}"#;

    const PATCHSET_JSON_WITH_COMMENTS : &'static str = r#"{"number":1,"revision":"3f58af760fc1e39fcc4a85b8ab6a6be032cf2ae2","parents":["578bc1e684098d2ac597e030442c3472f15ac3ad"],"ref":"refs/changes/01/1/1","uploader":{"name":"Administrator","email":"admin@example.com","username":"admin"},"createdOn":1524584729,"author":{"name":"jdoe","email":"jdoe@example.com","username":""},"isDraft":false,"kind":"REWORK","comments":[{"file":"/COMMIT_MSG","line":1,"reviewer":{"name":"jdoe","email":"john.doe@localhost","username":"jdoe"},"message":"This is a multiline\ncomment\non some change."}],"sizeInsertions":2,"sizeDeletions":-2}"#;

    fn get_event() -> gerrit::CommentAddedEvent {
        let event: Result<gerrit::Event, _> = serde_json::from_str(EVENT_JSON);
        match event.expect("failed to decode event") {
            gerrit::Event::CommentAdded(event) => event,
            event => panic!("wrong type of event: {:?}", event),
        }
    }

    fn get_change_with_comments() -> (gerrit::Change, gerrit::Patchset) {
        let change: Result<gerrit::Change, _> = serde_json::from_str(CHANGE_JSON_WITH_COMMENTS);
        assert!(change.is_ok());
        let patchset: Result<gerrit::Patchset, _> =
            serde_json::from_str(PATCHSET_JSON_WITH_COMMENTS);
        assert!(patchset.is_ok());
        (change.unwrap(), patchset.unwrap())
    }

    lazy_static! {
        static ref FORMAT_TEST_STATE: State = {
            let mut state = State::new();
            state.add_user(spark::EmailRef::new("some@example.com"));
            state
        };
        static ref FORMAT_TEST_USER: &'static User = FORMAT_TEST_STATE
            .find_user(spark::EmailRef::new("some@example.com"))
            .unwrap();
    }

    #[test]
    fn test_format_approval() {
        let event = get_event();
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 👍 +2 (Code-Review) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_unknown_labels() {
        let mut event = get_event();
        event
            .approvals
            .as_mut()
            .map(|approvals| approvals[0].approval_type = String::from("Some-New-Type"));
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 🤩 +2 (Some-New-Type) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_multiple_labels() {
        let mut event = get_event();
        event.approvals.as_mut().map(|approvals| {
            approvals.push(gerrit::Approval {
                approval_type: "Verified".to_string(),
                description: Some("Verified".to_string()),
                value: "1".to_string(),
                old_value: None,
                by: None,
            })
        });
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(
            res,
            Ok(Some("[Some review.](http://localhost/42) ([demo-project](http://localhost/q/project:demo-project+status:open)) 👍 +2 (Code-Review), 🌞 +1 (Verified) from [Approver](http://localhost/q/reviewer:approver@approvers.com+status:open)\n\n> Just a buggy script. FAILURE\n\n> And more problems. FAILURE"))
        );
    }

    #[test]
    fn format_approval_no_approvals() {
        let mut event = get_event();
        event.approvals = None;
        let res = Formatter::default().format_comment_added(&FORMAT_TEST_USER, &event, true);
        // Result<Option<String>, _> -> Result<Option<&str>, _>
        let res = res.as_ref().map(|o| o.as_ref().map(String::as_str));
        assert_eq!(res, Ok(None));
    }

    #[test]
    fn test_format_comments() {
        let mut event = get_event();
        let (change, patchset) = get_change_with_comments();
        event.comment = "(1 comment)".to_string();
        event.change = change;
        event.patchset = patchset;

        let res = Formatter::default()
            .format_comment_added(&FORMAT_TEST_USER, &event, true)
            .expect("format failed")
            .expect("no comments");

        assert!(res.ends_with("`/COMMIT_MSG`\n\n> [Line 1](http://localhost:8080/#/c/1/1//COMMIT_MSG@1) by [jdoe](http://localhost:8080/q/reviewer:john.doe@localhost+status:open): This is a multiline\n> comment\n> on some change.\n"), "no inline comments: {:?}", res);
    }
}
<|endoftext|>