<commit_before>// Copyright (c) 2016
// Jeff Nettleton
//
// Licensed under the MIT license (http://opensource.org/licenses/MIT). This
// file may not be copied, modified, or distributed except according to those
// terms

use std;
use std::collections::HashMap;
use serde_json;
use serde::de::DeserializeOwned;

/// This enum represents the various types of HTTP requests.
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub enum Method {
    Get,
    Put,
    Post,
    Delete,
    NoImpl,
}

/// This enum represents the errors that might be encountered.
#[derive(Debug)]
pub enum RequestError {
    ParseError(String),
    JsonObjError(serde_json::Error),
    JsonStrError(serde_json::Error),
    StrCopyError(std::string::FromUtf8Error),
}

impl From<serde_json::Error> for RequestError {
    fn from(err: serde_json::Error) -> RequestError {
        RequestError::JsonStrError(err)
    }
}

impl From<std::string::FromUtf8Error> for RequestError {
    fn from(err: std::string::FromUtf8Error) -> RequestError {
        RequestError::StrCopyError(err)
    }
}

/// A trait that allows for extracting variables from URIs.
pub trait FromUri {
    /// A function to parse a string into the correct type.
    fn from_uri(data: &str) -> Self;
}

impl FromUri for String {
    fn from_uri(data: &str) -> String {
        String::from(data)
    }
}

impl FromUri for i32 {
    fn from_uri(data: &str) -> i32 {
        data.parse::<i32>().expect("matched integer can't be parsed")
    }
}

impl FromUri for u32 {
    fn from_uri(data: &str) -> u32 {
        data.parse::<u32>().expect("matched integer can't be parsed")
    }
}

impl FromUri for f32 {
    fn from_uri(data: &str) -> f32 {
        data.parse::<f32>().expect("matched float can't be parsed")
    }
}

/// This struct represents a request from an HTTP client.
#[derive(Debug)]
pub struct Request {
    pub method:  Method,
    pub path:    String,
    pub payload: Vec<u8>,
    pub params:  HashMap<String, String>,
    headers:     HashMap<String, String>,
}

impl Request {
    /// Create a new, empty Request.
    pub fn new() -> Request {
        Request {
            method:  Method::NoImpl,
            path:    String::new(),
            headers: HashMap::new(),
            params:  HashMap::new(),
            payload: Vec::with_capacity(2048),
        }
    }

    /// Get an HTTP header contained in the Request.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let browser = req.get_header("User-Agent");
    ///
    ///     match browser {
    ///         Some(ua) => utils::make_response(format!("You're using {}!", ua), "text/plain", 200),
    ///         None     => utils::make_response("Bad browser, no user agent!", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_header(&self, name: &str) -> Option<String> {
        let key = String::from(name);

        match self.headers.get(&key) {
            Some(val)   => Some(val.clone()),
            None        => None,
        }
    }

    /// Get a variable from the URI.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the route "/hello/<str:name>"
    /// fn handler(req: &Request) -> Response {
    ///     let name: String = req.get("name");
    ///     utils::make_response(format!("<b>Hello, {}!</b>", name), "text/html", 200)
    /// }
    /// ```
    pub fn get<T: FromUri>(&self, name: &str) -> T {
        if !self.params.contains_key(name) {
            panic!("invalid route parameter {:?}", name);
        }

        FromUri::from_uri(&self.params[name])
    }

    /// Get a raw JSON payload from the request.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the POST route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let data = req.get_json();
    ///
    ///     match data {
    ///         Ok(val) => utils::make_response(format!("We got: {}", val), "text/plain", 200),
    ///         Err(_)  => utils::make_response("We got nothing :(", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_json(&self) -> Result<serde_json::Value, RequestError> {
        let payload = String::from_utf8(self.payload.clone())?;
        let data = serde_json::from_str(&payload)?;

        Ok(data)
    }

    /// Get a composed JSON payload from the request.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use canteen::{Request, Response};
    ///
    /// #[derive(RustcDecodable)]
    /// struct Foo {
    ///     item: i32,
    /// }
    ///
    /// // Given the POST route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let data: Foo = req.get_json_obj();
    ///
    ///     match data {
    ///         Ok(foo) => utils::make_response(format!("We got: {}!", data.item), "text/plain", 200),
    ///         Err(_)  => utils::make_response("We got nothing :(", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_json_obj<T>(&self) -> Result<T, RequestError>
                where T: DeserializeOwned {
        let payload = String::from_utf8(self.payload.clone())?;
        let data = serde_json::from_str(&payload)?;

        Ok(data)
    }

    fn parse(&mut self, rqstr: &str) {
        let mut buf: Vec<&str> = rqstr.splitn(2, "\r\n").collect();
        let ask: Vec<&str> = buf[0].splitn(3, ' ').collect();

        self.method = match ask[0] {
            "GET"           => Method::Get,
            "PUT" | "PATCH" => Method::Put,
            "POST"          => Method::Post,
            "DELETE"        => Method::Delete,
            _               => Method::NoImpl,
        };
        self.path = String::from(ask[1]);

        loop {
            buf = buf[1].splitn(2, "\r\n").collect();

            if buf[0] == "" {
                if buf.len() == 1 || buf[1] == "" {
                    // no payload
                    break;
                }

                self.payload.extend(buf[1].as_bytes());
                break;
            }

            let hdr: Vec<&str> = buf[0].splitn(2, ": ").collect();

            if hdr.len() == 2 {
                self.headers.insert(String::from(hdr[0]), String::from(hdr[1]));
            }
        }
    }
}

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

impl std::str::FromStr for Request {
    type Err = RequestError;

    /// Create a Request from an HTTP request string.
    fn from_str(rqstr: &str) -> Result<Self, Self::Err> {
        let mut req = Request::new();
        req.parse(rqstr);
        Ok(req)
    }
}

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

    #[derive(Deserialize)]
    struct Foo {
        item: i32,
    }

    #[test]
    fn test_fromuri_trait_i32() {
        let pos = String::from("1234");
        assert_eq!(1234, <i32 as FromUri>::from_uri(&pos));

        let neg = String::from("-4321");
        assert_eq!(-4321, <i32 as FromUri>::from_uri(&neg));
    }

    #[test]
    fn test_fromuri_trait_u32() {
        let orig = String::from("1234");
        assert_eq!(1234, <u32 as FromUri>::from_uri(&orig));
    }

    #[test]
    fn test_fromuri_trait_string() {
        let orig = String::from("foobar");
        assert_eq!("foobar", <String as FromUri>::from_uri(&orig));
    }

    #[test]
    fn test_fromuri_trait_float() {
        let pos = String::from("123.45");
        assert_eq!(123.45f32, <f32 as FromUri>::from_uri(&pos));

        let neg = String::from("-54.321");
        assert_eq!(-54.321f32, <f32 as FromUri>::from_uri(&neg));
    }

    #[test]
    fn test_get_fromuri_i32() {
        let mut req = Request::new();
        req.params.insert(String::from("test"), String::from("1234"));
        let val: i32 = req.get("test");

        assert_eq!(1234, val);
    }

    #[test]
    fn test_get_json() {
        let mut req = Request::new();
        req.payload.extend_from_slice("{ \"item\": 123 }".as_bytes());

        let data = req.get_json().unwrap();

        assert_eq!(true, data.is_object());

        let obj = data.as_object().unwrap();
        let val = obj.get("item").unwrap();

        assert_eq!(true, val.is_u64());
        assert_eq!(123u64, val.as_u64().unwrap());
    }

    #[test]
    fn test_get_json_obj() {
        let mut req = Request::new();
        req.payload.extend_from_slice("{ \"item\": 123 }".as_bytes());

        let data: Foo = req.get_json_obj().unwrap();

        assert_eq!(123, data.item);
    }
}
<commit_msg>Remove unused RequestError variants<commit_after>// Copyright (c) 2016
// Jeff Nettleton
//
// Licensed under the MIT license (http://opensource.org/licenses/MIT). This
// file may not be copied, modified, or distributed except according to those
// terms

use std;
use std::collections::HashMap;
use serde_json;
use serde::de::DeserializeOwned;

/// This enum represents the various types of HTTP requests.
#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
pub enum Method {
    Get,
    Put,
    Post,
    Delete,
    NoImpl,
}

/// This enum represents the errors that might be encountered.
#[derive(Debug)]
pub enum RequestError {
    JsonStrError(serde_json::Error),
    StrCopyError(std::string::FromUtf8Error),
}

impl From<serde_json::Error> for RequestError {
    fn from(err: serde_json::Error) -> RequestError {
        RequestError::JsonStrError(err)
    }
}

impl From<std::string::FromUtf8Error> for RequestError {
    fn from(err: std::string::FromUtf8Error) -> RequestError {
        RequestError::StrCopyError(err)
    }
}

/// A trait that allows for extracting variables from URIs.
pub trait FromUri {
    /// A function to parse a string into the correct type.
    fn from_uri(data: &str) -> Self;
}

impl FromUri for String {
    fn from_uri(data: &str) -> String {
        String::from(data)
    }
}

impl FromUri for i32 {
    fn from_uri(data: &str) -> i32 {
        data.parse::<i32>().expect("matched integer can't be parsed")
    }
}

impl FromUri for u32 {
    fn from_uri(data: &str) -> u32 {
        data.parse::<u32>().expect("matched integer can't be parsed")
    }
}

impl FromUri for f32 {
    fn from_uri(data: &str) -> f32 {
        data.parse::<f32>().expect("matched float can't be parsed")
    }
}

/// This struct represents a request from an HTTP client.
#[derive(Debug)]
pub struct Request {
    pub method:  Method,
    pub path:    String,
    pub payload: Vec<u8>,
    pub params:  HashMap<String, String>,
    headers:     HashMap<String, String>,
}

impl Request {
    /// Create a new, empty Request.
    pub fn new() -> Request {
        Request {
            method:  Method::NoImpl,
            path:    String::new(),
            headers: HashMap::new(),
            params:  HashMap::new(),
            payload: Vec::with_capacity(2048),
        }
    }

