<commit_before>//! IRC wire protocol message parsers and generators. Incomplete; new messages are added as needed.

use std::fs::File;
use std::io::Write;
use std::str;
use std;

pub fn user<W : Write>(hostname: &str, realname: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "USER {} 0 * :{}\r\n", hostname, realname)
}

pub fn nick<W : Write>(arg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "NICK {}\r\n", arg)
}

pub fn pong<W: Write>(arg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "PONG {}\r\n", arg)
}

pub fn join<W: Write>(channel: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "JOIN {}\r\n", channel)
}

pub fn privmsg<W: Write>(msgtarget: &str, msg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "PRIVMSG {} :{}\r\n", msgtarget, msg)
}

pub fn quit<W : Write>(msg : Option<&str>, mut sink: W) -> std::io::Result<()> {
    match msg {
        None => write!(sink, "QUIT\r\n"),
        Some(msg) => write!(sink, "QUIT {}\r\n", msg)
    }
}

/// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
/// From RFC 2812:
/// > If the prefix is missing from the message, it is assumed to have originated from the
/// > connection from which it was received from.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),

    User {
        nick: String,
        /// user@host
        user: String,
    },
}

/// A PRIVMSG receiver.
#[derive(Debug, PartialEq, Eq)]
pub enum Receiver {
    Chan(String),
    User(String)
}

#[derive(Debug, PartialEq, Eq)]
pub struct Msg {
    pub pfx: Option<Pfx>,
    pub cmd: Cmd,
}

#[derive(Debug, PartialEq, Eq)]
pub enum Cmd {
    PRIVMSG {
        // TODO: In theory this should be a list of receivers, but in practice I've never
        // encountered that case.
        receivers: Receiver,
        contents: String
    },

    JOIN {
        // TODO: Same as above, this should be a list ...
        chan: String
        // TODO: key field might be useful when joining restricted channels. In practice I've never
        // needed it.
    },

    PART {
        // TODO: List of channels
        chan: String,
        msg: Option<String>
    },

    QUIT {
        msg: Option<String>,
    },

    NOTICE {
        nick: String,
        msg: String,
    },

    NICK {
        nick: String,
    },

    PING {
        server: String,
    },

    /// An IRC message other than the ones listed above.
    Other {
        cmd: String,
        params: Vec<String>
    },

    /// Numeric replies are kept generic as there are just too many replies and we probably only
    /// need to handle a small subset of them.
    Reply {
        num: u16,
        params: Vec<String>,
    }
}

/// An intermediate type used during parsing.
enum MsgType<'a> {
    Cmd(&'a str),
    Num(u16),
}

static CRLF: [u8; 2] = [b'\r', b'\n'];

impl Msg {
    /// Try to read an IRC message off buffer. Drops the message when parsing is successful.
    /// Otherwise the buffer is left unchanged.
    pub fn read(buf: &mut Vec<u8>, log_file: &Option<File>) -> Option<Msg> {
        // find "\r\n" separator. `IntoSearcher` implementation for slice needs `str` (why??) so
        // using this hacky method instead.
        let crlf_idx = {
            match buf.windows(2).position(|sub| sub == CRLF) {
                None => return None,
                Some(i) => i,
            }
        };

        let ret = {
            let mut slice: &[u8] = &buf[ 0 .. crlf_idx ];

            // Log the message in debug mode
            if cfg!(debug_assertions) {
                writeln!(log_file.as_ref().unwrap(), "{}",
                unsafe { str::from_utf8_unchecked(slice) }).unwrap();
            }

            let pfx: Option<Pfx> = {
                if slice[0] == b':' {
                    // parse prefix
                    let ws_idx = find_byte(slice, b' ').unwrap();
                    let (mut pfx, slice_) = slice.split_at(ws_idx);
                    // drop the : from pfx
                    pfx = &pfx[ 1 .. ];
                    slice = &slice_[ 1 .. ]; // drop the space
                    Some(parse_pfx(&pfx))
                } else {
                    None
                }
            };

            let msg_ty: MsgType = {
                let ws_idx = find_byte(slice, b' ').unwrap();
                let (cmd, slice_) = slice.split_at(ws_idx);
                slice = &slice_[ 1 .. ]; // drop the space
                match parse_reply_num(cmd) {
                    None => MsgType::Cmd(unsafe {
                        // Cmd strings are added by the server and they're always ASCII strings, so
                        // this is safe and O(1).
                        str::from_utf8_unchecked(cmd)
                    }),
                    Some(num) => MsgType::Num(num)
                }
            };

            let params: Vec<&str> = parse_params(unsafe { str::from_utf8_unchecked(slice) });
            let cmd =
                match msg_ty {
                    MsgType::Cmd("PRIVMSG") if params.len() == 2 => {
                        let target = params[0];
                        let msg = params[1];
                        let receiver =
                            if target.chars().nth(0) == Some('#') {
                                Receiver::Chan(target.to_owned())
                            } else {
                                Receiver::User(target.to_owned())
                            };
                        Cmd::PRIVMSG {
                            receivers: receiver,
                            contents: msg.to_owned(),
                        }
                    },
                    MsgType::Cmd("JOIN") if params.len() == 1 => {
                        let chan = params[0];
                        Cmd::JOIN {
                            chan: chan.to_owned(),
                        }
                    },
                    MsgType::Cmd("PART") if params.len() == 1 || params.len() == 2 => {
                        let mb_msg = if params.len() == 2 { Some(params[1].to_owned()) } else { None };
                        Cmd::PART {
                            chan: params[0].to_owned(),
                            msg: mb_msg,
                        }
                    }
                    MsgType::Cmd("QUIT") if params.len() == 0 || params.len() == 1 => {
                        let mb_msg = if params.len() == 1 { Some(params[0].to_owned()) } else { None };
                        Cmd::QUIT {
                            msg: mb_msg,
                        }
                    }
                    MsgType::Cmd("NOTICE") if params.len() == 2 => {
                        let nick = params[0];
                        let msg = params[1];
                        Cmd::NOTICE {
                            nick: nick.to_owned(),
                            msg: msg.to_owned(),
                        }
                    }
                    MsgType::Cmd("NICK") if params.len() == 1 => {
                        let nick = params[0];
                        Cmd::NICK {
                            nick: nick.to_owned(),
                        }
                    }
                    MsgType::Cmd("PING") if params.len() == 1 => {
                        Cmd::PING {
                            server: params[0].to_owned(),
                        }
                    }
                    MsgType::Num(n) => {
                        Cmd::Reply {
                            num: n,
                            params: params.into_iter().map(|s| s.to_owned()).collect(),
                        }
                    }
                    MsgType::Cmd(cmd) => {
                        Cmd::Other {
                            cmd: cmd.to_owned(),
                            params: params.into_iter().map(|s| s.to_owned()).collect(),
                        }
                    }
                };

            Msg {
                pfx: pfx,
                cmd: cmd,
            }
        };

        buf.drain(0 .. crlf_idx + 2);
        Some(ret)
    }
}

fn parse_pfx(pfx: &[u8]) -> Pfx {
    match find_byte(pfx, b'!') {
        None => Pfx::Server(unsafe { str::from_utf8_unchecked(pfx).to_owned() }),
        Some(idx) => {
            Pfx::User {
                nick: unsafe { str::from_utf8_unchecked(&pfx[ 0 .. idx ]) }.to_owned(),
                user: unsafe { str::from_utf8_unchecked(&pfx[ idx + 1 .. ]) }.to_owned()
            }
        }
    }
}

fn parse_reply_num(bs: &[u8]) -> Option<u16> {

    fn is_num_ascii(b : u8) -> bool {
        b >= b'0' && b <= b'9'
    }

    if bs.len() == 3 {
        let n3 = unsafe { *bs.get_unchecked(0) };
        let n2 = unsafe { *bs.get_unchecked(1) };
        let n1 = unsafe { *bs.get_unchecked(2) };
        if is_num_ascii(n3) && is_num_ascii(n2) && is_num_ascii(n1) {
            return Some(((n3 - b'0') as u16) * 100 +
                        ((n2 - b'0') as u16) * 10  +
                        ((n1 - b'0') as u16));
        }
    }
    None
}

fn parse_params(chrs: &str) -> Vec<&str> {
    debug_assert!(chrs.chars().nth(0) != Some(' '));

    let mut ret: Vec<&str> = Vec::new();

    let mut slice_begins = 0;
    for (char_idx, char) in chrs.char_indices() {
        if char == ':' {
            ret.push(unsafe { chrs.slice_unchecked(char_idx + 1, chrs.len()) });
            return ret;
        } else if char == ' ' {
            ret.push(unsafe { chrs.slice_unchecked(slice_begins, char_idx) });
            slice_begins = char_idx + 1;
        }
    }

    if slice_begins != chrs.len() {
        ret.push(unsafe { chrs.slice_unchecked(slice_begins, chrs.len()) });
    }

    ret
}

fn find_byte(buf: &[u8], byte0: u8) -> Option<usize> {
    for (byte_idx, byte) in buf.iter().enumerate() {
        if *byte == byte0 {
            return Some(byte_idx);
        }
    }
    None
}

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

    #[test]
    fn test_parse_params() {
        assert_eq!(parse_params("p1 p2 p3"), vec!["p1", "p2", "p3"]);
        let v: Vec<&str> = vec![];
        assert_eq!(parse_params(""), v);
        assert_eq!(parse_params(":foo bar baz "), vec!["foo bar baz "]);
        assert_eq!(parse_params(":"), vec![""]);
    }

    #[test]
    fn test_privmsg_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":nick!~nick@unaffiliated/nick PRIVMSG tiny :a b c\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "nick".to_owned(),
                    user: "~nick@unaffiliated/nick".to_owned(),
                }),
                cmd: Cmd::PRIVMSG {
                    receivers: Receiver::User("tiny".to_owned()),
                    contents: "a b c".to_owned()
                }
            }));
        assert_eq!(buf.len(), 0);
    }

    #[test]
    fn test_notice_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":barjavel.freenode.net NOTICE * :*** Looking up your hostname...\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf),
            Some(Msg {
                pfx: Some(Pfx::Server("barjavel.freenode.net".to_owned())),
                cmd: Cmd::NOTICE {
                    nick: "*".to_owned(),
                    msg: "*** Looking up your hostname...".to_owned()
                }
            }));
    }

    #[test]
    fn test_numeric_parsing() {
        let mut buf = vec![];
        write!(&mut buf,
               ":barjavel.freenode.net 001 tiny :Welcome to the freenode Internet Relay Chat Network tiny\r\n")
            .unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 002 tiny :Your host is barjavel.freenode.net[123.123.123.123/8001], \
               running version ircd-seven-1.1.4\r\n").unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 004 tiny_test barjavel.freenode.net \
               ircd-seven-1.1.4 DOQRSZaghilopswz \
               CFILMPQSbcefgijklmnopqrstvz bkloveqjfI\r\n").unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 005 tiny_test CHANTYPES=# EXCEPTS INVEX \
               CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ \
               MAXLIST=bqeI:100 MODES=4 NETWORK=freenode STATUSMSG=@+ CALLERID=g \
               CASEMAPPING=rfc1459 :are supported by this server\r\n").unwrap();

        let mut msgs = vec![];
        while let Some(msg) = Msg::read(&mut buf) {
            msgs.push(msg);
        }

        assert_eq!(msgs.len(), 4);
    }

    #[test]
    fn test_part_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":tiny!~tiny@123.123.123.123 PART #haskell\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "tiny".to_owned(),
                    user: "~tiny@123.123.123.123".to_owned(),
                }),
                cmd: Cmd::PART {
                    chan: "#haskell".to_owned(),
                    msg: None
                },
            }));
    }

    #[test]
    fn test_join_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":tiny!~tiny@192.168.0.1 JOIN #haskell\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "tiny".to_owned(),
                    user: "~tiny@192.168.0.1".to_owned(),
                }),
                cmd: Cmd::JOIN {
                    chan: "#haskell".to_owned(),
                }
            }));
        assert_eq!(buf.len(), 0);
    }
}
<commit_msg>Fix wire unit tests<commit_after>//! IRC wire protocol message parsers and generators. Incomplete; new messages are added as needed.

use std::fs::File;
use std::io::Write;
use std::str;
use std;

pub fn user<W : Write>(hostname: &str, realname: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "USER {} 0 * :{}\r\n", hostname, realname)
}

pub fn nick<W : Write>(arg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "NICK {}\r\n", arg)
}

pub fn pong<W: Write>(arg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "PONG {}\r\n", arg)
}

pub fn join<W: Write>(channel: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "JOIN {}\r\n", channel)
}

pub fn privmsg<W: Write>(msgtarget: &str, msg: &str, mut sink: W) -> std::io::Result<()> {
    write!(sink, "PRIVMSG {} :{}\r\n", msgtarget, msg)
}

pub fn quit<W : Write>(msg : Option<&str>, mut sink: W) -> std::io::Result<()> {
    match msg {
        None => write!(sink, "QUIT\r\n"),
        Some(msg) => write!(sink, "QUIT {}\r\n", msg)
    }
}

/// <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
/// From RFC 2812:
/// > If the prefix is missing from the message, it is assumed to have originated from the
/// > connection from which it was received from.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),

    User {
        nick: String,
        /// user@host
        user: String,
    },
}

/// A PRIVMSG receiver.
#[derive(Debug, PartialEq, Eq)]
pub enum Receiver {
    Chan(String),
    User(String)
}

#[derive(Debug, PartialEq, Eq)]
pub struct Msg {
    pub pfx: Option<Pfx>,
    pub cmd: Cmd,
}

#[derive(Debug, PartialEq, Eq)]
pub enum Cmd {
    PRIVMSG {
        // TODO: In theory this should be a list of receivers, but in practice I've never
        // encountered that case.
        receivers: Receiver,
        contents: String
    },

    JOIN {
        // TODO: Same as above, this should be a list ...
        chan: String
        // TODO: key field might be useful when joining restricted channels. In practice I've never
        // needed it.
    },

    PART {
        // TODO: List of channels
        chan: String,
        msg: Option<String>
    },

    QUIT {
        msg: Option<String>,
    },

    NOTICE {
        nick: String,
        msg: String,
    },

    NICK {
        nick: String,
    },

    PING {
        server: String,
    },

    /// An IRC message other than the ones listed above.
    Other {
        cmd: String,
        params: Vec<String>
    },

    /// Numeric replies are kept generic as there are just too many replies and we probably only
    /// need to handle a small subset of them.
    Reply {
        num: u16,
        params: Vec<String>,
    }
}

/// An intermediate type used during parsing.
enum MsgType<'a> {
    Cmd(&'a str),
    Num(u16),
}

static CRLF: [u8; 2] = [b'\r', b'\n'];

impl Msg {
    /// Try to read an IRC message off buffer. Drops the message when parsing is successful.
    /// Otherwise the buffer is left unchanged.
    pub fn read(buf: &mut Vec<u8>, log_file: &Option<File>) -> Option<Msg> {
        // find "\r\n" separator. `IntoSearcher` implementation for slice needs `str` (why??) so
        // using this hacky method instead.
        let crlf_idx = {
            match buf.windows(2).position(|sub| sub == CRLF) {
                None => return None,
                Some(i) => i,
            }
        };

        let ret = {
            let mut slice: &[u8] = &buf[ 0 .. crlf_idx ];

            // Log the message in debug mode
            if let Some(mut log_file) = log_file.as_ref() {
                writeln!(log_file, "{}",
                         unsafe { str::from_utf8_unchecked(slice) }).unwrap();
            }

            let pfx: Option<Pfx> = {
                if slice[0] == b':' {
                    // parse prefix
                    let ws_idx = find_byte(slice, b' ').unwrap();
                    let (mut pfx, slice_) = slice.split_at(ws_idx);
                    // drop the : from pfx
                    pfx = &pfx[ 1 .. ];
                    slice = &slice_[ 1 .. ]; // drop the space
                    Some(parse_pfx(&pfx))
                } else {
                    None
                }
            };

            let msg_ty: MsgType = {
                let ws_idx = find_byte(slice, b' ').unwrap();
                let (cmd, slice_) = slice.split_at(ws_idx);
                slice = &slice_[ 1 .. ]; // drop the space
                match parse_reply_num(cmd) {
                    None => MsgType::Cmd(unsafe {
                        // Cmd strings are added by the server and they're always ASCII strings, so
                        // this is safe and O(1).
                        str::from_utf8_unchecked(cmd)
                    }),
                    Some(num) => MsgType::Num(num)
                }
            };

            let params: Vec<&str> = parse_params(unsafe { str::from_utf8_unchecked(slice) });
            let cmd =
                match msg_ty {
                    MsgType::Cmd("PRIVMSG") if params.len() == 2 => {
                        let target = params[0];
                        let msg = params[1];
                        let receiver =
                            if target.chars().nth(0) == Some('#') {
                                Receiver::Chan(target.to_owned())
                            } else {
                                Receiver::User(target.to_owned())
                            };
                        Cmd::PRIVMSG {
                            receivers: receiver,
                            contents: msg.to_owned(),
                        }
                    },
                    MsgType::Cmd("JOIN") if params.len() == 1 => {
                        let chan = params[0];
                        Cmd::JOIN {
                            chan: chan.to_owned(),
                        }
                    },
                    MsgType::Cmd("PART") if params.len() == 1 || params.len() == 2 => {
                        let mb_msg = if params.len() == 2 { Some(params[1].to_owned()) } else { None };
                        Cmd::PART {
                            chan: params[0].to_owned(),
                            msg: mb_msg,
                        }
                    }
                    MsgType::Cmd("QUIT") if params.len() == 0 || params.len() == 1 => {
                        let mb_msg = if params.len() == 1 { Some(params[0].to_owned()) } else { None };
                        Cmd::QUIT {
                            msg: mb_msg,
                        }
                    }
                    MsgType::Cmd("NOTICE") if params.len() == 2 => {
                        let nick = params[0];
                        let msg = params[1];
                        Cmd::NOTICE {
                            nick: nick.to_owned(),
                            msg: msg.to_owned(),
                        }
                    }
                    MsgType::Cmd("NICK") if params.len() == 1 => {
                        let nick = params[0];
                        Cmd::NICK {
                            nick: nick.to_owned(),
                        }
                    }
                    MsgType::Cmd("PING") if params.len() == 1 => {
                        Cmd::PING {
                            server: params[0].to_owned(),
                        }
                    }
                    MsgType::Num(n) => {
                        Cmd::Reply {
                            num: n,
                            params: params.into_iter().map(|s| s.to_owned()).collect(),
                        }
                    }
                    MsgType::Cmd(cmd) => {
                        Cmd::Other {
                            cmd: cmd.to_owned(),
                            params: params.into_iter().map(|s| s.to_owned()).collect(),
                        }
                    }
                };

            Msg {
                pfx: pfx,
                cmd: cmd,
            }
        };

        buf.drain(0 .. crlf_idx + 2);
        Some(ret)
    }
}

fn parse_pfx(pfx: &[u8]) -> Pfx {
    match find_byte(pfx, b'!') {
        None => Pfx::Server(unsafe { str::from_utf8_unchecked(pfx).to_owned() }),
        Some(idx) => {
            Pfx::User {
                nick: unsafe { str::from_utf8_unchecked(&pfx[ 0 .. idx ]) }.to_owned(),
                user: unsafe { str::from_utf8_unchecked(&pfx[ idx + 1 .. ]) }.to_owned()
            }
        }
    }
}

fn parse_reply_num(bs: &[u8]) -> Option<u16> {

    fn is_num_ascii(b : u8) -> bool {
        b >= b'0' && b <= b'9'
    }

    if bs.len() == 3 {
        let n3 = unsafe { *bs.get_unchecked(0) };
        let n2 = unsafe { *bs.get_unchecked(1) };
        let n1 = unsafe { *bs.get_unchecked(2) };
        if is_num_ascii(n3) && is_num_ascii(n2) && is_num_ascii(n1) {
            return Some(((n3 - b'0') as u16) * 100 +
                        ((n2 - b'0') as u16) * 10  +
                        ((n1 - b'0') as u16));
        }
    }
    None
}