    /// Get an HTTP header contained in the Request.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let browser = req.get_header("User-Agent");
    ///
    ///     match browser {
    ///         Some(ua) => utils::make_response(format!("You're using {}!", ua), "text/plain", 200),
    ///         None     => utils::make_response("Bad browser, no user agent!", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_header(&self, name: &str) -> Option<String> {
        let key = String::from(name);

        match self.headers.get(&key) {
            Some(val)   => Some(val.clone()),
            None        => None,
        }
    }

    /// Get a variable from the URI.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the route "/hello/<str:name>"
    /// fn handler(req: &Request) -> Response {
    ///     let name: String = req.get("name");
    ///     utils::make_response(format!("<b>Hello, {}!</b>", name), "text/html", 200)
    /// }
    /// ```
    pub fn get<T: FromUri>(&self, name: &str) -> T {
        if !self.params.contains_key(name) {
            panic!("invalid route parameter {:?}", name);
        }

        FromUri::from_uri(&self.params[name])
    }

    /// Get a raw JSON payload from the request.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use canteen::{Request, Response};
    /// use canteen::utils;
    ///
    /// // Given the POST route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let data = req.get_json();
    ///
    ///     match data {
    ///         Ok(val) => utils::make_response(format!("We got: {}", val), "text/plain", 200),
    ///         Err(_)  => utils::make_response("We got nothing :(", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_json(&self) -> Result<serde_json::Value, RequestError> {
        let payload = String::from_utf8(self.payload.clone())?;
        let data = serde_json::from_str(&payload)?;

        Ok(data)
    }

    /// Get a composed JSON payload from the request.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// use canteen::{Request, Response};
    ///
    /// #[derive(RustcDecodable)]
    /// struct Foo {
    ///     item: i32,
    /// }
    ///
    /// // Given the POST route "/hello"
    /// fn handler(req: &Request) -> Response {
    ///     let data: Foo = req.get_json_obj();
    ///
    ///     match data {
    ///         Ok(foo) => utils::make_response(format!("We got: {}!", data.item), "text/plain", 200),
    ///         Err(_)  => utils::make_response("We got nothing :(", "text/plain", 200),
    ///     }
    /// }
    /// ```
    pub fn get_json_obj<T>(&self) -> Result<T, RequestError>
                where T: DeserializeOwned {
        let payload = String::from_utf8(self.payload.clone())?;
        let data = serde_json::from_str(&payload)?;

        Ok(data)
    }

    fn parse(&mut self, rqstr: &str) {
        let mut buf: Vec<&str> = rqstr.splitn(2, "\r\n").collect();
        let ask: Vec<&str> = buf[0].splitn(3, ' ').collect();

        self.method = match ask[0] {
            "GET"           => Method::Get,
            "PUT" | "PATCH" => Method::Put,
            "POST"          => Method::Post,
            "DELETE"        => Method::Delete,
            _               => Method::NoImpl,
        };
        self.path = String::from(ask[1]);

        loop {
            buf = buf[1].splitn(2, "\r\n").collect();

            if buf[0] == "" {
                if buf.len() == 1 || buf[1] == "" {
                    // no payload
                    break;
                }

                self.payload.extend(buf[1].as_bytes());
                break;
            }

            let hdr: Vec<&str> = buf[0].splitn(2, ": ").collect();

            if hdr.len() == 2 {
                self.headers.insert(String::from(hdr[0]), String::from(hdr[1]));
            }
        }
    }
}

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

impl std::str::FromStr for Request {
    type Err = RequestError;

    /// Create a Request from an HTTP request string.
    fn from_str(rqstr: &str) -> Result<Self, Self::Err> {
        let mut req = Request::new();
        req.parse(rqstr);
        Ok(req)
    }
}

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

    #[derive(Deserialize)]
    struct Foo {
        item: i32,
    }

    #[test]
    fn test_fromuri_trait_i32() {
        let pos = String::from("1234");
        assert_eq!(1234, <i32 as FromUri>::from_uri(&pos));

        let neg = String::from("-4321");
        assert_eq!(-4321, <i32 as FromUri>::from_uri(&neg));
    }

    #[test]
    fn test_fromuri_trait_u32() {
        let orig = String::from("1234");
        assert_eq!(1234, <u32 as FromUri>::from_uri(&orig));
    }

    #[test]
    fn test_fromuri_trait_string() {
        let orig = String::from("foobar");
        assert_eq!("foobar", <String as FromUri>::from_uri(&orig));
    }

    #[test]
    fn test_fromuri_trait_float() {
        let pos = String::from("123.45");
        assert_eq!(123.45f32, <f32 as FromUri>::from_uri(&pos));

        let neg = String::from("-54.321");
        assert_eq!(-54.321f32, <f32 as FromUri>::from_uri(&neg));
    }

    #[test]
    fn test_get_fromuri_i32() {
        let mut req = Request::new();
        req.params.insert(String::from("test"), String::from("1234"));
        let val: i32 = req.get("test");

        assert_eq!(1234, val);
    }

    #[test]
    fn test_get_json() {
        let mut req = Request::new();
        req.payload.extend_from_slice("{ \"item\": 123 }".as_bytes());

        let data = req.get_json().unwrap();

        assert_eq!(true, data.is_object());

        let obj = data.as_object().unwrap();
        let val = obj.get("item").unwrap();

        assert_eq!(true, val.is_u64());
        assert_eq!(123u64, val.as_u64().unwrap());
    }

    #[test]
    fn test_get_json_obj() {
        let mut req = Request::new();
        req.payload.extend_from_slice("{ \"item\": 123 }".as_bytes());

        let data: Foo = req.get_json_obj().unwrap();

        assert_eq!(123, data.item);
    }
}
<|endoftext|>


<commit_before>#[cfg(feature = "reqwest")]
extern crate reqwest;

use Value;
use error::{Error, RequestErrorKind};
use utils::escape_xml;
use transport::Transport;
use parser::parse_response;

use std::io::{self, Write};
use std::collections::BTreeMap;

/// A request to call a procedure.
#[derive(Clone, Debug)]
pub struct Request<'a> {
    name: &'a str,
    args: Vec<Value>,
}

impl<'a> Request<'a> {
    /// Creates a new request to call a function named `name`.
    ///
    /// By default, no arguments are passed. Use the `arg` method to append arguments.
    pub fn new(name: &'a str) -> Self {
        Request {
            name,
            args: Vec::new(),
        }
    }

    /// Appends an argument to be passed to the current list of arguments.
    pub fn arg<T: Into<Value>>(mut self, value: T) -> Self {
        self.args.push(value.into());
        self
    }

    /// Performs the request using a [`Transport`].
    ///
    /// # Errors
    ///
    /// Any errors that occur while sending the request using the [`Transport`] will be returned to
    /// the caller. Additionally, if the response is malformed (invalid XML), or indicates that the
    /// method call failed, an error will also be returned.
    ///
    /// [`Transport`]: trait.Transport.html
    pub fn call<T: Transport>(&self, transport: T) -> Result<Value, Error> {
        let mut reader = transport.transmit(self)
            .map_err(RequestErrorKind::TransportError)?;

        let response = parse_response(&mut reader).map_err(RequestErrorKind::ParseError)?;

        let value = response.map_err(RequestErrorKind::Fault)?;
        Ok(value)
    }

    /// Performs the request on a URL.
    ///
    /// This is a convenience method that will internally create a new `reqwest::Client` and send an
    /// HTTP POST request to the given URL. If you only use this method to perform requests, you
    /// don't need to depend on `reqwest` yourself.
    ///
    /// This method is only available when the `reqwest` feature is enabled (this is the default).
    ///
    /// # Errors
    ///
    /// Since this is just a convenience wrapper around [`Request::call`], the same error conditions
    /// apply.
    ///
    /// Any reqwest errors will be propagated to the caller.
    ///
    /// [`Request::call`]: #method.call
    /// [`Transport`]: trait.Transport.html
    #[cfg(feature = "reqwest")]
    pub fn call_url(&self, url: &str) -> Result<Value, Error> {
        // While we could implement `Transport` for `&str`, such an impl might not be completely
        // obvious, so I've added this method instead. Might want to reconsider if someone has an
        // objection.
        self.call(reqwest::Client::new().post(url))
    }