fn parse_params(chrs: &str) -> Vec<&str> {
    debug_assert!(chrs.chars().nth(0) != Some(' '));

    let mut ret: Vec<&str> = Vec::new();

    let mut slice_begins = 0;
    for (char_idx, char) in chrs.char_indices() {
        if char == ':' {
            ret.push(unsafe { chrs.slice_unchecked(char_idx + 1, chrs.len()) });
            return ret;
        } else if char == ' ' {
            ret.push(unsafe { chrs.slice_unchecked(slice_begins, char_idx) });
            slice_begins = char_idx + 1;
        }
    }

    if slice_begins != chrs.len() {
        ret.push(unsafe { chrs.slice_unchecked(slice_begins, chrs.len()) });
    }

    ret
}

fn find_byte(buf: &[u8], byte0: u8) -> Option<usize> {
    for (byte_idx, byte) in buf.iter().enumerate() {
        if *byte == byte0 {
            return Some(byte_idx);
        }
    }
    None
}

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

    #[test]
    fn test_parse_params() {
        assert_eq!(parse_params("p1 p2 p3"), vec!["p1", "p2", "p3"]);
        let v: Vec<&str> = vec![];
        assert_eq!(parse_params(""), v);
        assert_eq!(parse_params(":foo bar baz "), vec!["foo bar baz "]);
        assert_eq!(parse_params(":"), vec![""]);
    }

    #[test]
    fn test_privmsg_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":nick!~nick@unaffiliated/nick PRIVMSG tiny :a b c\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf, &None),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "nick".to_owned(),
                    user: "~nick@unaffiliated/nick".to_owned(),
                }),
                cmd: Cmd::PRIVMSG {
                    receivers: Receiver::User("tiny".to_owned()),
                    contents: "a b c".to_owned()
                }
            }));
        assert_eq!(buf.len(), 0);
    }

    #[test]
    fn test_notice_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":barjavel.freenode.net NOTICE * :*** Looking up your hostname...\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf, &None),
            Some(Msg {
                pfx: Some(Pfx::Server("barjavel.freenode.net".to_owned())),
                cmd: Cmd::NOTICE {
                    nick: "*".to_owned(),
                    msg: "*** Looking up your hostname...".to_owned()
                }
            }));
    }

    #[test]
    fn test_numeric_parsing() {
        let mut buf = vec![];
        write!(&mut buf,
               ":barjavel.freenode.net 001 tiny :Welcome to the freenode Internet Relay Chat Network tiny\r\n")
            .unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 002 tiny :Your host is barjavel.freenode.net[123.123.123.123/8001], \
               running version ircd-seven-1.1.4\r\n").unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 004 tiny_test barjavel.freenode.net \
               ircd-seven-1.1.4 DOQRSZaghilopswz \
               CFILMPQSbcefgijklmnopqrstvz bkloveqjfI\r\n").unwrap();
        write!(&mut buf,
               ":barjavel.freenode.net 005 tiny_test CHANTYPES=# EXCEPTS INVEX \
               CHANMODES=eIbq,k,flj,CFLMPQScgimnprstz CHANLIMIT=#:120 PREFIX=(ov)@+ \
               MAXLIST=bqeI:100 MODES=4 NETWORK=freenode STATUSMSG=@+ CALLERID=g \
               CASEMAPPING=rfc1459 :are supported by this server\r\n").unwrap();

        let mut msgs = vec![];
        while let Some(msg) = Msg::read(&mut buf, &None) {
            msgs.push(msg);
        }

        assert_eq!(msgs.len(), 4);
    }

    #[test]
    fn test_part_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":tiny!~tiny@123.123.123.123 PART #haskell\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf, &None),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "tiny".to_owned(),
                    user: "~tiny@123.123.123.123".to_owned(),
                }),
                cmd: Cmd::PART {
                    chan: "#haskell".to_owned(),
                    msg: None
                },
            }));
    }

    #[test]
    fn test_join_parsing() {
        let mut buf = vec![];
        write!(&mut buf, ":tiny!~tiny@192.168.0.1 JOIN #haskell\r\n").unwrap();
        assert_eq!(
            Msg::read(&mut buf, &None),
            Some(Msg {
                pfx: Some(Pfx::User {
                    nick: "tiny".to_owned(),
                    user: "~tiny@192.168.0.1".to_owned(),
                }),
                cmd: Cmd::JOIN {
                    chan: "#haskell".to_owned(),
                }
            }));
        assert_eq!(buf.len(), 0);
    }
}
<|endoftext|>


<commit_before>use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;
use std::borrow::Cow;

use zip::read::{ZipFile, ZipArchive};
use zip::result::ZipError;
use quick_xml::{XmlReader, Event, AsStr};

use {DataType, ExcelReader, Range, Cell};
use vba::VbaProject;
use errors::*;

/// A struct representing xml zipped excel file
/// Xlsx, Xlsm, Xlam
pub struct Xlsx {
    zip: ZipArchive<File>,
}

impl Xlsx {
    fn xml_reader<'a>(&'a mut self,
                      path: &str)
                      -> Option<Result<XmlReader<BufReader<ZipFile<'a>>>>> {
        match self.zip.by_name(path) {
            Ok(f) => {
                Some(Ok(XmlReader::from_reader(BufReader::new(f))
                    .with_check(false)
                    .trim_text(false)))
            }
            Err(ZipError::FileNotFound) => None,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

impl ExcelReader for Xlsx {
    fn new(f: File) -> Result<Self> {
        Ok(Xlsx { zip: ZipArchive::new(f)? })
    }

    fn has_vba(&mut self) -> bool {
        self.zip.by_name("xl/vbaProject.bin").is_ok()
    }

    fn vba_project(&mut self) -> Result<Cow<VbaProject>> {
        let mut f = self.zip.by_name("xl/vbaProject.bin")?;
        let len = f.size() as usize;
        VbaProject::new(&mut f, len).map(|v| Cow::Owned(v))
    }

    fn read_shared_strings(&mut self) -> Result<Vec<String>> {
        let mut xml = match self.xml_reader("xl/sharedStrings.xml") {
            None => return Ok(Vec::new()),
            Some(x) => x?,
        };
        let mut strings = Vec::new();
        let mut rich_buffer: Option<String> = None;
        while let Some(res_event) = xml.next() {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"r" => {
                    if let None = rich_buffer {
                        // use a buffer since richtext has multiples <r> and <t> for the same cell
                        rich_buffer = Some(String::new());
                    }
                }
                Ok(Event::End(ref e)) if e.name() == b"si" => {
                    if let Some(s) = rich_buffer {
                        strings.push(s);
                        rich_buffer = None;
                    }
                }
                Ok(Event::Start(ref e)) if e.name() == b"t" => {
                    let value = xml.read_text_unescaped(b"t")?;
                    if let Some(ref mut s) = rich_buffer {
                        s.push_str(&value);
                    } else {
                        strings.push(value);
                    }
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(strings)
    }

    fn read_sheets_names(&mut self,
                         relationships: &HashMap<Vec<u8>, String>)
                         -> Result<Vec<(String, String)>> {
        let xml = match self.xml_reader("xl/workbook.xml") {
            None => return Ok(Vec::new()),
            Some(x) => x?,
        };
        let mut sheets = Vec::new();
        for res_event in xml {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"sheet" => {
                    let mut name = String::new();
                    let mut path = String::new();
                    for a in e.unescaped_attributes() {
                        match a? {
                            (b"name", v) => name = v.as_str()?.to_string(),
                            (b"r:id", v) => {
                                let r = &relationships[&*v][..];
                                // target may have pre-prended "/xl/" or "xl/" path;
                                // strip if present
                                let r = if r.starts_with("/xl/") {
                                    &r[4..]
                                } else if r.starts_with("xl/") {
                                    &r[3..]
                                } else {
                                    r
                                };
                                path = format!("xl/{}", r);
                            }
                            _ => (),
                        }
                    }
                    sheets.push((name, path));
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(sheets)
    }

    fn read_relationships(&mut self) -> Result<HashMap<Vec<u8>, String>> {
        let xml = match self.xml_reader("xl/_rels/workbook.xml.rels") {
            None => return Err("Cannot find relationships file".into()),
            Some(x) => x?,
        };
        let mut relationships = HashMap::new();
        for res_event in xml {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"Relationship" => {
                    let mut id = Vec::new();
                    let mut target = String::new();
                    for a in e.attributes() {
                        match a? {
                            (b"Id", v) => id.extend_from_slice(v),
                            (b"Target", v) => target = v.as_str()?.to_string(),
                            _ => (),
                        }
                    }
                    relationships.insert(id, target);
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(relationships)
    }

    fn read_worksheet_range(&mut self, path: &str, strings: &[String]) -> Result<Range> {
        let mut xml = match self.xml_reader(path) {
            None => return Err(format!("Cannot find {} path", path).into()),
            Some(x) => x?,
        };
        let mut cells = Vec::new();
        'xml: while let Some(res_event) = xml.next() {
            match res_event {
                Err(e) => return Err(e.into()),
                Ok(Event::Start(ref e)) => {
                    match e.name() {
                        b"dimension" => {
                            for a in e.attributes() {
                                if let (b"ref", rdim) = a? {
                                    let (start, end) = get_dimension(rdim)?;
                                    cells.reserve(((end.0 - start.0 + 1) * (end.1 - start.1 + 1)) as
                                                 usize);
                                    continue 'xml;
                                }
                            }
                            return Err(format!("Expecting dimension, got {:?}", e).into());
                        }
                        b"sheetData" => read_sheet_data(&mut xml, strings, &mut cells)?,
                        _ => (),
                    }
                }
                _ => (),
            }
        }
        Ok(Range::from_sparse(cells))
    }
}

/// read sheetData node
fn read_sheet_data(xml: &mut XmlReader<BufReader<ZipFile>>,
                   strings: &[String],
                   cells: &mut Vec<Cell>)
                   -> Result<()> {
    while let Some(res_event) = xml.next() {
        match res_event {
            Err(e) => return Err(e.into()),
            Ok(Event::Start(ref c_element)) if c_element.name() == b"c" => {
                let pos = match c_element.attributes()
                    .filter_map(|a| match a {
                        Err(e) => Some(Err(e.into())),
                        Ok((b"r", v)) => Some(get_row_column(v)),
                        _ => None,
                    })
                    .next() {
                    Some(v) => v?,
                    None => return Err("Cell without a 'r' reference tag".into()),
                };
                loop {
                    match xml.next() {
                        Some(Err(e)) => return Err(e.into()),
                        Some(Ok(Event::Start(ref e))) => {
                            match e.name() {
                                b"v" => {
                                    // value
                                    let v = xml.read_text_unescaped(b"v")?;
                                    let value = match c_element.attributes()
                                        .filter_map(|a| a.ok())
                                        .find(|&(k, _)| k == b"t") {
                                        Some((_, b"s")) => {
                                            // shared string
                                            let idx: usize = v.parse()?;
                                            DataType::String(strings[idx].clone())
                                        }
                                        Some((_, b"str")) => {
                                            // regular string
                                            DataType::String(v)
                                        }
                                        Some((_, b"b")) => {
                                            // boolean
                                            DataType::Bool(v != "0")
                                        }
                                        Some((_, b"e")) => {
                                            // error
                                            DataType::Error(v.parse()?)
                                        }
                                        _ => v.parse().map(DataType::Float)?,
                                    };
                                    cells.push(Cell::new(pos, value));
                                    break;
                                }
                                b"f" => (), // formula, ignore
                                _name => return Err("not v or f node".into()),
                            }
                        }
                        Some(Ok(Event::End(ref e))) if e.name() == b"c" => break,
                        None => return Err("End of xml".into()),
                        _ => (),
                    }
                }
            }
            Ok(Event::End(ref e)) if e.name() == b"sheetData" => return Ok(()),
            _ => (),
        }
    }
    Err("Could not find </sheetData>".into())
}

/// converts a text representation (e.g. "A6:G67") of a dimension into integers
/// - top left (row, column),
/// - bottom right (row, column)
fn get_dimension(dimension: &[u8]) -> Result<((u32, u32), (u32, u32))> {
    let parts: Vec<_> = dimension.split(|c| *c == b':')
        .map(|s| get_row_column(s))
        .collect::<Result<Vec<_>>>()?;

    match parts.len() {
        0 => Err("dimension cannot be empty".into()),
        1 => Ok((parts[0], parts[0])),
        2 => Ok((parts[0], parts[1])),
        len => Err(format!("range dimension has 0 or 1 ':', got {}", len).into()),
    }
}

/// converts a text range name into its position (row, column) (0 based index)
fn get_row_column(range: &[u8]) -> Result<(u32, u32)> {
    let (mut row, mut col) = (0, 0);
    let mut pow = 1;
    let mut readrow = true;
    for c in range.iter().rev() {
        match *c {
            c @ b'0'...b'9' => {
                if readrow {
                    row += ((c - b'0') as u32) * pow;
                    pow *= 10;
                } else {
                    return Err(format!("Numeric character are only allowed \
                        at the end of the range: {:x}",
                                       c)
                        .into());
                }
            }
            c @ b'A'...b'Z' => {
                if readrow {
                    pow = 1;
                    readrow = false;
                }
                col += ((c - b'A') as u32 + 1) * pow;
                pow *= 26;
            }
            c @ b'a'...b'z' => {
                if readrow {
                    pow = 1;
                    readrow = false;
                }
                col += ((c - b'a') as u32 + 1) * pow;
                pow *= 26;
            }
            _ => return Err(format!("Expecting alphanumeric character, got {:x}", c).into()),
        }
    }
    Ok((row - 1, col - 1))
}

#[test]
fn test_dimensions() {
    assert_eq!(get_row_column(b"A1").unwrap(), (0, 0));
    assert_eq!(get_row_column(b"C107").unwrap(), (106, 2));
    assert_eq!(get_dimension(b"C2:D35").unwrap(), ((1, 2), (34, 3)));
}
<commit_msg>implement inlineStr cell content parsing<commit_after>use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;
use std::borrow::Cow;

use zip::read::{ZipFile, ZipArchive};
use zip::result::ZipError;
use quick_xml::{XmlReader, Event, AsStr, Element};

use {DataType, ExcelReader, Range, Cell};
use vba::VbaProject;
use errors::*;

/// A struct representing xml zipped excel file
/// Xlsx, Xlsm, Xlam
pub struct Xlsx {
    zip: ZipArchive<File>,
}

impl Xlsx {
    fn xml_reader<'a>(&'a mut self,
                      path: &str)
                      -> Option<Result<XmlReader<BufReader<ZipFile<'a>>>>> {
        match self.zip.by_name(path) {
            Ok(f) => {
                Some(Ok(XmlReader::from_reader(BufReader::new(f))
                    .with_check(false)
                    .trim_text(false)))
            }
            Err(ZipError::FileNotFound) => None,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

impl ExcelReader for Xlsx {
    fn new(f: File) -> Result<Self> {
        Ok(Xlsx { zip: ZipArchive::new(f)? })
    }

    fn has_vba(&mut self) -> bool {
        self.zip.by_name("xl/vbaProject.bin").is_ok()
    }

    fn vba_project(&mut self) -> Result<Cow<VbaProject>> {
        let mut f = self.zip.by_name("xl/vbaProject.bin")?;
        let len = f.size() as usize;
        VbaProject::new(&mut f, len).map(|v| Cow::Owned(v))
    }

    fn read_shared_strings(&mut self) -> Result<Vec<String>> {
        let mut xml = match self.xml_reader("xl/sharedStrings.xml") {
            None => return Ok(Vec::new()),
            Some(x) => x?,
        };
        let mut strings = Vec::new();
        let mut rich_buffer: Option<String> = None;
        while let Some(res_event) = xml.next() {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"r" => {
                    if let None = rich_buffer {
                        // use a buffer since richtext has multiples <r> and <t> for the same cell
                        rich_buffer = Some(String::new());
                    }
                }
                Ok(Event::End(ref e)) if e.name() == b"si" => {
                    if let Some(s) = rich_buffer {
                        strings.push(s);
                        rich_buffer = None;
                    }
                }
                Ok(Event::Start(ref e)) if e.name() == b"t" => {
                    let value = xml.read_text_unescaped(b"t")?;
                    if let Some(ref mut s) = rich_buffer {
                        s.push_str(&value);
                    } else {
                        strings.push(value);
                    }
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(strings)
    }

    fn read_sheets_names(&mut self,
                         relationships: &HashMap<Vec<u8>, String>)
                         -> Result<Vec<(String, String)>> {
        let xml = match self.xml_reader("xl/workbook.xml") {
            None => return Ok(Vec::new()),
            Some(x) => x?,
        };
        let mut sheets = Vec::new();
        for res_event in xml {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"sheet" => {
                    let mut name = String::new();
                    let mut path = String::new();
                    for a in e.unescaped_attributes() {
                        match a? {
                            (b"name", v) => name = v.as_str()?.to_string(),
                            (b"r:id", v) => {
                                let r = &relationships[&*v][..];
                                // target may have pre-prended "/xl/" or "xl/" path;
                                // strip if present
                                let r = if r.starts_with("/xl/") {
                                    &r[4..]
                                } else if r.starts_with("xl/") {
                                    &r[3..]
                                } else {
                                    r
                                };
                                path = format!("xl/{}", r);
                            }
                            _ => (),
                        }
                    }
                    sheets.push((name, path));
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(sheets)
    }

    fn read_relationships(&mut self) -> Result<HashMap<Vec<u8>, String>> {
        let xml = match self.xml_reader("xl/_rels/workbook.xml.rels") {
            None => return Err("Cannot find relationships file".into()),
            Some(x) => x?,
        };
        let mut relationships = HashMap::new();
        for res_event in xml {
            match res_event {
                Ok(Event::Start(ref e)) if e.name() == b"Relationship" => {
                    let mut id = Vec::new();
                    let mut target = String::new();
                    for a in e.attributes() {
                        match a? {
                            (b"Id", v) => id.extend_from_slice(v),
                            (b"Target", v) => target = v.as_str()?.to_string(),
                            _ => (),
                        }
                    }
                    relationships.insert(id, target);
                }
                Err(e) => return Err(e.into()),
                _ => (),
            }
        }
        Ok(relationships)
    }

    fn read_worksheet_range(&mut self, path: &str, strings: &[String]) -> Result<Range> {
        let mut xml = match self.xml_reader(path) {
            None => return Err(format!("Cannot find {} path", path).into()),
            Some(x) => x?,
        };
        let mut cells = Vec::new();
        'xml: while let Some(res_event) = xml.next() {
            match res_event {
                Err(e) => return Err(e.into()),
                Ok(Event::Start(ref e)) => {
                    match e.name() {
                        b"dimension" => {
                            for a in e.attributes() {
                                if let (b"ref", rdim) = a? {
                                    let (start, end) = get_dimension(rdim)?;
                                    cells.reserve(((end.0 - start.0 + 1) * (end.1 - start.1 + 1)) as
                                                 usize);
                                    continue 'xml;
                                }
                            }
                            return Err(format!("Expecting dimension, got {:?}", e).into());
                        }
                        b"sheetData" => read_sheet_data(&mut xml, strings, &mut cells)?,
                        _ => (),
                    }
                }
                _ => (),
            }
        }
        Ok(Range::from_sparse(cells))
    }
}

/// read sheetData node
fn read_sheet_data(xml: &mut XmlReader<BufReader<ZipFile>>,
                   strings: &[String],
                   cells: &mut Vec<Cell>)
                   -> Result<()> {
    /// read the contents of an <is> cell
    fn read_inline_str(xml: &mut XmlReader<BufReader<ZipFile>>) -> Result<DataType> {
        while let Some(is_evt) = xml.next() {
            match is_evt {
                Err(e) => return Err(e.into()),
                Ok(Event::Start(ref t)) if t.name() == b"t" => {
                    return Ok(DataType::String(xml.read_text_unescaped(b"t")?));
                }
                _ => {}
            }
        }
        Err("unable to read inlineStr".into())
    }
    /// read the contents of a <v> cell
    fn read_value(xml: &mut XmlReader<BufReader<ZipFile>>,
                  strings: &[String],
                  c_element: &Element)
                  -> Result<DataType> {
        let v = xml.read_text_unescaped(b"v")?;
        match get_attribute(c_element, b"t")? {
            Some(b"s") => {
                // shared string
                let idx: usize = v.parse()?;
                Ok(DataType::String(strings[idx].clone()))
            }
            Some(b"b") => {
                // boolean
                Ok(DataType::Bool(v != "0"))
            }
            Some(b"e") => {
                // error
                Ok(DataType::Error(v.parse()?))
            }
            Some(b"d") => {
                // date
                // TODO: create a DataType::Date
                // currently just return as string (ISO 8601)
                Ok(DataType::String(v))
            }
            Some(b"str") => {
                // see http://officeopenxml.com/SScontentOverview.php
                // str - refers to formula cells
                // * <c .. t='v' .. > indicates calculated value (this case)
                // * <c .. t='f' .. > to the formula string (ignored case
                // TODO: Fully support a DataType::Formula representing both Formula string &
                // last calculated value?
                v.parse()
                    .map(DataType::Float)
                    .map_err(Error::from)
            }
            Some(b"n") => {
                // n - number
                v.parse().map(DataType::Float).map_err(Error::from)
            }
            None => {
                v.parse()
                    .map(DataType::Float)
                    .map_err(Error::from)
            }
            Some(b"is") => {
                // this case should be handled in outer loop over cell elements, in which
                // case read_inline_str is called instead. Case included here for completeness.
                return Err("called read_value on a cell of type inlineStr".into());
            }
            Some(t) => return Err(format!("unknown cell 't' attribute={:?}", t).into()),
        }
    }
    /// search through an Element's attributes for the named one
    fn get_attribute<'a>(e: &'a Element, n: &'a [u8]) -> Result<Option<&'a [u8]>> {
        for a in e.attributes() {
            match a {
                Ok((k, v)) if k == n => return Ok(Some(v)),
                Ok(_) => {} // ignore other attributes
                Err(qe) => return Err(qe.into()),
            }
        }

        Ok(None)
    }
    while let Some(res_event) = xml.next() {
        match res_event {
            Err(e) => return Err(e.into()),
            Ok(Event::Start(ref c_element)) if c_element.name() == b"c" => {
                let pos = get_attribute(c_element, b"r")
                    .and_then(|o| o.ok_or_else(|| "Cell missing 'r' attribute tag".into()))
                    .and_then(get_row_column)?;

                loop {
                    match xml.next() {
                        Some(Err(e)) => return Err(e.into()),
                        Some(Ok(Event::Start(ref e))) => {
                            match e.name() {
                                b"is" => {
                                    cells.push(Cell::new(pos, read_inline_str(xml)?));
                                    break;
                                }
                                b"v" => {
                                    cells.push(Cell::new(pos, read_value(xml, strings, c_element)?));
                                    break;
                                }
                                b"f" => {} // ignore f nodes
                                n => return Err(format!("not a v, f, or is node: {:?}", n).into()),
                            }
                        }
                        Some(Ok(Event::End(ref e))) if e.name() == b"c" => {
                            break;
                        }
                        None => return Err("End of xml".into()),
                        _ => (),
                    }
                }
            }
            Ok(Event::End(ref e)) if e.name() == b"sheetData" => return Ok(()),
            _ => (),
        }
    }
    Err("Could not find </sheetData>".into())
}

/// converts a text representation (e.g. "A6:G67") of a dimension into integers
/// - top left (row, column),
/// - bottom right (row, column)
fn get_dimension(dimension: &[u8]) -> Result<((u32, u32), (u32, u32))> {
    let parts: Vec<_> = dimension.split(|c| *c == b':')
        .map(|s| get_row_column(s))
        .collect::<Result<Vec<_>>>()?;

    match parts.len() {
        0 => Err("dimension cannot be empty".into()),
        1 => Ok((parts[0], parts[0])),
        2 => Ok((parts[0], parts[1])),
        len => Err(format!("range dimension has 0 or 1 ':', got {}", len).into()),
    }
}

/// converts a text range name into its position (row, column) (0 based index)
fn get_row_column(range: &[u8]) -> Result<(u32, u32)> {
    let (mut row, mut col) = (0, 0);
    let mut pow = 1;
    let mut readrow = true;
    for c in range.iter().rev() {
        match *c {
            c @ b'0'...b'9' => {
                if readrow {
                    row += ((c - b'0') as u32) * pow;
                    pow *= 10;
                } else {
                    return Err(format!("Numeric character are only allowed \
                        at the end of the range: {:x}",
                                       c)
                        .into());
                }
            }
            c @ b'A'...b'Z' => {
                if readrow {
                    pow = 1;
                    readrow = false;
                }
                col += ((c - b'A') as u32 + 1) * pow;
                pow *= 26;
            }
            c @ b'a'...b'z' => {
                if readrow {
                    pow = 1;
                    readrow = false;
                }
                col += ((c - b'a') as u32 + 1) * pow;
                pow *= 26;
            }
            _ => return Err(format!("Expecting alphanumeric character, got {:x}", c).into()),
        }
    }
    Ok((row - 1, col - 1))
}

#[test]
fn test_dimensions() {
    assert_eq!(get_row_column(b"A1").unwrap(), (0, 0));
    assert_eq!(get_row_column(b"C107").unwrap(), (106, 2));
    assert_eq!(get_dimension(b"C2:D35").unwrap(), ((1, 2), (34, 3)));
}
<|endoftext|>


<commit_before>use mio::*;
use mio::tcp::{TcpStream};
use mio::channel::channel;
use mio::channel::{Sender, Receiver};
use std::collections::HashMap;
use std::net::IpAddr;
use metainfo::MetaInfo;
use metainfo::SHA1Hash20b;
use wire::handler::{BasicHandler, PeerHandler};

const OUTSIDE_MSG: Token = Token(0);

pub struct Protocol<H: PeerHandler> {
    streams: HashMap<usize, TcpStream>,
    poll: Poll,
    sender: Sender<ChanMsg>,
    receiver: Receiver<ChanMsg>,
    info: MetaInfo,
    info_hash: SHA1Hash20b,
    handlers: Vec<H>
}

#[derive(Debug)]
pub enum ChanMsg {
    NewPeer(IpAddr)
}

impl<H: PeerHandler> Protocol<H> {
    pub fn new(info: &MetaInfo, hash: SHA1Hash20b) -> (Protocol<H>, Sender<ChanMsg>, Receiver<ChanMsg>) {
        let poll = Poll::new().unwrap();

        match (channel(), channel()) {
            ((to_inside, from_outside), (to_outside, from_inside)) => {

                poll.register(&from_outside, OUTSIDE_MSG, Ready::readable(),
                    PollOpt::edge()).unwrap();

                let proto = Protocol {
                    streams: HashMap::new(),
                    poll: poll,
                    sender: to_outside,
                    receiver: from_outside,
                    info: info.clone(),
                    info_hash: hash,
                    handlers: Vec::new()
                };

                (proto, to_inside, from_inside)
            }
        }
    }

    pub fn run(&mut self) {
        let mut events = Events::with_capacity(1024);
        loop {
             self.poll.poll(&mut events, None).unwrap();
             for event in events.iter() {
                 match event.token() {
                     OUTSIDE_MSG => {
                         match self.receiver.try_recv() {
                             Ok(msg) => self._handle_outside_msg(msg),
                             _ => ()
                         }
                     },
                     _ => println!("Oooooh better do something!")
                 }
             }
        }
    }

    fn _handle_outside_msg(&self, msg: ChanMsg) {
    }
}
<commit_msg>Implement connecting to peer using socket<commit_after>use mio::*;
use mio::tcp::{TcpStream};
use mio::channel::channel;
use mio::channel::{Sender, Receiver};
use std::collections::HashMap;
use std::net::IpAddr;
use std::net::SocketAddr;
use metainfo::MetaInfo;
use metainfo::SHA1Hash20b;
use wire::handler::{BasicHandler, PeerHandler};

const OUTSIDE_MSG: Token = Token(0);

pub struct Protocol<H: PeerHandler> {
    streams: HashMap<usize, TcpStream>,
    poll: Poll,
    sender: Sender<ChanMsg>,
    receiver: Receiver<ChanMsg>,
    info: MetaInfo,
    info_hash: SHA1Hash20b,
    handlers: Vec<H>
}

#[derive(Debug)]
pub enum ChanMsg {
    NewPeer(IpAddr, u16)
}

impl<H: PeerHandler> Protocol<H> {
    pub fn new(info: &MetaInfo, hash: SHA1Hash20b) -> (Protocol<H>, Sender<ChanMsg>, Receiver<ChanMsg>) {
        let poll = Poll::new().unwrap();

        match (channel(), channel()) {
            ((to_inside, from_outside), (to_outside, from_inside)) => {

                poll.register(&from_outside, OUTSIDE_MSG, Ready::readable(),
                    PollOpt::edge()).unwrap();

                let proto = Protocol {
                    streams: HashMap::new(),
                    poll: poll,
                    sender: to_outside,
                    receiver: from_outside,
                    info: info.clone(),
                    info_hash: hash,
                    handlers: Vec::new()
                };

                (proto, to_inside, from_inside)
            }
        }
    }

    pub fn run(&mut self) {
        let mut events = Events::with_capacity(1024);
        loop {
             self.poll.poll(&mut events, None).unwrap();
             for event in events.iter() {
                 match event.token() {
                     OUTSIDE_MSG => {
                         match self.receiver.try_recv() {
                             Ok(msg) => self._handle_outside_msg(msg),
                             _ => ()
                         }
                     },
                     _ => println!("Oooooh better do something!")
                 }
             }
        }
    }

    fn _handle_outside_msg(&mut self, msg: ChanMsg) {
        match msg {
            ChanMsg::NewPeer(ip, port) => self._connect_to_peer(ip, port)
        }
    }

    fn _connect_to_peer(&mut self, addr: IpAddr, port: u16) {
        let sock_addr = SocketAddr::new(addr, port);
        let sock = TcpStream::connect(&sock_addr).unwrap();
        self.poll.register(&sock, Token(1), Ready::readable() | Ready::writable(), PollOpt::edge()).unwrap();
    }
}
<|endoftext|>


<commit_before>use std::marker;
use std::cell::UnsafeCell;
use std::fmt;
use std::mem;
use std::ptr;
use std::ops::{Deref, DerefMut};

// For now, treat this as an arch-independent constant.
const CACHE_LINE: usize = 32;

#[cfg_attr(feature = "nightly",
           repr(simd))]
#[derive(Debug)]
struct Padding(u64, u64, u64, u64);

/// Pad `T` to the length of a cacheline.
///
/// Sometimes concurrent programming requires a piece of data to be padded out
/// to the size of a cacheline to avoid "false sharing": cachelines being
/// invalidated due to unrelated concurrent activity. Use the `CachePadded` type
/// when you want to *avoid* cache locality.
///
/// At the moment, cache lines are assumed to be 32 * sizeof(usize) on all
/// architectures.
///
/// **Warning**: the wrapped data is never dropped; move out using `ptr::read`
/// if you need to run dtors.
pub struct CachePadded<T> {
    data: UnsafeCell<[usize; CACHE_LINE]>,
    _marker: ([Padding; 0], marker::PhantomData<T>),
}

impl<T> fmt::Debug for CachePadded<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "CachePadded {{ ... }}")
    }
}

unsafe impl<T: Send> Send for CachePadded<T> {}
unsafe impl<T: Sync> Sync for CachePadded<T> {}

/// Types for which mem::zeroed() is safe.
///
/// If a type `T: ZerosValid`, then a sequence of zeros the size of `T` must be
/// a valid member of the type `T`.
pub unsafe trait ZerosValid {}

#[cfg(feature = "nightly")]
unsafe impl ZerosValid for .. {}

macro_rules! zeros_valid { ($( $T:ty )*) => ($(
    unsafe impl ZerosValid for $T {}
)*)}

zeros_valid!(u8 u16 u32 u64 usize);
zeros_valid!(i8 i16 i32 i64 isize);

unsafe impl ZerosValid for ::std::sync::atomic::AtomicUsize {}
unsafe impl<T> ZerosValid for ::std::sync::atomic::AtomicPtr<T> {}

impl<T: ZerosValid> CachePadded<T> {
    /// A const fn equivalent to mem::zeroed().
    #[cfg(not(feature = "nightly"))]
    pub fn zeroed() -> CachePadded<T> {
        CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        }
    }

    /// A const fn equivalent to mem::zeroed().
    #[cfg(feature = "nightly")]
    pub const fn zeroed() -> CachePadded<T> {
        CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        }
    }
}

#[inline]
/// Assert that the size and alignment of `T` are consistent with `CachePadded<T>`.
fn assert_valid<T>() {
    assert!(mem::size_of::<T>() <= mem::size_of::<CachePadded<T>>());
    assert!(mem::align_of::<T>() <= mem::align_of::<CachePadded<T>>());
}

impl<T> CachePadded<T> {
    /// Wrap `t` with cacheline padding.
    ///
    /// **Warning**: the wrapped data is never dropped; move out using
    /// `ptr:read` if you need to run dtors.
    pub fn new(t: T) -> CachePadded<T> {
        assert_valid::<T>();
        let ret = CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        };
        unsafe {
            let p: *mut T = mem::transmute(&ret.data);
            ptr::write(p, t);
        }
        ret
    }
}

impl<T> Deref for CachePadded<T> {
    type Target = T;
    fn deref(&self) -> &T {
        assert_valid::<T>();
        unsafe { mem::transmute(&self.data) }
    }
}

impl<T> DerefMut for CachePadded<T> {
    fn deref_mut(&mut self) -> &mut T {
        assert_valid::<T>();
        unsafe { mem::transmute(&mut self.data) }
    }
}

// FIXME: support Drop by pulling out a version usable for statics
/*
impl<T> Drop for CachePadded<T> {
    fn drop(&mut self) {
        assert_valid::<T>();
        let p: *mut T = mem::transmute(&self.data);
        mem::drop(ptr::read(p));
    }
}
*/

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

    #[test]
    fn cache_padded_store_u64() {
        let x: CachePadded<u64> = CachePadded::new(17);
        assert_eq!(*x, 17);
    }

    #[test]
    fn cache_padded_store_pair() {
        let x: CachePadded<(u64, u64)> = CachePadded::new((17, 37));
        assert_eq!(x.0, 17);
        assert_eq!(x.1, 37);
    }
}
<commit_msg>Use the new auto trait syntax<commit_after>use std::marker;
use std::cell::UnsafeCell;
use std::fmt;
use std::mem;
use std::ptr;
use std::ops::{Deref, DerefMut};

// For now, treat this as an arch-independent constant.
const CACHE_LINE: usize = 32;

#[cfg_attr(feature = "nightly",
           repr(simd))]