    /// Formats this `Request` as a UTF-8 encoded XML document.
    ///
    /// # Errors
    ///
    /// Any errors reported by the writer will be propagated to the caller. If the writer never
    /// returns an error, neither will this method.
    pub fn write_as_xml<W: Write>(&self, fmt: &mut W) -> io::Result<()> {
        write!(fmt, r#"<?xml version="1.0" encoding="utf-8"?>"#)?;
        write!(fmt, r#"<methodCall>"#)?;
        write!(fmt, r#"    <methodName>{}</methodName>"#, escape_xml(&self.name))?;
        write!(fmt, r#"    <params>"#)?;
        for value in &self.args {
            write!(fmt, r#"        <param>"#)?;
            value.write_as_xml(fmt)?;
            write!(fmt, r#"        </param>"#)?;
        }
        write!(fmt, r#"    </params>"#)?;
        write!(fmt, r#"</methodCall>"#)?;
        Ok(())
    }

    /// Serialize this `Request` into an XML-RPC struct that can be passed to
    /// the [`system.multicall`](https://mirrors.talideon.com/articles/multicall.html)
    /// XML-RPC method, specifically a struct with two fields:
    ///
    /// * `methodName`: the request name
    /// * `params`: the request arguments
    pub fn into_multicall_struct(self) -> Value {
        let mut multicall_struct: BTreeMap<String, Value> = BTreeMap::new();

        multicall_struct.insert("methodName".into(), self.name.into());
        multicall_struct.insert("params".into(), Value::Array(self.args));

        Value::Struct(multicall_struct)
    }
}

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

    #[test]
    fn escapes_method_names() {
        let mut output: Vec<u8> = Vec::new();
        let req = Request::new("x<&x");

        req.write_as_xml(&mut output).unwrap();
        assert!(
            str::from_utf8(&output)
            .unwrap()
            .contains("<methodName>x<&x</methodName>"));
    }
}
<commit_msg>Make `call_url` generic over all `U: IntoUrl`<commit_after>#[cfg(feature = "reqwest")]
extern crate reqwest;

use Value;
use error::{Error, RequestErrorKind};
use utils::escape_xml;
use transport::Transport;
use parser::parse_response;

use std::io::{self, Write};
use std::collections::BTreeMap;

/// A request to call a procedure.
#[derive(Clone, Debug)]
pub struct Request<'a> {
    name: &'a str,
    args: Vec<Value>,
}

impl<'a> Request<'a> {
    /// Creates a new request to call a function named `name`.
    ///
    /// By default, no arguments are passed. Use the `arg` method to append arguments.
    pub fn new(name: &'a str) -> Self {
        Request {
            name,
            args: Vec::new(),
        }
    }

    /// Appends an argument to be passed to the current list of arguments.
    pub fn arg<T: Into<Value>>(mut self, value: T) -> Self {
        self.args.push(value.into());
        self
    }

    /// Performs the request using a [`Transport`].
    ///
    /// # Errors
    ///
    /// Any errors that occur while sending the request using the [`Transport`] will be returned to
    /// the caller. Additionally, if the response is malformed (invalid XML), or indicates that the
    /// method call failed, an error will also be returned.
    ///
    /// [`Transport`]: trait.Transport.html
    pub fn call<T: Transport>(&self, transport: T) -> Result<Value, Error> {
        let mut reader = transport.transmit(self)
            .map_err(RequestErrorKind::TransportError)?;

        let response = parse_response(&mut reader).map_err(RequestErrorKind::ParseError)?;

        let value = response.map_err(RequestErrorKind::Fault)?;
        Ok(value)
    }

    /// Performs the request on a URL.
    ///
    /// You can pass a `&str` or an already parsed reqwest URL.
    ///
    /// This is a convenience method that will internally create a new `reqwest::Client` and send an
    /// HTTP POST request to the given URL. If you only use this method to perform requests, you
    /// don't need to depend on `reqwest` yourself.
    ///
    /// This method is only available when the `reqwest` feature is enabled (this is the default).
    ///
    /// # Errors
    ///
    /// Since this is just a convenience wrapper around [`Request::call`], the same error conditions
    /// apply.
    ///
    /// Any reqwest errors will be propagated to the caller.
    ///
    /// [`Request::call`]: #method.call
    /// [`Transport`]: trait.Transport.html
    #[cfg(feature = "reqwest")]
    pub fn call_url<U: reqwest::IntoUrl>(&self, url: U) -> Result<Value, Error> {
        // While we could implement `Transport` for `T: IntoUrl`, such an impl might not be
        // completely obvious (as it applies to `&str), so I've added this method instead.
        // Might want to reconsider if someone has an objection.
        self.call(reqwest::Client::new().post(url))
    }

    /// Formats this `Request` as a UTF-8 encoded XML document.
    ///
    /// # Errors
    ///
    /// Any errors reported by the writer will be propagated to the caller. If the writer never
    /// returns an error, neither will this method.
    pub fn write_as_xml<W: Write>(&self, fmt: &mut W) -> io::Result<()> {
        write!(fmt, r#"<?xml version="1.0" encoding="utf-8"?>"#)?;
        write!(fmt, r#"<methodCall>"#)?;
        write!(fmt, r#"    <methodName>{}</methodName>"#, escape_xml(&self.name))?;
        write!(fmt, r#"    <params>"#)?;
        for value in &self.args {
            write!(fmt, r#"        <param>"#)?;
            value.write_as_xml(fmt)?;
            write!(fmt, r#"        </param>"#)?;
        }
        write!(fmt, r#"    </params>"#)?;
        write!(fmt, r#"</methodCall>"#)?;
        Ok(())
    }

    /// Serialize this `Request` into an XML-RPC struct that can be passed to
    /// the [`system.multicall`](https://mirrors.talideon.com/articles/multicall.html)
    /// XML-RPC method, specifically a struct with two fields:
    ///
    /// * `methodName`: the request name
    /// * `params`: the request arguments
    pub fn into_multicall_struct(self) -> Value {
        let mut multicall_struct: BTreeMap<String, Value> = BTreeMap::new();

        multicall_struct.insert("methodName".into(), self.name.into());
        multicall_struct.insert("params".into(), Value::Array(self.args));

        Value::Struct(multicall_struct)
    }
}

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

    #[test]
    fn escapes_method_names() {
        let mut output: Vec<u8> = Vec::new();
        let req = Request::new("x<&x");

        req.write_as_xml(&mut output).unwrap();
        assert!(
            str::from_utf8(&output)
            .unwrap()
            .contains("<methodName>x<&x</methodName>"));
    }
}
<|endoftext|>


<commit_before>use super::scope::Scope;
use serialize::json::{Json, ToJson, Object, Array};
use super::event::EventType;
use std::string::ToString;
use std::str::FromStr;
use super::error::ParseConfigGroupError;
use std::ascii::AsciiExt;

/// The version of the DaZeus plugin communication protocol that these bindings understand.
pub const PROTOCOL_VERSION: &'static str = "1";

/// A `String` for the target IRC network.
pub type Network = String;

/// A `String` containing the message to be sent.
pub type Message = String;

/// A `String` containing the target (receiver) of some command or message.
///
/// Typically this is a client or some channel on an IRC network.
pub type Target = String;

/// A `String` indicating the name of some command.
pub type Command = String;

/// A `String` indicating the name of the plugin. This is used for retrieving configuration.
pub type PluginName = String;

/// A `String` indicating the version of the protocol used by the bindings.
pub type PluginVersion = String;

/// The type of config that should be retrieved.
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigGroup {
    /// Indicates a config value that should be retrieved from the plugin settings.
    Plugin,
    /// Indicates a config value that should be retrieved from the core settings.
    Core,
}

impl ToString for ConfigGroup {
    fn to_string(&self) -> String {
        match *self {
            ConfigGroup::Plugin => "plugin".to_string(),
            ConfigGroup::Core => "core".to_string(),
        }
    }
}

impl FromStr for ConfigGroup {
    type Err = ParseConfigGroupError;

    fn from_str(s: &str) -> Result<Self, ParseConfigGroupError> {
        match &s.to_ascii_lowercase()[..] {
            "plugin" => Ok(ConfigGroup::Plugin),
            "core" => Ok(ConfigGroup::Core),
            _ => Err(ParseConfigGroupError::new())
        }
    }
}

/// An enum of all requests that can be sent to your DaZeus instance.
///
/// Note that typically you won't create these request instances directly. Instead you can use the
/// different `DaZeus` methods. However if you wish, you can directly use `DaZeus::send()` to send
/// these requests yourself.
#[derive(Debug, Clone, PartialEq)]
pub enum Request {
    /// Subscribe to a certain event type.
    ///
    /// # Example
    /// ```
    /// Request::Subscribe(EventType::PrivMsg)
    /// ```
    Subscribe(EventType),
    /// Unsubscribe from an event.
    ///
    /// Note that you cannot specify `EventType::Command()` for this request, as registered
    /// commands cannot be unregistered from the DaZeus server. To stop listening, just let DaZeus
    /// remove the listener.
    ///
    /// # Example
    /// ```
    /// Request::Unsubscribe(EventType::Join)
    /// ```
    Unsubscribe(EventType),
    /// Subscribe to a command (optionally on a specific network).
    ///
    /// You can use `Request::Subscribe(EventType::Command("example".to_string())` as an
    /// alternative to `Request::SubscribeCommand("example".to_string())`. Note that the
    /// former does not allow you to optionally specify a network on which the command is actively
    /// listened to.
    ///
    /// # Example
    /// ```
    /// Request::SubscribeCommand("greet".to_string(), Some("freenode".to_string()))
    /// ```
    SubscribeCommand(Command, Option<Network>),
    /// Retrieve a list of networks that the DaZeus core is currently connected to.
    ///
    /// # Example
    /// ```
    /// Request::Networks
    /// ```
    Networks,
    /// Retrieve a list of channels on the specified network that the bot has joined.
    ///
    /// # Example
    /// ```
    /// Request::Channels("freenode".to_string())
    /// ```
    Channels(Network),
    /// Request to send a message to a specific target on some network.
    ///
    /// This will request DaZeus to send a PRIVMSG.
    ///
    /// # Example
    /// ```
    /// Request::Message("freenode".to_string(), "#botters-test".to_string(), "Hello!".to_string())
    /// ```
    Message(Network, Target, Message),
    /// Request to send a notice to some target on some network.
    ///
    /// This will request DaZeus to send a NOTICE.
    ///
    /// # Example
    /// ```
    /// Request::Notice("example".to_string(), "MrExample".to_string(), "Message!".to_string())
    /// ```
    Notice(Network, Target, Message),
    /// Request to send a CTCP message to some client on some network.
    ///
    /// # Example
    /// ```
    /// Request::Ctcp("example".to_string(), "MrExample".to_string(), "VERSION".to_string())
    /// ```
    Ctcp(Network, Target, Message),
    /// Request to send a CTCP message reply to some client on some network.
    ///
    /// # Example
    /// ```
    /// Request::CtcpReply("example".to_string(), "MrExample".to_string(), "VERSION DaZeus 2.0".to_string())
    /// ```
    CtcpReply(Network, Target, Message),
    /// Request to send a CTCP ACTION message to some target on some network.
    ///
    /// A CTCP ACTION is most known by users as the `/me` command.
    ///
    /// # Example
    /// ```
    /// Request::Action("example".to_string(), "#example", "is creating an example".to_string())
    /// ```
    Action(Network, Target, Message),
    /// Request to send the list of names in some channel.
    ///
    /// Note that such a request will generate an `EventType::Names` event if the server allows it,
    /// instead of responding to this request directly.
    ///
    /// # Example
    /// ```
    /// Request::Names("freenode".to_string(), "#freenode".to_string())
    /// ```
    Names(Network, Target),
    /// Request to send a whois on some target.
    ///
    /// Note that such a request will generate an `EventType::Whois` event if the server allows it,
    /// instead of responding to this request directly.
    ///
    /// # Example
    /// ```
    /// Request::Whois("example".to_string(), "MrExample".to_string())
    /// ```
    Whois(Network, Target),
    /// Request to join a channel on some network.
    ///
    /// # Example
    /// ```
    /// Request::Join("freenode".to_string(), "#freenode".to_string())
    /// ```
    Join(Network, Target),
    /// Request to leave a channel on some network.
    ///
    /// # Example
    /// ```
    /// Request::Part("freenode".to_string(), "#freenode".to_string())
    /// ```
    Part(Network, Target),
    /// Request the nickname of the bot on a network.
    ///
    /// # Example
    /// ```
    /// Request::Nick("freenode".to_string())
    /// ```
    Nick(Network),
    /// Request for a handshake to the DaZeus Core.
    ///
    /// The handshake allows the plugin to retrieve configuration options.
    ///
    /// The second parameter of this request variant is the plugin version. Note that this
    /// parameter should be equal to the version that these bindings understand.
    ///
    /// The optional third parameter may provide an alternative name to be used to retrieve
    /// options from the DaZeus core config. By default the name of the plugin will be used.
    ///
    /// # Example
    /// ```
    /// Request::Handshake("my_plugin".to_string(), PROTOCOL_VERSION.to_string(), None)
    /// ```
    Handshake(PluginName, PluginVersion, Option<String>),
    /// Retrieve some option from the DaZeus core config.
    ///
    /// The second parameter is the section from which the configuration parameter should be
    /// retrieved. This may either be `ConfigGroup::Core` or `ConfigGroup::Plugin`
    ///
    /// Note that in order to successfully retrieve these configuration values the plugin first
    /// needs to have completed a succesful handshake with the core.
    ///
    /// # Example
    /// ```
    /// Request::Config("highlight".to_string(), ConfigGroup::Core)
    /// ```
    Config(String, ConfigGroup),
    /// Retrieve a property from the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::GetProperty("example".to_string(), Scope::any())
    /// ```
    GetProperty(String, Scope),
    /// Set a property in the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::SetProperty("example".to_string(), "value".to_string(), Scope::any())
    /// ```
    SetProperty(String, String, Scope),
    /// Remove a property from the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::UnsetProperty("example".to_string(), Scope::any())
    /// ```
    UnsetProperty(String, Scope),
    /// Retrieve a set of keys that is available for some prefix and scope.
    ///
    /// The first parameter of the variant is the prefix string for retrieving property keys.
    ///
    /// # Example
    /// ```
    /// Request::PropertyKeys("path.to".to_string(), Scope::network("example"))
    /// ```
    PropertyKeys(String, Scope),
    /// Set a permission in the permission database of the DaZeus core.
    ///
    /// # Example
    /// ```
    /// Request::SetPermission("edit".to_string(), true, Scope::sender("example", "MrExample"))
    /// ```
    SetPermission(String, bool, Scope),
    /// Request a permission to be retrieved from the permission database of the DaZeus core.
    ///
    /// The second parameter of this variant indicates the default value that the core should
    /// return if the permission was not found inside the permission database.
    ///
    /// # Example
    /// ```
    /// Request::HasPermission("edit".to_string(), false, Scope::sender("example", "MrExample"))
    /// ```
    HasPermission(String, bool, Scope),
    /// Remove a permission from the permission database of the DaZeus core.
    ///
    /// # Example
    /// ```
    /// Request::UnsetPermission("edit".to_string(), Scope::sender("example", "MrExample"))
    /// ```
    UnsetPermission(String, Scope),
}

impl Request {
    fn get_json_name(&self) -> Json {
        let s = match *self {
            Request::Subscribe(EventType::Command(_)) => "command",
            Request::Subscribe(_) => "subscribe",
            Request::Unsubscribe(_) => "unsubscribe",
            Request::SubscribeCommand(_, _) => "command",
            Request::Networks => "networks",
            Request::Channels(_) => "channels",
            Request::Message(_, _, _) => "message",
            Request::Notice(_, _, _) => "notice",
            Request::Ctcp(_, _, _) => "ctcp",
            Request::CtcpReply(_, _, _) => "ctcp_rep",
            Request::Action(_, _, _) => "action",
            Request::Names(_, _) => "names",
            Request::Whois(_, _) => "whois",
            Request::Join(_, _) => "join",
            Request::Part(_, _) => "part",
            Request::Nick(_) => "nick",
            Request::Handshake(_, _, _) => "handshake",
            Request::Config(_, _) => "config",
            Request::GetProperty(_, _) => "property",
            Request::SetProperty(_, _, _) => "property",
            Request::UnsetProperty(_, _) => "property",
            Request::PropertyKeys(_, _) => "property",
            Request::SetPermission(_, _, _) => "permission",
            Request::HasPermission(_, _, _) => "permission",
            Request::UnsetPermission(_, _) => "permission",
        };

        Json::String(s.to_string())
    }

    fn get_action_type(&self) -> String {
        let s = match *self {
            Request::Networks | Request::Channels(_) | Request::Nick(_) | Request::Config(_, _) => "get",
            _ => "do",
        };

        s.to_string()
    }
}

/// Implements transforming the request to a Json object that is ready to be sent a DaZeus core.
impl ToJson for Request {
    fn to_json(&self) -> Json {
        let mut obj = Object::new();
        obj.insert(self.get_action_type(), self.get_json_name());

        let mut params = Array::new();

        macro_rules! push_str { ($x: expr) => ( params.push(Json::String($x.clone())) ) }

        match *self {
            Request::Subscribe(EventType::Command(ref cmd)) => {
                push_str!(cmd);
            },
            Request::Unsubscribe(EventType::Command(_)) => {
                // we can't actually unsubscribe a command
                panic!("Cannot unsubscribe from command");
            },
            Request::Subscribe(ref evt) => {
                push_str!(evt.to_string());
            },
            Request::Unsubscribe(ref evt) => {
                push_str!(evt.to_string());
            },
            Request::SubscribeCommand(ref cmd, Some(ref network)) => {
                push_str!(cmd);
                push_str!(network);
            },
            Request::SubscribeCommand(ref cmd, None) => {
                push_str!(cmd);
            },
            Request::Networks => (),
            Request::Channels(ref network)
            | Request::Nick(ref network) => {
                push_str!(network);
            },
            Request::Message(ref network, ref channel, ref message)
            | Request::Notice(ref network, ref channel, ref message)
            | Request::Ctcp(ref network, ref channel, ref message)
            | Request::CtcpReply(ref network, ref channel, ref message)
            | Request::Action(ref network, ref channel, ref message) => {
                push_str!(network);
                push_str!(channel);
                push_str!(message);
            },
            Request::Names(ref network, ref channel)
            | Request::Join(ref network, ref channel)
            | Request::Part(ref network, ref channel) => {
                push_str!(network);
                push_str!(channel);
            },
            Request::Whois(ref network, ref user) => {
                push_str!(network);
                push_str!(user);
            },
            Request::Handshake(ref name, ref version, Some(ref config_name)) => {
                push_str!(name);
                push_str!(version);
                push_str!(PROTOCOL_VERSION.to_string());
                push_str!(config_name);
            },
            Request::Handshake(ref name, ref version, None) => {
                push_str!(name);
                push_str!(version);
                push_str!(PROTOCOL_VERSION.to_string());
                push_str!(name);
            },
            Request::Config(ref key, ref ctype) => {
                push_str!(key);
                push_str!(ctype.to_string());
            },
            Request::GetProperty(ref property, ref scope) => {
                push_str!("get".to_string());
                push_str!(property);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::SetProperty(ref property, ref value, ref scope) => {
                push_str!("set".to_string());
                push_str!(property);
                push_str!(value);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::UnsetProperty(ref property, ref scope) => {
                push_str!("unset".to_string());
                push_str!(property);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::PropertyKeys(ref prefix, ref scope) => {
                push_str!("keys".to_string());
                push_str!(prefix);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::SetPermission(ref permission, ref default, ref scope) => {
                push_str!("set".to_string());
                push_str!(permission);
                params.push(Json::Boolean(*default));
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::HasPermission(ref permission, ref default, ref scope) => {
                push_str!("get".to_string());
                push_str!(permission);
                params.push(Json::Boolean(*default));
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::UnsetPermission(ref permission, ref scope) => {
                push_str!("unset".to_string());
                push_str!(permission);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
        }

        if params.len() > 0 {
            obj.insert("params".to_string(), Json::Array(params));
        }

        Json::Object(obj)
    }
}
<commit_msg>Fix retrieving config options<commit_after>use super::scope::Scope;
use serialize::json::{Json, ToJson, Object, Array};
use super::event::EventType;
use std::string::ToString;
use std::str::FromStr;
use super::error::ParseConfigGroupError;
use std::ascii::AsciiExt;

/// The version of the DaZeus plugin communication protocol that these bindings understand.
pub const PROTOCOL_VERSION: &'static str = "1";

/// A `String` for the target IRC network.
pub type Network = String;

/// A `String` containing the message to be sent.
pub type Message = String;

/// A `String` containing the target (receiver) of some command or message.
///
/// Typically this is a client or some channel on an IRC network.
pub type Target = String;

/// A `String` indicating the name of some command.
pub type Command = String;

/// A `String` indicating the name of the plugin. This is used for retrieving configuration.
pub type PluginName = String;

/// A `String` indicating the version of the protocol used by the bindings.
pub type PluginVersion = String;

/// The type of config that should be retrieved.
#[derive(Debug, Clone, PartialEq)]
pub enum ConfigGroup {
    /// Indicates a config value that should be retrieved from the plugin settings.
    Plugin,
    /// Indicates a config value that should be retrieved from the core settings.
    Core,
}

impl ToString for ConfigGroup {
    fn to_string(&self) -> String {
        match *self {
            ConfigGroup::Plugin => "plugin".to_string(),
            ConfigGroup::Core => "core".to_string(),
        }
    }
}

impl FromStr for ConfigGroup {
    type Err = ParseConfigGroupError;

    fn from_str(s: &str) -> Result<Self, ParseConfigGroupError> {
        match &s.to_ascii_lowercase()[..] {
            "plugin" => Ok(ConfigGroup::Plugin),
            "core" => Ok(ConfigGroup::Core),
            _ => Err(ParseConfigGroupError::new())
        }
    }
}

/// An enum of all requests that can be sent to your DaZeus instance.
///
/// Note that typically you won't create these request instances directly. Instead you can use the
/// different `DaZeus` methods. However if you wish, you can directly use `DaZeus::send()` to send
/// these requests yourself.
#[derive(Debug, Clone, PartialEq)]
pub enum Request {
    /// Subscribe to a certain event type.
    ///
    /// # Example
    /// ```
    /// Request::Subscribe(EventType::PrivMsg)
    /// ```
    Subscribe(EventType),
    /// Unsubscribe from an event.
    ///
    /// Note that you cannot specify `EventType::Command()` for this request, as registered
    /// commands cannot be unregistered from the DaZeus server. To stop listening, just let DaZeus
    /// remove the listener.
    ///
    /// # Example
    /// ```
    /// Request::Unsubscribe(EventType::Join)
    /// ```
    Unsubscribe(EventType),
    /// Subscribe to a command (optionally on a specific network).
    ///
    /// You can use `Request::Subscribe(EventType::Command("example".to_string())` as an
    /// alternative to `Request::SubscribeCommand("example".to_string())`. Note that the
    /// former does not allow you to optionally specify a network on which the command is actively
    /// listened to.
    ///
    /// # Example
    /// ```
    /// Request::SubscribeCommand("greet".to_string(), Some("freenode".to_string()))
    /// ```
    SubscribeCommand(Command, Option<Network>),
    /// Retrieve a list of networks that the DaZeus core is currently connected to.
    ///
    /// # Example
    /// ```
    /// Request::Networks
    /// ```
    Networks,
    /// Retrieve a list of channels on the specified network that the bot has joined.
    ///
    /// # Example
    /// ```
    /// Request::Channels("freenode".to_string())
    /// ```
    Channels(Network),
    /// Request to send a message to a specific target on some network.
    ///
    /// This will request DaZeus to send a PRIVMSG.
    ///
    /// # Example
    /// ```
    /// Request::Message("freenode".to_string(), "#botters-test".to_string(), "Hello!".to_string())
    /// ```
    Message(Network, Target, Message),
    /// Request to send a notice to some target on some network.
    ///
    /// This will request DaZeus to send a NOTICE.
    ///
    /// # Example
    /// ```
    /// Request::Notice("example".to_string(), "MrExample".to_string(), "Message!".to_string())
    /// ```
    Notice(Network, Target, Message),
    /// Request to send a CTCP message to some client on some network.
    ///
    /// # Example
    /// ```
    /// Request::Ctcp("example".to_string(), "MrExample".to_string(), "VERSION".to_string())
    /// ```
    Ctcp(Network, Target, Message),
    /// Request to send a CTCP message reply to some client on some network.
    ///
    /// # Example
    /// ```
    /// Request::CtcpReply("example".to_string(), "MrExample".to_string(), "VERSION DaZeus 2.0".to_string())
    /// ```
    CtcpReply(Network, Target, Message),
    /// Request to send a CTCP ACTION message to some target on some network.
    ///
    /// A CTCP ACTION is most known by users as the `/me` command.
    ///
    /// # Example
    /// ```
    /// Request::Action("example".to_string(), "#example", "is creating an example".to_string())
    /// ```
    Action(Network, Target, Message),
    /// Request to send the list of names in some channel.
    ///
    /// Note that such a request will generate an `EventType::Names` event if the server allows it,
    /// instead of responding to this request directly.
    ///
    /// # Example
    /// ```
    /// Request::Names("freenode".to_string(), "#freenode".to_string())
    /// ```
    Names(Network, Target),
    /// Request to send a whois on some target.
    ///
    /// Note that such a request will generate an `EventType::Whois` event if the server allows it,
    /// instead of responding to this request directly.
    ///
    /// # Example
    /// ```
    /// Request::Whois("example".to_string(), "MrExample".to_string())
    /// ```
    Whois(Network, Target),
    /// Request to join a channel on some network.
    ///
    /// # Example
    /// ```
    /// Request::Join("freenode".to_string(), "#freenode".to_string())
    /// ```
    Join(Network, Target),
    /// Request to leave a channel on some network.
    ///
    /// # Example
    /// ```
    /// Request::Part("freenode".to_string(), "#freenode".to_string())
    /// ```
    Part(Network, Target),
    /// Request the nickname of the bot on a network.
    ///
    /// # Example
    /// ```
    /// Request::Nick("freenode".to_string())
    /// ```
    Nick(Network),
    /// Request for a handshake to the DaZeus Core.
    ///
    /// The handshake allows the plugin to retrieve configuration options.
    ///
    /// The second parameter of this request variant is the plugin version. Note that this
    /// parameter should be equal to the version that these bindings understand.
    ///
    /// The optional third parameter may provide an alternative name to be used to retrieve
    /// options from the DaZeus core config. By default the name of the plugin will be used.
    ///
    /// # Example
    /// ```
    /// Request::Handshake("my_plugin".to_string(), PROTOCOL_VERSION.to_string(), None)
    /// ```
    Handshake(PluginName, PluginVersion, Option<String>),
    /// Retrieve some option from the DaZeus core config.
    ///
    /// The second parameter is the section from which the configuration parameter should be
    /// retrieved. This may either be `ConfigGroup::Core` or `ConfigGroup::Plugin`
    ///
    /// Note that in order to successfully retrieve these configuration values the plugin first
    /// needs to have completed a succesful handshake with the core.
    ///
    /// # Example
    /// ```
    /// Request::Config("highlight".to_string(), ConfigGroup::Core)
    /// ```
    Config(String, ConfigGroup),
    /// Retrieve a property from the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::GetProperty("example".to_string(), Scope::any())
    /// ```
    GetProperty(String, Scope),
    /// Set a property in the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::SetProperty("example".to_string(), "value".to_string(), Scope::any())
    /// ```
    SetProperty(String, String, Scope),
    /// Remove a property from the internal DaZeus core database.
    ///
    /// # Example
    /// ```
    /// Request::UnsetProperty("example".to_string(), Scope::any())
    /// ```
    UnsetProperty(String, Scope),
    /// Retrieve a set of keys that is available for some prefix and scope.
    ///
    /// The first parameter of the variant is the prefix string for retrieving property keys.
    ///
    /// # Example
    /// ```
    /// Request::PropertyKeys("path.to".to_string(), Scope::network("example"))
    /// ```
    PropertyKeys(String, Scope),
    /// Set a permission in the permission database of the DaZeus core.
    ///
    /// # Example
    /// ```
    /// Request::SetPermission("edit".to_string(), true, Scope::sender("example", "MrExample"))
    /// ```
    SetPermission(String, bool, Scope),
    /// Request a permission to be retrieved from the permission database of the DaZeus core.
    ///
    /// The second parameter of this variant indicates the default value that the core should
    /// return if the permission was not found inside the permission database.
    ///
    /// # Example
    /// ```
    /// Request::HasPermission("edit".to_string(), false, Scope::sender("example", "MrExample"))
    /// ```
    HasPermission(String, bool, Scope),
    /// Remove a permission from the permission database of the DaZeus core.
    ///
    /// # Example
    /// ```
    /// Request::UnsetPermission("edit".to_string(), Scope::sender("example", "MrExample"))
    /// ```
    UnsetPermission(String, Scope),
}

impl Request {
    fn get_json_name(&self) -> Json {
        let s = match *self {
            Request::Subscribe(EventType::Command(_)) => "command",
            Request::Subscribe(_) => "subscribe",
            Request::Unsubscribe(_) => "unsubscribe",
            Request::SubscribeCommand(_, _) => "command",
            Request::Networks => "networks",
            Request::Channels(_) => "channels",
            Request::Message(_, _, _) => "message",
            Request::Notice(_, _, _) => "notice",
            Request::Ctcp(_, _, _) => "ctcp",
            Request::CtcpReply(_, _, _) => "ctcp_rep",
            Request::Action(_, _, _) => "action",
            Request::Names(_, _) => "names",
            Request::Whois(_, _) => "whois",
            Request::Join(_, _) => "join",
            Request::Part(_, _) => "part",
            Request::Nick(_) => "nick",
            Request::Handshake(_, _, _) => "handshake",
            Request::Config(_, _) => "config",
            Request::GetProperty(_, _) => "property",
            Request::SetProperty(_, _, _) => "property",
            Request::UnsetProperty(_, _) => "property",
            Request::PropertyKeys(_, _) => "property",
            Request::SetPermission(_, _, _) => "permission",
            Request::HasPermission(_, _, _) => "permission",
            Request::UnsetPermission(_, _) => "permission",
        };

        Json::String(s.to_string())
    }

    fn get_action_type(&self) -> String {
        let s = match *self {
            Request::Networks | Request::Channels(_) | Request::Nick(_) | Request::Config(_, _) => "get",
            _ => "do",
        };

        s.to_string()
    }
}

/// Implements transforming the request to a Json object that is ready to be sent a DaZeus core.
impl ToJson for Request {
    fn to_json(&self) -> Json {
        let mut obj = Object::new();
        obj.insert(self.get_action_type(), self.get_json_name());

        let mut params = Array::new();

        macro_rules! push_str { ($x: expr) => ( params.push(Json::String($x.clone())) ) }

        match *self {
            Request::Subscribe(EventType::Command(ref cmd)) => {
                push_str!(cmd);
            },
            Request::Unsubscribe(EventType::Command(_)) => {
                // we can't actually unsubscribe a command
                panic!("Cannot unsubscribe from command");
            },
            Request::Subscribe(ref evt) => {
                push_str!(evt.to_string());
            },
            Request::Unsubscribe(ref evt) => {
                push_str!(evt.to_string());
            },
            Request::SubscribeCommand(ref cmd, Some(ref network)) => {
                push_str!(cmd);
                push_str!(network);
            },
            Request::SubscribeCommand(ref cmd, None) => {
                push_str!(cmd);
            },
            Request::Networks => (),
            Request::Channels(ref network)
            | Request::Nick(ref network) => {
                push_str!(network);
            },
            Request::Message(ref network, ref channel, ref message)
            | Request::Notice(ref network, ref channel, ref message)
            | Request::Ctcp(ref network, ref channel, ref message)
            | Request::CtcpReply(ref network, ref channel, ref message)
            | Request::Action(ref network, ref channel, ref message) => {
                push_str!(network);
                push_str!(channel);
                push_str!(message);
            },
            Request::Names(ref network, ref channel)
            | Request::Join(ref network, ref channel)
            | Request::Part(ref network, ref channel) => {
                push_str!(network);
                push_str!(channel);
            },
            Request::Whois(ref network, ref user) => {
                push_str!(network);
                push_str!(user);
            },
            Request::Handshake(ref name, ref version, Some(ref config_name)) => {
                push_str!(name);
                push_str!(version);
                push_str!(PROTOCOL_VERSION.to_string());
                push_str!(config_name);
            },
            Request::Handshake(ref name, ref version, None) => {
                push_str!(name);
                push_str!(version);
                push_str!(PROTOCOL_VERSION.to_string());
                push_str!(name);
            },
            Request::Config(ref key, ref ctype) => {
                push_str!(ctype.to_string());
                push_str!(key);
            },
            Request::GetProperty(ref property, ref scope) => {
                push_str!("get".to_string());
                push_str!(property);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::SetProperty(ref property, ref value, ref scope) => {
                push_str!("set".to_string());
                push_str!(property);
                push_str!(value);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::UnsetProperty(ref property, ref scope) => {
                push_str!("unset".to_string());
                push_str!(property);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::PropertyKeys(ref prefix, ref scope) => {
                push_str!("keys".to_string());
                push_str!(prefix);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::SetPermission(ref permission, ref default, ref scope) => {
                push_str!("set".to_string());
                push_str!(permission);
                params.push(Json::Boolean(*default));
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::HasPermission(ref permission, ref default, ref scope) => {
                push_str!("get".to_string());
                push_str!(permission);
                params.push(Json::Boolean(*default));
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
            Request::UnsetPermission(ref permission, ref scope) => {
                push_str!("unset".to_string());
                push_str!(permission);
                if !scope.is_any() {
                    obj.insert("scope".to_string(), scope.to_json());
                }
            },
        }

        if params.len() > 0 {
            obj.insert("params".to_string(), Json::Array(params));
        }

        Json::Object(obj)
    }
}
<|endoftext|>


<commit_before>use linalg::vector::Vector;

pub trait Kernel {
    fn kernel(&self, x1: &[f64], x2: &[f64]) -> f64;
}

pub struct SquaredExp {
    ls: f64,
    ampl: f64,
}

impl SquaredExp {
    pub fn new(ls: f64, ampl: f64) -> SquaredExp {
        SquaredExp {
            ls: ls,
            ampl: ampl,
        }
    }
}

impl Default for SquaredExp {
    fn default() -> SquaredExp {
        SquaredExp {
            ls: 1f64,
            ampl: 1f64,
        }
    }
}

impl Kernel for SquaredExp {
    fn kernel(&self, x1: &[f64], x2: &[f64]) -> f64 {
        assert_eq!(x1.len(), x2.len());

        let v1 = Vector::new(x1.to_vec());
        let v2 = Vector::new(x2.to_vec());

        let x = -(&v1 - &v2).dot(&(v1 - v2)) / (2f64 * self.ls * self.ls);
        (self.ampl * x.exp())
    }
}
<commit_msg>Docs and exampls<commit_after>use linalg::vector::Vector;

/// The Kernel trait
///
/// Requires a function mapping two vectors to a scalar.
pub trait Kernel {
    fn kernel(&self, x1: &[f64], x2: &[f64]) -> f64;
}

/// Squared exponential kernel
///
/// Equivalently a gaussian function.
///
/// The kernel function is given by:
///
/// f(x1,x2) = A _exp_(-||x1-x2||^2 / 2(l^2))
///
/// Where A is the amplitude and l the length scale.
pub struct SquaredExp {
    ls: f64,
    ampl: f64,
}

impl SquaredExp {

    /// Construct a new squared exponential kernel.
    ///
    /// # Examples
    ///
    /// ```
    /// use rusty_machine::learning::toolkit::SquaredExp;
    ///
    /// let ker = SquaredExp::new(2f64, 1f64);
    /// ```
    pub fn new(ls: f64, ampl: f64) -> SquaredExp {
        SquaredExp {
            ls: ls,
            ampl: ampl,
        }
    }
}

impl Default for SquaredExp {

    /// Constructs the default Squared Exp kernel.
    ///
    /// The default settings are:
    /// - length scale = 1
    /// - amplitude = 1
    ///
    /// # Examples
    ///
    /// ```
    /// use rusty_machine::learning::toolkit::SquaredExp;
    ///
    /// let ker = SquaredExp::default();
    /// ```
    fn default() -> SquaredExp {
        SquaredExp {
            ls: 1f64,
            ampl: 1f64,
        }
    }
}

impl Kernel for SquaredExp {
    /// The squared exponential kernel function.
    fn kernel(&self, x1: &[f64], x2: &[f64]) -> f64 {
        assert_eq!(x1.len(), x2.len());

        let v1 = Vector::new(x1.to_vec());
        let v2 = Vector::new(x2.to_vec());

        let x = -(&v1 - &v2).dot(&(v1 - v2)) / (2f64 * self.ls * self.ls);
        (self.ampl * x.exp())
    }
}
<|endoftext|>


<commit_before>extern crate gag;
extern crate libc;
extern crate num;
extern crate time;
extern crate termbox_sys as termbox;
#[macro_use] extern crate bitflags;

pub use self::style::{Style, RB_BOLD, RB_UNDERLINE, RB_REVERSE, RB_NORMAL};

use std::error::Error;
use std::fmt;
use std::io;
use std::char;
use std::default::Default;

use num::FromPrimitive;
use termbox::RawEvent;
use libc::c_int;
use gag::Hold;
use time::Duration;

pub mod keyboard;
pub mod mouse;

pub use self::running::running;
pub use keyboard::Key;
pub use mouse::Mouse;

#[derive(Clone, Copy)]
pub enum Event {
    KeyEventRaw(u8, u16, u32),
    KeyEvent(Option<Key>),
    ResizeEvent(i32, i32),
    MouseEvent(Mouse, i32, i32),
    NoEvent
}

#[derive(Clone, Copy, Debug)]
pub enum InputMode {
    Current = 0x00,

    /// When ESC sequence is in the buffer and it doesn't match any known
    /// ESC sequence => ESC means TB_KEY_ESC
    Esc     = 0x01,
    /// When ESC sequence is in the buffer and it doesn't match any known
    /// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
    Alt     = 0x02,
    /// Same as `Esc` but enables mouse events
    EscMouse = 0x05,
    /// Same as `Alt` but enables mouse events
    AltMouse = 0x06
}

#[derive(Clone, Copy, PartialEq)]
#[repr(C,u16)]
pub enum Color {
    Default =  0x00,
    Black =    0x01,
    Red =      0x02,
    Green =    0x03,
    Yellow =   0x04,
    Blue =     0x05,
    Magenta =  0x06,
    Cyan =     0x07,
    White =    0x08,
}

mod style {
    bitflags! {
        #[repr(C)]
        flags Style: u16 {
            const TB_NORMAL_COLOR = 0x000F,
            const RB_BOLD = 0x0100,
            const RB_UNDERLINE = 0x0200,
            const RB_REVERSE = 0x0400,
            const RB_NORMAL = 0x0000,
            const TB_ATTRIB = RB_BOLD.bits | RB_UNDERLINE.bits | RB_REVERSE.bits,
        }
    }

    impl Style {
        pub fn from_color(color: super::Color) -> Style {
            Style { bits: color as u16 & TB_NORMAL_COLOR.bits }
        }
    }
}

const NIL_RAW_EVENT: RawEvent = RawEvent { etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0, x: 0, y: 0 };

#[derive(Debug)]
pub enum EventError {
   TermboxError,
   Unknown(isize),
}

impl fmt::Display for EventError {
   fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
      write!(fmt, "{}", self.description())
   }
}

impl Error for EventError {
   fn description(&self) -> &str {
      match *self {
         EventError::TermboxError => "Error in Termbox",
         // I don't know how to format this without lifetime error.
         // EventError::Unknown(n) => &format!("There was an unknown error. Error code: {}", n),
         EventError::Unknown(_) => "Unknown error in Termbox",
      }
   }
}

impl FromPrimitive for EventError {
   fn from_i64(n: i64) -> Option<EventError> {
      match n {
         -1 => Some(EventError::TermboxError),
         n => Some(EventError::Unknown(n as isize)),
      }
   }

   fn from_u64(n: u64) -> Option<EventError> {
      Some(EventError::Unknown(n as isize))
   }
}

pub type EventResult = Result<Event, EventError>;

/// Unpack a RawEvent to an Event
///
/// if the `raw` parameter is true, then the Event variant will be the raw
/// representation of the event.
///     for instance KeyEventRaw instead of KeyEvent
///
/// This is useful if you want to interpret the raw event data yourself, rather
/// than having rustbox translate it to its own representation.
fn unpack_event(ev_type: c_int, ev: &RawEvent, raw: bool) -> EventResult {
    match ev_type {
        0 => Ok(Event::NoEvent),
        1 => Ok(
            if raw {
                Event::KeyEventRaw(ev.emod, ev.key, ev.ch)
            } else {
                let k = match ev.key {
                    0 => char::from_u32(ev.ch).map(|c| Key::Char(c)),
                    a => Key::from_code(a),
                };
                Event::KeyEvent(k)
            }),
        2 => Ok(Event::ResizeEvent(ev.w, ev.h)),
        3 => {
            let mouse = Mouse::from_code(ev.key).unwrap_or(Mouse::Left);
            Ok(Event::MouseEvent(mouse, ev.x, ev.y))
        },
        // `unwrap` is safe here because FromPrimitive for EventError only returns `Some`.
        n => Err(FromPrimitive::from_isize(n as isize).unwrap()),
    }
}

#[derive(Debug)]
pub enum InitError {
    BufferStderrFailed(io::Error),
    AlreadyOpen,
    UnsupportedTerminal,
    FailedToOpenTTy,
    PipeTrapError,
    Unknown(isize),
}

impl fmt::Display for InitError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", self.description())
    }
}

impl Error for InitError {
    fn description(&self) -> &str {
        match *self {
            InitError::BufferStderrFailed(_) => "Could not redirect stderr",
            InitError::AlreadyOpen => "RustBox is already open",
            InitError::UnsupportedTerminal => "Unsupported terminal",
            InitError::FailedToOpenTTy => "Failed to open TTY",
            InitError::PipeTrapError => "Pipe trap error",
            InitError::Unknown(_) => "Unknown error from Termbox",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            InitError::BufferStderrFailed(ref e) => Some(e),
            _ => None
        }
    }
}

impl FromPrimitive for InitError {
   fn from_i64(n: i64) -> Option<InitError> {
      match n {
         -1 => Some(InitError::UnsupportedTerminal),
         -2 => Some(InitError::FailedToOpenTTy),
         -3 => Some(InitError::PipeTrapError),
         n => Some(InitError::Unknown(n as isize)),
      }
   }

   fn from_u64(n: u64) -> Option<InitError> {
      Some(InitError::Unknown(n as isize))
   }
}

#[allow(missing_copy_implementations)]
pub struct RustBox {
    // We only bother to redirect stderr for the moment, since it's used for panic!
    _stderr: Option<Hold>,
    // RAII lock.
    //
    // Note that running *MUST* be the last field in the destructor, since destructors run in
    // top-down order. Otherwise it will not properly protect the above fields.
    _running: running::RunningGuard,
}

// Termbox is not thread safe
impl !Send for RustBox {}

#[derive(Clone, Copy,Debug)]
pub struct InitOptions {
    /// Use this option to initialize with a specific input mode
    ///
    /// See InputMode enum for details on the variants.
    pub input_mode: InputMode,

    /// Use this option to automatically buffer stderr while RustBox is running.  It will be
    /// written when RustBox exits.
    ///
    /// This option uses a nonblocking OS pipe to buffer stderr output.  This means that if the
    /// pipe fills up, subsequent writes will fail until RustBox exits.  If this is a concern for
    /// your program, don't use RustBox's default pipe-based redirection; instead, redirect stderr
    /// to a log file or another process that is capable of handling it better.
    pub buffer_stderr: bool,
}

impl Default for InitOptions {
    fn default() -> Self {
        InitOptions {
            input_mode: InputMode::Current,
            buffer_stderr: false,
        }
    }
}

mod running {
    use std::sync::atomic::{self, AtomicBool};

    // The state of the RustBox is protected by the lock. Yay, global state!
    static RUSTBOX_RUNNING: AtomicBool = atomic::ATOMIC_BOOL_INIT;

    /// true iff RustBox is currently running. Beware of races here--don't rely on this for anything
    /// critical unless you happen to know that RustBox cannot change state when it is called (a good
    /// usecase would be checking to see if it's worth risking double printing backtraces to avoid
    /// having them swallowed up by RustBox).
    pub fn running() -> bool {
        RUSTBOX_RUNNING.load(atomic::Ordering::SeqCst)
    }

    // Internal RAII guard used to ensure we release the running lock whenever we acquire it.
    #[allow(missing_copy_implementations)]
    pub struct RunningGuard(());

    pub fn run() -> Option<RunningGuard> {
        // Ensure that we are not already running and simultaneously set RUSTBOX_RUNNING using an
        // atomic swap. This ensures that contending threads don't trample each other.
        if RUSTBOX_RUNNING.swap(true, atomic::Ordering::SeqCst) {
            // The Rustbox was already running.
            None
        } else {
            // The RustBox was not already running, and now we have the lock.
            Some(RunningGuard(()))
        }
    }

    impl Drop for RunningGuard {
        fn drop(&mut self) {
            // Indicate that we're free now. We could probably get away with lower atomicity here,
            // but there's no reason to take that chance.
            RUSTBOX_RUNNING.store(false, atomic::Ordering::SeqCst);
        }
    }
}

impl RustBox {
    /// Initialize rustbox.
    ///
    /// For the default options, you can use:
    ///
    /// ```
    /// use rustbox::RustBox;
    /// use std::default::Default;
    /// let rb = RustBox::init(Default::default());
    /// ```
    ///
    /// Otherwise, you can specify:
    ///
    /// ```
    /// use rustbox::{RustBox, InitOptions};
    /// use std::default::Default;
    /// let rb = RustBox::init(InitOptions { input_mode: rustbox::InputMode::Esc, ..Default::default() });
    /// ```
    pub fn init(opts: InitOptions) -> Result<RustBox, InitError> {
        let running = match running::run() {
            Some(r) => r,
            None => return Err(InitError::AlreadyOpen),
        };

        let stderr = if opts.buffer_stderr {
            Some(try!(Hold::stderr().map_err(|e| InitError::BufferStderrFailed(e))))
        } else {
            None
        };

        // Create the RustBox.
        let rb = unsafe { match termbox::tb_init() {
            0 => RustBox {
                _stderr: stderr,
                _running: running,
            },
            res => {
                return Err(FromPrimitive::from_isize(res as isize).unwrap())
            }
        }};
        match opts.input_mode {
            InputMode::Current => (),
            _ => rb.set_input_mode(opts.input_mode),
        }
        Ok(rb)
    }

    pub fn width(&self) -> usize {
        unsafe { termbox::tb_width() as usize }
    }

    pub fn height(&self) -> usize {
        unsafe { termbox::tb_height() as usize }
    }

    pub fn clear(&self) {
        unsafe { termbox::tb_clear() }
    }

    pub fn present(&self) {
        unsafe { termbox::tb_present() }
    }

    pub fn set_cursor(&self, x: isize, y: isize) {
        unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
    }

    pub unsafe fn change_cell(&self, x: usize, y: usize, ch: u32, fg: u16, bg: u16) {
        termbox::tb_change_cell(x as c_int, y as c_int, ch, fg, bg)
    }

    pub fn print(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, s: &str) {
        let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
        let bg = Style::from_color(bg);
        for (i, ch) in s.chars().enumerate() {
            unsafe {
                self.change_cell(x+i, y, ch as u32, fg.bits(), bg.bits());
            }
        }
    }

    pub fn print_char(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, ch: char) {
        let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
        let bg = Style::from_color(bg);
        unsafe {
            self.change_cell(x, y, ch as u32, fg.bits(), bg.bits());
        }
    }

    pub fn poll_event(&self, raw: bool) -> EventResult {
        let ev = NIL_RAW_EVENT;
        let rc = unsafe {
            termbox::tb_poll_event(&ev as *const RawEvent)
        };
        unpack_event(rc, &ev, raw)
    }

    pub fn peek_event(&self, timeout: Duration, raw: bool) -> EventResult {
        let ev = NIL_RAW_EVENT;
        let rc = unsafe {
            termbox::tb_peek_event(&ev as *const RawEvent, timeout.num_milliseconds() as c_int)
        };
        unpack_event(rc, &ev, raw)
    }

    pub fn set_input_mode(&self, mode: InputMode) {
        unsafe {
            termbox::tb_select_input_mode(mode as c_int);
        }
    }
}

impl Drop for RustBox {
    fn drop(&mut self) {
        // Since only one instance of the RustBox is ever accessible, we should not
        // need to do this atomically.
        // Note: we should definitely have RUSTBOX_RUNNING = true here.
        unsafe {
            termbox::tb_shutdown();
        }
    }
}
<commit_msg>Change !Send to PhandomData<*mut ()><commit_after>extern crate gag;
extern crate libc;
extern crate num;
extern crate time;
extern crate termbox_sys as termbox;
#[macro_use] extern crate bitflags;

pub use self::style::{Style, RB_BOLD, RB_UNDERLINE, RB_REVERSE, RB_NORMAL};

use std::error::Error;
use std::fmt;
use std::io;
use std::char;
use std::default::Default;
use std::marker::PhantomData;

use num::FromPrimitive;
use termbox::RawEvent;
use libc::c_int;
use gag::Hold;
use time::Duration;

pub mod keyboard;
pub mod mouse;

pub use self::running::running;
pub use keyboard::Key;
pub use mouse::Mouse;

#[derive(Clone, Copy)]
pub enum Event {
    KeyEventRaw(u8, u16, u32),
    KeyEvent(Option<Key>),
    ResizeEvent(i32, i32),
    MouseEvent(Mouse, i32, i32),
    NoEvent
}

#[derive(Clone, Copy, Debug)]
pub enum InputMode {
    Current = 0x00,

    /// When ESC sequence is in the buffer and it doesn't match any known
    /// ESC sequence => ESC means TB_KEY_ESC
    Esc     = 0x01,
    /// When ESC sequence is in the buffer and it doesn't match any known
    /// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
    Alt     = 0x02,
    /// Same as `Esc` but enables mouse events
    EscMouse = 0x05,
    /// Same as `Alt` but enables mouse events
    AltMouse = 0x06
}

#[derive(Clone, Copy, PartialEq)]
#[repr(C,u16)]
pub enum Color {
    Default =  0x00,
    Black =    0x01,
    Red =      0x02,
    Green =    0x03,
    Yellow =   0x04,
    Blue =     0x05,
    Magenta =  0x06,
    Cyan =     0x07,
    White =    0x08,
}

mod style {
    bitflags! {
        #[repr(C)]
        flags Style: u16 {
            const TB_NORMAL_COLOR = 0x000F,
            const RB_BOLD = 0x0100,
            const RB_UNDERLINE = 0x0200,
            const RB_REVERSE = 0x0400,
            const RB_NORMAL = 0x0000,
            const TB_ATTRIB = RB_BOLD.bits | RB_UNDERLINE.bits | RB_REVERSE.bits,
        }
    }

    impl Style {
        pub fn from_color(color: super::Color) -> Style {
            Style { bits: color as u16 & TB_NORMAL_COLOR.bits }
        }
    }
}

const NIL_RAW_EVENT: RawEvent = RawEvent { etype: 0, emod: 0, key: 0, ch: 0, w: 0, h: 0, x: 0, y: 0 };

#[derive(Debug)]
pub enum EventError {
   TermboxError,
   Unknown(isize),
}

impl fmt::Display for EventError {
   fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
      write!(fmt, "{}", self.description())
   }
}

impl Error for EventError {
   fn description(&self) -> &str {
      match *self {
         EventError::TermboxError => "Error in Termbox",
         // I don't know how to format this without lifetime error.
         // EventError::Unknown(n) => &format!("There was an unknown error. Error code: {}", n),
         EventError::Unknown(_) => "Unknown error in Termbox",
      }
   }
}

impl FromPrimitive for EventError {
   fn from_i64(n: i64) -> Option<EventError> {
      match n {
         -1 => Some(EventError::TermboxError),
         n => Some(EventError::Unknown(n as isize)),
      }
   }

   fn from_u64(n: u64) -> Option<EventError> {
      Some(EventError::Unknown(n as isize))
   }
}

pub type EventResult = Result<Event, EventError>;

/// Unpack a RawEvent to an Event
///
/// if the `raw` parameter is true, then the Event variant will be the raw
/// representation of the event.
///     for instance KeyEventRaw instead of KeyEvent
///
/// This is useful if you want to interpret the raw event data yourself, rather
/// than having rustbox translate it to its own representation.
fn unpack_event(ev_type: c_int, ev: &RawEvent, raw: bool) -> EventResult {
    match ev_type {
        0 => Ok(Event::NoEvent),
        1 => Ok(
            if raw {
                Event::KeyEventRaw(ev.emod, ev.key, ev.ch)
            } else {
                let k = match ev.key {
                    0 => char::from_u32(ev.ch).map(|c| Key::Char(c)),
                    a => Key::from_code(a),
                };
                Event::KeyEvent(k)
            }),
        2 => Ok(Event::ResizeEvent(ev.w, ev.h)),
        3 => {
            let mouse = Mouse::from_code(ev.key).unwrap_or(Mouse::Left);
            Ok(Event::MouseEvent(mouse, ev.x, ev.y))
        },
        // `unwrap` is safe here because FromPrimitive for EventError only returns `Some`.
        n => Err(FromPrimitive::from_isize(n as isize).unwrap()),
    }
}

#[derive(Debug)]
pub enum InitError {
    BufferStderrFailed(io::Error),
    AlreadyOpen,
    UnsupportedTerminal,
    FailedToOpenTTy,
    PipeTrapError,
    Unknown(isize),
}

impl fmt::Display for InitError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", self.description())
    }
}

impl Error for InitError {
    fn description(&self) -> &str {
        match *self {
            InitError::BufferStderrFailed(_) => "Could not redirect stderr",
            InitError::AlreadyOpen => "RustBox is already open",
            InitError::UnsupportedTerminal => "Unsupported terminal",
            InitError::FailedToOpenTTy => "Failed to open TTY",
            InitError::PipeTrapError => "Pipe trap error",
            InitError::Unknown(_) => "Unknown error from Termbox",
        }
    }

    fn cause(&self) -> Option<&Error> {
        match *self {
            InitError::BufferStderrFailed(ref e) => Some(e),
            _ => None
        }
    }
}

impl FromPrimitive for InitError {
   fn from_i64(n: i64) -> Option<InitError> {
      match n {
         -1 => Some(InitError::UnsupportedTerminal),
         -2 => Some(InitError::FailedToOpenTTy),
         -3 => Some(InitError::PipeTrapError),
         n => Some(InitError::Unknown(n as isize)),
      }
   }

   fn from_u64(n: u64) -> Option<InitError> {
      Some(InitError::Unknown(n as isize))
   }
}

#[allow(missing_copy_implementations)]
pub struct RustBox {
    // We only bother to redirect stderr for the moment, since it's used for panic!
    _stderr: Option<Hold>,
    // RAII lock.
    //
    // Note that running *MUST* be the last field in the destructor, since destructors run in
    // top-down order. Otherwise it will not properly protect the above fields.
    _running: running::RunningGuard,
    // Termbox is not thread safe. See #39.
    _phantom: PhantomData<*mut ()>,
}

#[derive(Clone, Copy,Debug)]
pub struct InitOptions {
    /// Use this option to initialize with a specific input mode
    ///
    /// See InputMode enum for details on the variants.
    pub input_mode: InputMode,

    /// Use this option to automatically buffer stderr while RustBox is running.  It will be
    /// written when RustBox exits.
    ///
    /// This option uses a nonblocking OS pipe to buffer stderr output.  This means that if the
    /// pipe fills up, subsequent writes will fail until RustBox exits.  If this is a concern for
    /// your program, don't use RustBox's default pipe-based redirection; instead, redirect stderr
    /// to a log file or another process that is capable of handling it better.
    pub buffer_stderr: bool,
}

impl Default for InitOptions {
    fn default() -> Self {
        InitOptions {
            input_mode: InputMode::Current,
            buffer_stderr: false,
        }
    }
}

mod running {
    use std::sync::atomic::{self, AtomicBool};

    // The state of the RustBox is protected by the lock. Yay, global state!
    static RUSTBOX_RUNNING: AtomicBool = atomic::ATOMIC_BOOL_INIT;

    /// true iff RustBox is currently running. Beware of races here--don't rely on this for anything
    /// critical unless you happen to know that RustBox cannot change state when it is called (a good
    /// usecase would be checking to see if it's worth risking double printing backtraces to avoid
    /// having them swallowed up by RustBox).
    pub fn running() -> bool {
        RUSTBOX_RUNNING.load(atomic::Ordering::SeqCst)
    }

    // Internal RAII guard used to ensure we release the running lock whenever we acquire it.
    #[allow(missing_copy_implementations)]
    pub struct RunningGuard(());

    pub fn run() -> Option<RunningGuard> {
        // Ensure that we are not already running and simultaneously set RUSTBOX_RUNNING using an
        // atomic swap. This ensures that contending threads don't trample each other.
        if RUSTBOX_RUNNING.swap(true, atomic::Ordering::SeqCst) {
            // The Rustbox was already running.
            None
        } else {
            // The RustBox was not already running, and now we have the lock.
            Some(RunningGuard(()))
        }
    }

    impl Drop for RunningGuard {
        fn drop(&mut self) {
            // Indicate that we're free now. We could probably get away with lower atomicity here,
            // but there's no reason to take that chance.
            RUSTBOX_RUNNING.store(false, atomic::Ordering::SeqCst);
        }
    }
}

impl RustBox {
    /// Initialize rustbox.
    ///
    /// For the default options, you can use:
    ///
    /// ```
    /// use rustbox::RustBox;
    /// use std::default::Default;
    /// let rb = RustBox::init(Default::default());
    /// ```
    ///
    /// Otherwise, you can specify:
    ///
    /// ```
    /// use rustbox::{RustBox, InitOptions};
    /// use std::default::Default;
    /// let rb = RustBox::init(InitOptions { input_mode: rustbox::InputMode::Esc, ..Default::default() });
    /// ```
    pub fn init(opts: InitOptions) -> Result<RustBox, InitError> {
        let running = match running::run() {
            Some(r) => r,
            None => return Err(InitError::AlreadyOpen),
        };

        let stderr = if opts.buffer_stderr {
            Some(try!(Hold::stderr().map_err(|e| InitError::BufferStderrFailed(e))))
        } else {
            None
        };

        // Create the RustBox.
        let rb = unsafe { match termbox::tb_init() {
            0 => RustBox {
                _stderr: stderr,
                _running: running,
                _phantom: PhantomData,
            },
            res => {
                return Err(FromPrimitive::from_isize(res as isize).unwrap())
            }
        }};
        match opts.input_mode {
            InputMode::Current => (),
            _ => rb.set_input_mode(opts.input_mode),
        }
        Ok(rb)
    }

    pub fn width(&self) -> usize {
        unsafe { termbox::tb_width() as usize }
    }

    pub fn height(&self) -> usize {
        unsafe { termbox::tb_height() as usize }
    }

    pub fn clear(&self) {
        unsafe { termbox::tb_clear() }
    }

    pub fn present(&self) {
        unsafe { termbox::tb_present() }
    }

    pub fn set_cursor(&self, x: isize, y: isize) {
        unsafe { termbox::tb_set_cursor(x as c_int, y as c_int) }
    }

    pub unsafe fn change_cell(&self, x: usize, y: usize, ch: u32, fg: u16, bg: u16) {
        termbox::tb_change_cell(x as c_int, y as c_int, ch, fg, bg)
    }

    pub fn print(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, s: &str) {
        let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
        let bg = Style::from_color(bg);
        for (i, ch) in s.chars().enumerate() {
            unsafe {
                self.change_cell(x+i, y, ch as u32, fg.bits(), bg.bits());
            }
        }
    }

    pub fn print_char(&self, x: usize, y: usize, sty: Style, fg: Color, bg: Color, ch: char) {
        let fg = Style::from_color(fg) | (sty & style::TB_ATTRIB);
        let bg = Style::from_color(bg);
        unsafe {
            self.change_cell(x, y, ch as u32, fg.bits(), bg.bits());
        }
    }

    pub fn poll_event(&self, raw: bool) -> EventResult {
        let ev = NIL_RAW_EVENT;
        let rc = unsafe {
            termbox::tb_poll_event(&ev as *const RawEvent)
        };
        unpack_event(rc, &ev, raw)
    }

    pub fn peek_event(&self, timeout: Duration, raw: bool) -> EventResult {
        let ev = NIL_RAW_EVENT;
        let rc = unsafe {
            termbox::tb_peek_event(&ev as *const RawEvent, timeout.num_milliseconds() as c_int)
        };
        unpack_event(rc, &ev, raw)
    }

    pub fn set_input_mode(&self, mode: InputMode) {
        unsafe {
            termbox::tb_select_input_mode(mode as c_int);
        }
    }
}

impl Drop for RustBox {
    fn drop(&mut self) {
        // Since only one instance of the RustBox is ever accessible, we should not
        // need to do this atomically.
        // Note: we should definitely have RUSTBOX_RUNNING = true here.
        unsafe {
            termbox::tb_shutdown();
        }
    }
}
<|endoftext|>