#[derive(Debug)]
struct Padding(u64, u64, u64, u64);

/// Pad `T` to the length of a cacheline.
///
/// Sometimes concurrent programming requires a piece of data to be padded out
/// to the size of a cacheline to avoid "false sharing": cachelines being
/// invalidated due to unrelated concurrent activity. Use the `CachePadded` type
/// when you want to *avoid* cache locality.
///
/// At the moment, cache lines are assumed to be 32 * sizeof(usize) on all
/// architectures.
///
/// **Warning**: the wrapped data is never dropped; move out using `ptr::read`
/// if you need to run dtors.
pub struct CachePadded<T> {
    data: UnsafeCell<[usize; CACHE_LINE]>,
    _marker: ([Padding; 0], marker::PhantomData<T>),
}

impl<T> fmt::Debug for CachePadded<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "CachePadded {{ ... }}")
    }
}

unsafe impl<T: Send> Send for CachePadded<T> {}
unsafe impl<T: Sync> Sync for CachePadded<T> {}

/// Types for which mem::zeroed() is safe.
///
/// If a type `T: ZerosValid`, then a sequence of zeros the size of `T` must be
/// a valid member of the type `T`.
#[cfg(not(feature = "nightly"))]
pub unsafe trait ZerosValid {}

/// Types for which mem::zeroed() is safe.
///
/// If a type `T: ZerosValid`, then a sequence of zeros the size of `T` must be
/// a valid member of the type `T`.
#[cfg(feature = "nightly")]
pub unsafe auto trait ZerosValid {}

macro_rules! zeros_valid { ($( $T:ty )*) => ($(
    unsafe impl ZerosValid for $T {}
)*)}

zeros_valid!(u8 u16 u32 u64 usize);
zeros_valid!(i8 i16 i32 i64 isize);

unsafe impl ZerosValid for ::std::sync::atomic::AtomicUsize {}
unsafe impl<T> ZerosValid for ::std::sync::atomic::AtomicPtr<T> {}

impl<T: ZerosValid> CachePadded<T> {
    /// A const fn equivalent to mem::zeroed().
    #[cfg(not(feature = "nightly"))]
    pub fn zeroed() -> CachePadded<T> {
        CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        }
    }

    /// A const fn equivalent to mem::zeroed().
    #[cfg(feature = "nightly")]
    pub const fn zeroed() -> CachePadded<T> {
        CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        }
    }
}

#[inline]
/// Assert that the size and alignment of `T` are consistent with `CachePadded<T>`.
fn assert_valid<T>() {
    assert!(mem::size_of::<T>() <= mem::size_of::<CachePadded<T>>());
    assert!(mem::align_of::<T>() <= mem::align_of::<CachePadded<T>>());
}

impl<T> CachePadded<T> {
    /// Wrap `t` with cacheline padding.
    ///
    /// **Warning**: the wrapped data is never dropped; move out using
    /// `ptr:read` if you need to run dtors.
    pub fn new(t: T) -> CachePadded<T> {
        assert_valid::<T>();
        let ret = CachePadded {
            data: UnsafeCell::new(([0; CACHE_LINE])),
            _marker: ([], marker::PhantomData),
        };
        unsafe {
            let p: *mut T = mem::transmute(&ret.data);
            ptr::write(p, t);
        }
        ret
    }
}

impl<T> Deref for CachePadded<T> {
    type Target = T;
    fn deref(&self) -> &T {
        assert_valid::<T>();
        unsafe { mem::transmute(&self.data) }
    }
}

impl<T> DerefMut for CachePadded<T> {
    fn deref_mut(&mut self) -> &mut T {
        assert_valid::<T>();
        unsafe { mem::transmute(&mut self.data) }
    }
}

// FIXME: support Drop by pulling out a version usable for statics
/*
impl<T> Drop for CachePadded<T> {
    fn drop(&mut self) {
        assert_valid::<T>();
        let p: *mut T = mem::transmute(&self.data);
        mem::drop(ptr::read(p));
    }
}
*/

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

    #[test]
    fn cache_padded_store_u64() {
        let x: CachePadded<u64> = CachePadded::new(17);
        assert_eq!(*x, 17);
    }

    #[test]
    fn cache_padded_store_pair() {
        let x: CachePadded<(u64, u64)> = CachePadded::new((17, 37));
        assert_eq!(x.0, 17);
        assert_eq!(x.1, 37);
    }
}
<|endoftext|>


<commit_before>// GObject Introspection Rust bindings.
// Copyright (C) 2014  Luis Araujo <araujoc.luisf@gmail.com>

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use glib_gobject::GBoolean;
use types::{GIBaseInfo, GICallableInfo, GITypeInfo};

use std::mem::transmute;

#[link(name = "girepository-1.0")]
extern "C" {
    fn g_callable_info_is_method(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_can_throw_gerror(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_get_return_type(info: *GICallableInfo) -> *GITypeInfo;
}

/// Determines if the callable info is a method.
pub fn is_method(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_is_method(info) }
}

pub fn can_throw_error(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_can_throw_gerror(info) }
}

/// Obtain the return type of a callable item as a GITypeInfo.
pub fn get_return_type(info: *GICallableInfo) -> *GITypeInfo {
    unsafe { g_callable_info_get_return_type(info) }
}

/// Convert GIBaseInfo to GICallableInfo.
pub fn to_gi_callable_info(object: *GIBaseInfo) -> *GICallableInfo {
    unsafe { transmute(object) }
}
<commit_msg>Add new methods to callableinfo module<commit_after>// GObject Introspection Rust bindings.
// Copyright (C) 2014  Luis Araujo <araujoc.luisf@gmail.com>

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

extern crate libc;

use callableinfo::libc::{c_char, c_int};
use glib_gobject::GBoolean;
use types::{GIBaseInfo, GICallableInfo, GIArgInfo, GITypeInfo, GITransfer};
use utils::to_string;

use std::mem::transmute;

#[link(name = "girepository-1.0")]
extern "C" {
    fn g_callable_info_is_method(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_can_throw_gerror(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_get_return_type(info: *GICallableInfo) -> *GITypeInfo;
    fn g_callable_info_load_return_type(info: *GICallableInfo, type_: *GITypeInfo);
    fn g_callable_info_get_return_attribute(info: *GICallableInfo, name: *c_char) -> *c_char;
    fn g_callable_info_get_caller_owns(info: *GICallableInfo) -> c_int;
    fn g_callable_info_may_return_null(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_skip_return(info: *GICallableInfo) -> GBoolean;
    fn g_callable_info_get_n_args(info: *GICallableInfo) -> c_int;
    fn g_callable_info_get_arg(info: *GICallableInfo, n: c_int) -> *GIArgInfo;
    fn g_callable_info_load_arg(info: *GICallableInfo, n: c_int, arg: *GIArgInfo);
}

/// Determines if the callable info is a method.
pub fn is_method(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_is_method(info) }
}

pub fn can_throw_error(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_can_throw_gerror(info) }
}

/// Obtain the return type of a callable item as a GITypeInfo.
pub fn get_return_type(info: *GICallableInfo) -> *GITypeInfo {
    unsafe { g_callable_info_get_return_type(info) }
}

/// Obtain information about a return value of callable; this function is a
/// variant of g_callable_info_get_return_type() designed for stack allocation.
pub fn load_return_type(info: *GICallableInfo, type_: *GITypeInfo) {
    unsafe { g_callable_info_load_return_type(info, type_) }
}

/// Retrieve an arbitrary attribute associated with the return value.
pub fn get_return_attribute(info: *GICallableInfo, name: &str) -> Option<String> {
    to_string(name.with_c_str(|c_name| unsafe {
        g_callable_info_get_return_attribute(info, c_name)
    }))
}

/// See whether the caller owns the return value of this callable.
pub fn get_caller_owns(info: *GICallableInfo) -> Option<GITransfer> {
    let transfer: Option<GITransfer> =
        FromPrimitive::from_i32(unsafe { g_callable_info_get_caller_owns(info) });
    return transfer
}

/// See if a callable could return NULL.
pub fn may_return_null(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_may_return_null(info) }
}

/// See if a callable's return value is only useful in C.
pub fn skip_return(info: *GICallableInfo) -> GBoolean {
    unsafe { g_callable_info_skip_return(info) }
}

/// Obtain the number of arguments (both IN and OUT) for this callable.
pub fn get_n_args(info: *GICallableInfo) -> int {
    unsafe { g_callable_info_get_n_args(info) as int }
}

/// Obtain information about a particular argument of this callable.
pub fn get_arg(info: *GICallableInfo, n: int) -> *GIArgInfo {
    unsafe { g_callable_info_get_arg(info, n as c_int) }
}

/// Obtain information about a particular argument of this callable; this
/// function is a variant of g_callable_info_get_arg() designed for stack
/// allocation.
pub fn load_arg(info: *GICallableInfo, n: int, arg: *GIArgInfo) {
    unsafe { g_callable_info_load_arg(info, n as c_int, arg) }
}

/// Convert GIBaseInfo to GICallableInfo.
pub fn to_gi_callable_info(object: *GIBaseInfo) -> *GICallableInfo {
    unsafe { transmute(object) }
}
<|endoftext|>


<commit_before>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::cell::Cell;

use client::menu::Menu;

use failure::Error;

pub enum Item {
    Submenu(Menu),
    Action(Box<Fn()>),
    Toggle(Toggle),
    Enum,
}

pub struct Toggle {
    state: Cell<bool>,
    on_toggle: Box<Fn(bool)>,
}

impl Toggle {
    pub fn new(init: bool, on_toggle: Box<Fn(bool)>) -> Toggle {
        let t = Toggle {
            state: Cell::new(init),
            on_toggle,
        };

        // initialize with default
        (t.on_toggle)(init);

        t
    }

    pub fn toggle(&self) {
        self.state.set(!self.state.get());
        (self.on_toggle)(self.state.get());
    }
}

// TODO: add wrapping configuration to enums
// e.g. resolution enum wraps, texture filtering does not
pub struct Enum {
    selected: Cell<usize>,
    items: Vec<EnumItem>,
}

impl Enum {
    pub fn new(init: usize, items: Vec<EnumItem>) -> Result<Enum, Error> {
        ensure!(items.len() > 0, "Enum element must have at least one item");
        ensure!(init < items.len(), "Invalid initial item ID");

        let e = Enum {
            selected: Cell::new(init),
            items,
        };

        // initialize with the default choice
        (e.items[e.selected.get()].on_select)();

        Ok(e)
    }

    pub fn selected_name(&self) -> &str {
        self.items[self.selected.get()].name.as_str()
    }

    pub fn select_next(&self) {
        let selected = match self.selected.get() + 1 {
            s if s >= self.items.len() => 0,
            s => s,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }

    pub fn select_prev(&self) {
        let selected = match self.selected.get() {
            0 => self.items.len() - 1,
            s => s - 1,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }
}

pub struct EnumItem {
    name: String,
    on_select: Box<Fn()>,
}

impl EnumItem {
    pub fn new<S>(name: S, on_select: Box<Fn()>) -> Result<EnumItem, Error>
    where
        S: AsRef<str>,
    {
        Ok(EnumItem {
            name: name.as_ref().to_string(),
            on_select,
        })
    }
}

pub struct Slider {
    min: f32,
    max: f32,
    increment: f32,
    steps: usize,

    selected: Cell<usize>,
    on_select: Box<Fn(f32)>,
}

impl Slider {
    pub fn new(min: f32, max: f32, steps: usize, init: usize, on_select: Box<Fn(f32)>) -> Result<Slider, Error> {
        ensure!(steps > 1, "Slider must have at least 2 steps");
        ensure!(init < steps, "Invalid initial setting");
        ensure!(min < max, "Minimum setting must be less than maximum setting");

        Ok(Slider {
            min,
            max,
            increment: (max - min) / (steps - 1) as f32,
            steps,
            selected: Cell::new(init),
            on_select,
        })
    }

    pub fn increase(&self) {
        let old = self.selected.get();

        if old != self.steps - 1 {
            self.selected.set(old + 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }

    pub fn decrease(&self) {
        let old = self.selected.get();

        if old != 0 {
            self.selected.set(old - 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }
}

pub struct TextField {
    chars: Vec<char>,
    max_len: Option<usize>,
    on_update: Box<Fn(&str)>,
}

impl TextField {
    pub fn new<S>(default: Option<S>, max_len: Option<usize>) -> Result<TextField, Error> {
        unimplemented!()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::cell::RefCell;
    use std::rc::Rc;

    #[test]
    fn test_toggle() {
        let s = Rc::new(RefCell::new("false".to_string()));

        let s2 = s.clone();
        let item = Toggle::new(false, Box::new(move |state| {
            s2.replace(format!("{}", state));
        }));
        item.toggle();

        assert_eq!(*s.borrow(), "true");
    }

    #[test]
    fn test_enum() {
        let target = Rc::new(RefCell::new("null".to_string()));

        let enum_items = (0..3i32).into_iter().map(|i: i32| {
            let target_handle = target.clone();
            EnumItem::new(format!("option_{}", i), Box::new(move || {
                target_handle.replace(format!("option_{}", i));
            })).unwrap()
        }).collect();

        let e = Enum::new(0, enum_items).unwrap();
        assert_eq!(*target.borrow(), "option_0");

        // wrap under
        e.select_prev();
        assert_eq!(*target.borrow(), "option_2");

        e.select_next();
        e.select_next();
        e.select_next();
        assert_eq!(*target.borrow(), "option_2");

        // wrap over
        e.select_next();
        assert_eq!(*target.borrow(), "option_0");
    }

    #[test]
    fn test_slider() {
        let f = Rc::new(Cell::new(0.0f32));

        let f2 = f.clone();
        let item = Slider::new(
            0.0,
            10.0,
            11,
            0,
            Box::new(move |f| {
                f2.set(f);
            }),
        ).unwrap();

        // don't underflow
        item.decrease();
        assert_eq!(f.get(), 0.0);

        for i in 0..10 {
            item.increase();
            assert_eq!(f.get(), i as f32 + 1.0);
        }

        // don't overflow
        item.increase();
        assert_eq!(f.get(), 10.0);
    }
}
<commit_msg>Add TextField menu item<commit_after>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use std::cell::Cell;

use client::menu::Menu;

use failure::Error;

pub enum Item {
    Submenu(Menu),
    Action(Box<Fn()>),
    Toggle(Toggle),
    Enum,
}

pub struct Toggle {
    state: Cell<bool>,
    on_toggle: Box<Fn(bool)>,
}

impl Toggle {
    pub fn new(init: bool, on_toggle: Box<Fn(bool)>) -> Toggle {
        let t = Toggle {
            state: Cell::new(init),
            on_toggle,
        };

        // initialize with default
        (t.on_toggle)(init);

        t
    }

    pub fn toggle(&self) {
        self.state.set(!self.state.get());
        (self.on_toggle)(self.state.get());
    }
}

// TODO: add wrapping configuration to enums
// e.g. resolution enum wraps, texture filtering does not
pub struct Enum {
    selected: Cell<usize>,
    items: Vec<EnumItem>,
}

impl Enum {
    pub fn new(init: usize, items: Vec<EnumItem>) -> Result<Enum, Error> {
        ensure!(items.len() > 0, "Enum element must have at least one item");
        ensure!(init < items.len(), "Invalid initial item ID");

        let e = Enum {
            selected: Cell::new(init),
            items,
        };

        // initialize with the default choice
        (e.items[e.selected.get()].on_select)();

        Ok(e)
    }

    pub fn selected_name(&self) -> &str {
        self.items[self.selected.get()].name.as_str()
    }

    pub fn select_next(&self) {
        let selected = match self.selected.get() + 1 {
            s if s >= self.items.len() => 0,
            s => s,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }

    pub fn select_prev(&self) {
        let selected = match self.selected.get() {
            0 => self.items.len() - 1,
            s => s - 1,
        };

        self.selected.set(selected);
        (self.items[selected].on_select)();
    }
}

pub struct EnumItem {
    name: String,
    on_select: Box<Fn()>,
}

impl EnumItem {
    pub fn new<S>(name: S, on_select: Box<Fn()>) -> Result<EnumItem, Error>
    where
        S: AsRef<str>,
    {
        Ok(EnumItem {
            name: name.as_ref().to_string(),
            on_select,
        })
    }
}

pub struct Slider {
    min: f32,
    max: f32,
    increment: f32,
    steps: usize,

    selected: Cell<usize>,
    on_select: Box<Fn(f32)>,
}

impl Slider {
    pub fn new(
        min: f32,
        max: f32,
        steps: usize,
        init: usize,
        on_select: Box<Fn(f32)>,
    ) -> Result<Slider, Error> {
        ensure!(steps > 1, "Slider must have at least 2 steps");
        ensure!(init < steps, "Invalid initial setting");
        ensure!(
            min < max,
            "Minimum setting must be less than maximum setting"
        );

        Ok(Slider {
            min,
            max,
            increment: (max - min) / (steps - 1) as f32,
            steps,
            selected: Cell::new(init),
            on_select,
        })
    }

    pub fn increase(&self) {
        let old = self.selected.get();

        if old != self.steps - 1 {
            self.selected.set(old + 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }

    pub fn decrease(&self) {
        let old = self.selected.get();

        if old != 0 {
            self.selected.set(old - 1);
        }

        (self.on_select)(self.min + self.selected.get() as f32 * self.increment);
    }
}

pub struct TextField {
    chars: Vec<char>,
    max_len: Option<usize>,
    on_update: Box<Fn(&str)>,
    cursor: usize,
}

impl TextField {
    pub fn new<S>(
        default: Option<S>,
        max_len: Option<usize>,
        on_update: Box<Fn(&str)>,
    ) -> Result<TextField, Error>
    where
        S: AsRef<str>,
    {
        let chars = match default {
            Some(d) => d.as_ref().chars().collect(),
            None => Vec::new(),
        };

        let cursor = chars.len();

        Ok(TextField {
            chars,
            max_len,
            on_update,
            cursor,
        })
    }

    pub fn is_empty(&self) -> bool {
        self.chars.len() == 0
    }

    pub fn text(&self) -> String {
        self.chars.iter().collect()
    }

    pub fn len(&self) -> usize {
        self.chars.len()
    }

    pub fn set_cursor(&mut self, cursor: usize) -> Result<(), Error> {
        ensure!(cursor <= self.chars.len(), "Index out of range");

        self.cursor = cursor;

        Ok(())
    }

    pub fn home(&mut self) {
        self.cursor = 0;
    }

    pub fn end(&mut self) {
        self.cursor = self.chars.len();
    }

    pub fn cursor_right(&mut self) {
        if self.cursor < self.chars.len() {
            self.cursor += 1;
        }
    }

    pub fn cursor_left(&mut self) {
        if self.cursor > 1 {
            self.cursor -= 1;
        }
    }

    pub fn insert(&mut self, c: char) {
        if let Some(l) = self.max_len {
            if self.len() == l {
                return;
            }
        }

        self.chars.insert(self.cursor, c);
        (self.on_update)(&self.text());
    }

    pub fn backspace(&mut self) {
        if self.cursor > 1 {
            self.chars.remove(self.cursor - 1);
            (self.on_update)(&self.text());
        }
    }

    pub fn delete(&mut self) {
        if self.cursor < self.chars.len() {
            self.chars.remove(self.cursor);
            (self.on_update)(&self.text());
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::cell::RefCell;
    use std::rc::Rc;

    #[test]
    fn test_toggle() {
        let s = Rc::new(RefCell::new("false".to_string()));

        let s2 = s.clone();
        let item = Toggle::new(
            false,
            Box::new(move |state| {
                s2.replace(format!("{}", state));
            }),
        );
        item.toggle();

        assert_eq!(*s.borrow(), "true");
    }

    #[test]
    fn test_enum() {
        let target = Rc::new(RefCell::new("null".to_string()));

        let enum_items = (0..3i32)
            .into_iter()
            .map(|i: i32| {
                let target_handle = target.clone();
                EnumItem::new(
                    format!("option_{}", i),
                    Box::new(move || {
                        target_handle.replace(format!("option_{}", i));
                    }),
                )
                .unwrap()
            })
            .collect();

        let e = Enum::new(0, enum_items).unwrap();
        assert_eq!(*target.borrow(), "option_0");

        // wrap under
        e.select_prev();
        assert_eq!(*target.borrow(), "option_2");

        e.select_next();
        e.select_next();
        e.select_next();
        assert_eq!(*target.borrow(), "option_2");

        // wrap over
        e.select_next();
        assert_eq!(*target.borrow(), "option_0");
    }

    #[test]
    fn test_slider() {
        let f = Rc::new(Cell::new(0.0f32));

        let f2 = f.clone();
        let item = Slider::new(
            0.0,
            10.0,
            11,
            0,
            Box::new(move |f| {
                f2.set(f);
            }),
        )
        .unwrap();

        // don't underflow
        item.decrease();
        assert_eq!(f.get(), 0.0);

        for i in 0..10 {
            item.increase();
            assert_eq!(f.get(), i as f32 + 1.0);
        }

        // don't overflow
        item.increase();
        assert_eq!(f.get(), 10.0);
    }

    #[test]
    fn test_textfield() {
        let MAX_LEN = 10;
        let s = Rc::new(RefCell::new("before".to_owned()));
        let s2 = s.clone();

        let mut tf = TextField::new(
            Some("default"),
            Some(MAX_LEN),
            Box::new(move |x| {
                s2.replace(x.to_string());
            }),
        )
        .unwrap();

        tf.cursor_left();
        tf.backspace();
        tf.backspace();
        tf.home();
        tf.delete();
        tf.delete();
        tf.delete();
        tf.cursor_right();
        tf.insert('f');
        tf.end();
        tf.insert('e');
        tf.insert('r');

        assert_eq!(tf.text(), *s.borrow());

        for _ in 0..2 * MAX_LEN {
            tf.insert('x');
        }

        assert_eq!(tf.len(), MAX_LEN);
    }
}
<|endoftext|>


<commit_before>// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::num::{One, Zero};

pub fn Matrix2D<T:Add<T,T> + Copy + Mul<T,T> + One + Zero>(
        m11: T, m12: T,
        m21: T, m22: T,
        m31: T, m32: T)
    -> Matrix2D<T> {
    Matrix2D {
        m11: m11, m12: m12,
        m21: m21, m22: m22,
        m31: m31, m32: m32
    }
}

pub struct Matrix2D<T> {
    m11: T, m12: T,
    m21: T, m22: T,
    m31: T, m32: T
}

pub impl<T:Add<T,T> + Copy + Mul<T,T> + One + Zero> Matrix2D<T> {
    fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Matrix2D<T> {
        Matrix2D {
            m11: m11, m12: m12,
            m21: m21, m22: m22,
            m31: m31, m32: m32
        }
    }

    fn mul(&self, m: &Matrix2D<T>) -> Matrix2D<T> {
        Matrix2D(m.m11*self.m11 + m.m12*self.m21,
                 m.m11*self.m12 + m.m12*self.m22,
                 m.m21*self.m11 + m.m22*self.m21,
                 m.m21*self.m12 + m.m22*self.m22,
                 m.m31*self.m11 + m.m32*self.m21 + self.m31,
                 m.m31*self.m12 + m.m32*self.m22 + self.m32)
    }

    fn translate(&self, x: T, y: T) -> Matrix2D<T> {
        let (_0, _1) = (Zero::zero(), One::one());
        let matrix = Matrix2D::new(_1, _0,
                                   _0, _1,
                                   x, y);
        return self.mul(&matrix);
    }

    fn scale(&self, x: T, y: T) -> Matrix2D<T> {
        Matrix2D(self.m11 * x, self.m12,
                 self.m21,     self.m22 * y,
                 self.m31,     self.m32)
    }

    fn identity() -> Matrix2D<T> {
        let (_0, _1) = (Zero::zero(), One::one());
        return Matrix2D::new(_1, _0,
                             _0, _1,
                             _0, _0);
    }

    fn to_array(&self) -> [T, ..6] {
        [
            self.m11, self.m12,
            self.m21, self.m22,
            self.m31, self.m32
        ]
    }
}

<commit_msg>Remove redundant constructor in Matrix2D.<commit_after>// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use core::num::{One, Zero};

pub struct Matrix2D<T> {
    m11: T, m12: T,
    m21: T, m22: T,
    m31: T, m32: T
}

pub impl<T:Add<T,T> + Copy + Mul<T,T> + One + Zero> Matrix2D<T> {
    fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Matrix2D<T> {
        Matrix2D {
            m11: m11, m12: m12,
            m21: m21, m22: m22,
            m31: m31, m32: m32
        }
    }

    fn mul(&self, m: &Matrix2D<T>) -> Matrix2D<T> {
        Matrix2D::new(m.m11*self.m11 + m.m12*self.m21,
                      m.m11*self.m12 + m.m12*self.m22,
                      m.m21*self.m11 + m.m22*self.m21,
                      m.m21*self.m12 + m.m22*self.m22,
                      m.m31*self.m11 + m.m32*self.m21 + self.m31,
                      m.m31*self.m12 + m.m32*self.m22 + self.m32)
    }

    fn translate(&self, x: T, y: T) -> Matrix2D<T> {
         let (_0, _1) = (Zero::zero(), One::one());
         let matrix = Matrix2D::new(_1, _0,
                                    _0, _1,
                                    x, y);
        return self.mul(&matrix);
    }

    fn scale(&self, x: T, y: T) -> Matrix2D<T> {
        Matrix2D::new(self.m11 * x, self.m12,
                      self.m21,     self.m22 * y,
                      self.m31,     self.m32)
    }

    fn identity() -> Matrix2D<T> {
        let (_0, _1) = (Zero::zero(), One::one());
        return Matrix2D::new(_1, _0,
                             _0, _1,
                             _0, _0);
    }

    fn to_array(&self) -> [T, ..6] {
        [
            self.m11, self.m12,
            self.m21, self.m22,
            self.m31, self.m32
        ]
    }

}

<|endoftext|>