<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 ping<W: Write>(mut sink: W, arg: &str) -> std::io::Result<()> {
    write!(sink, "PING {}", 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 a 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
}

pub 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);
    }
}
<commit_msg>Fix PING msg format<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 ping<W: Write>(mut sink: W, arg: &str) -> std::io::Result<()> {
    write!(sink, "PING {}\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 a 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
}

pub 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>// (c) 2016-2017 Productize SPRL <joost@productize.be>

use std::fmt;
use std::result;
use std::str::FromStr;
use std::mem;

use Result;

/// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    /// convert self into a Sexp
    fn into_sexp(&self) -> Sexp;
}

impl Into<Sexp> for Vec<Sexp> {
    fn into(self: Vec<Sexp>) -> Sexp {
        Sexp::List(self)
    }
}

impl Into<Sexp> for String {
    fn into(self: String) -> Sexp {
        Sexp::String(self)
    }
}

impl<'a> From<&'a str> for Sexp {
    fn from(t: &str) -> Sexp {
        Sexp::String(t.into())
    }
}

impl<'a> From<&'a String> for Sexp {
    fn from(t: &String) -> Sexp {
        Sexp::String(t.clone())
    }
}

impl Into<Sexp> for i64 {
    fn into(self: i64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl Into<Sexp> for f64 {
    fn into(self: f64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl<'a> From<(&'a str, Sexp)> for Sexp {
    fn from(kv: (&str, Sexp)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(value);
        v.into()
    }
}

impl<'a, T: fmt::Display> From<(&'a str, &'a T)> for Sexp {
    fn from(kv: (&str, &T)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }
}

impl<'a> From<&'a Sexp> for Result<i64> {
    fn from(s:&Sexp) -> Result<i64> {
        s.i()
    }
}

impl Into<Result<f64> > for Sexp {
    fn into(self:Sexp) -> Result<f64> {
        self.f()
    }
}

impl Into<Result<String> > for Sexp {
    fn into(self:Sexp) -> Result<String> {
        Ok(self.string()?.clone())
    }
}

/// a symbolic-expression
/// Can be a string or a list or nothing
#[derive(Debug, Clone, PartialEq)]
pub enum Sexp {
    /// plain String symbolic-expression
    String(String),
    /// list symbolic-expression
    List(Vec<Sexp>),
    /// empty, trivial symbolic-expression
    Empty,
}

// Following the KiCad file formats specification chapter 4.4 - Identifiers and Strings:
// A string may not contain an actual newline or carriage return,
// but it may use an escape sequence to encode a // newline, such as \n.
// If a string has any of the following conditions, then it must be quoted
// with a leading and trailing double quote
// character, otherwise it is acceptable to not quote the string:
// 1. has one or more of the following 4 separator bytes: ASCII space,
// tab, '(', or ')'.
// 2. has one or more of the following bytes: '%', '{', or '}'.
// 3. has a length of zero bytes, and you need a place holder for the field,
//  then use "".
// 4. includes a byte of '-', and this byte is not in the first position of
// the string.
//

// Joost remark: kicad no longer seems to follow Rule 4.
// so we don't either

/// encode a string according to the guidelines given by Kicad
pub fn encode_string(s: &str) -> String {
    // fn rule_4(s:&str) -> bool {
    // s.contains('-') && s.len() > 1 && s.as_bytes()[0] != 45
    // }
    if s.contains('(') || s.contains(' ') || s.contains(')') || s.contains('\t') ||
       s.contains('{') || s.contains('}') || s.contains('}') || s.contains('%') ||
       s.is_empty()
    // || rule_4(s)
    {
        format!("\"{}\"", s)
    } else {
        String::from(s)
    }
}

impl Sexp {
    /// create an empty symbolic-expression
    #[deprecated(since="4.0.0", note="please use `Sexp::default()` instead")]
    pub fn new_empty() -> Sexp {
        Sexp::Empty
    }

    /// create a String type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_string<T>(s: T) -> Sexp
        where T: fmt::Display
    {
        format!("{}", s).into()
    }

    /// create a list type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_list(v: Vec<Sexp>) -> Sexp {
        Sexp::List(v)
    }

    /// create an empty list type symbolic-expression
    pub fn start(name: &str) -> Sexp {
        let mut v = vec![];
        v.push(Sexp::String(name.into()));
        Sexp::List(v)
    }

    /// push an element in a list
    pub fn push<T: Into<Sexp>>(&mut self, element: T) {
        match *self {
            Sexp::List(ref mut v) => v.push(element.into()),
            _ => panic!("Only use push on lists!"),
        }
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, the remainder is filled in via the provided
    /// fill function
    pub fn new_named_list<F>(name: &str, fill: F) -> Sexp
        where F: Fn(&mut Vec<Sexp>)
    {
        let mut v = vec![];
        v.push(name.into());
        fill(&mut v);
        Sexp::List(v)
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_named<T>(name: &str, value: T) -> Sexp
        where T: fmt::Display
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, and the second is another symbolic-expression
    /// created via the IntoSexp trait
    #[deprecated(since="4.0.5", note="please use `.into()` on a tuple instead")]
    pub fn new_named_sexp<T>(name: &str, value: &T) -> Sexp
        where T: IntoSexp
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(value.into_sexp());
        Sexp::List(v)
    }

    /// if the expression is a list, take out the list and swap it with Empty
    pub fn take_list(&mut self) -> Result<Vec<Sexp>> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::List(v) => Ok(v),
            _ => Err(format!("Not a list: {}", e).into()),
        }
    }

    /// if the expression is a string, take it out and swap it with Empty
    pub fn take_string(&mut self) -> Result<String> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::String(s) => Ok(s),
            _ => Err(format!("Not a string: {}", e).into()),
        }
    }

    /// create a symbolic-expression via the IntoSexp trait
    pub fn from<T: IntoSexp>(t: &T) -> Sexp {
        t.into_sexp()
    }

    /// access the symbolic-expression as if it is a List
    pub fn list(&self) -> Result<&Vec<Sexp>> {
        match *self {
            Sexp::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self).into()),
        }
    }

    /// access the symbolic-expression as if it is a String
    pub fn string(&self) -> Result<&String> {
        match *self {
            Sexp::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self).into()),
        }
    }

    /// is this expression a string
    pub fn is_string(&self) -> bool {
        match *self {
            Sexp::String(_) => true,
            _ => false,
        }
    }

    /// is this expression a list
    pub fn is_list(&self) -> bool {
        match *self {
            Sexp::List(_) => true,
            _ => false,
        }
    }

    /// access the symbolic-expression as if it is a String
    /// that is a f64
    pub fn f(&self) -> Result<f64> {
        let s = self.string()?;
        let f = f64::from_str(s)?;
        Ok(f)
    }

    /// access the symbolic-expression as if it is a String
    /// that is an i64
    pub fn i(&self) -> Result<i64> {
        let s = self.string()?;
        let i = i64::from_str(s)?;
        Ok(i)
    }

    /// access the symbolic-expression as if it is a List
    /// assuming the first element is a String and return that
    pub fn list_name(&self) -> Result<&String> {
        let l = self.list()?;
        let l = &l[..];
        let a = l[0].string()?;
        Ok(a)
    }

    /// access the symbolic-expression as if it is a named List
    /// where the name is provided and returns the remaining elements
    /// after the name as a slice
    pub fn slice_atom(&self, s: &str) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list {} doesn't start with {}, but with {}", self, s, st).into());
        };
        Ok(&v[1..])
    }

    /// access the symbolic-expression as if it is a named List
    /// with two elements where the name is provided and returns
    /// the next element in the list
    pub fn named_value(&self, s: &str) -> Result<&Sexp> {
        let v = self.list()?;
        if v.len() != 2 {
            return Err(format!("list {} is not a named_value", s).into());
        }
        let l = self.slice_atom(s)?;
        Ok(&l[0])
    }

    /// as named_value but converted to i64
    pub fn named_value_i(&self, s: &str) -> Result<i64> {
        self.named_value(s)?.i()
    }

    /// as named_value but converted to f64
    pub fn named_value_f(&self, s: &str) -> Result<f64> {
        self.named_value(s)?.f()
    }

    /// as named_value but converted to String
    pub fn named_value_string(&self, s: &str) -> Result<&String> {
        self.named_value(s)?.string()
    }

    /// get the symbolic-expression as a list which starts
    /// with a string that indicates the name and has num more
    /// elements, returns those elements
    pub fn slice_atom_num(&self, s: &str, num: usize) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st).into());
        };
        if v.len() != (num + 1) {
            return Err(format!("list ({}) doesn't have {} elements but {}",
                               s,
                               num,
                               v.len() - 1)
                .into());
        }
        Ok(&v[1..])
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
        match *self {
            Sexp::String(ref s) => write!(f, "{}", encode_string(s)),
            Sexp::List(ref v) => {
                write!(f, "(")?;
                let l = v.len();
                for (i, x) in v.iter().enumerate() {
                    if i < l - 1 {
                        write!(f, "{} ", x)?;
                    } else {
                        write!(f, "{}", x)?;
                    }
                }
                write!(f, ")")
            }
            Sexp::Empty => Ok(()),
        }
    }
}

impl Default for Sexp {
    fn default() -> Sexp {
        Sexp::Empty
    }
}
<commit_msg>one more from test<commit_after>// (c) 2016-2017 Productize SPRL <joost@productize.be>

use std::fmt;
use std::result;
use std::str::FromStr;
use std::mem;

use Result;

/// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    /// convert self into a Sexp
    fn into_sexp(&self) -> Sexp;
}

impl Into<Sexp> for Vec<Sexp> {
    fn into(self: Vec<Sexp>) -> Sexp {
        Sexp::List(self)
    }
}

impl Into<Sexp> for String {
    fn into(self: String) -> Sexp {
        Sexp::String(self)
    }
}

impl<'a> From<&'a str> for Sexp {
    fn from(t: &str) -> Sexp {
        Sexp::String(t.into())
    }
}

impl<'a> From<&'a String> for Sexp {
    fn from(t: &String) -> Sexp {
        Sexp::String(t.clone())
    }
}

impl Into<Sexp> for i64 {
    fn into(self: i64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl Into<Sexp> for f64 {
    fn into(self: f64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl<'a> From<(&'a str, Sexp)> for Sexp {
    fn from(kv: (&str, Sexp)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(value);
        v.into()
    }
}

impl<'a, T: fmt::Display> From<(&'a str, &'a T)> for Sexp {
    fn from(kv: (&str, &T)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }
}

impl<'a> From<&'a Sexp> for Result<f64> {
    fn from(s:&Sexp) -> Result<f64> {
        s.f()
    }
}

/// a symbolic-expression
/// Can be a string or a list or nothing
#[derive(Debug, Clone, PartialEq)]
pub enum Sexp {
    /// plain String symbolic-expression
    String(String),
    /// list symbolic-expression
    List(Vec<Sexp>),
    /// empty, trivial symbolic-expression
    Empty,
}

// Following the KiCad file formats specification chapter 4.4 - Identifiers and Strings:
// A string may not contain an actual newline or carriage return,
// but it may use an escape sequence to encode a // newline, such as \n.
// If a string has any of the following conditions, then it must be quoted
// with a leading and trailing double quote
// character, otherwise it is acceptable to not quote the string:
// 1. has one or more of the following 4 separator bytes: ASCII space,
// tab, '(', or ')'.
// 2. has one or more of the following bytes: '%', '{', or '}'.
// 3. has a length of zero bytes, and you need a place holder for the field,
//  then use "".
// 4. includes a byte of '-', and this byte is not in the first position of
// the string.
//

// Joost remark: kicad no longer seems to follow Rule 4.
// so we don't either

/// encode a string according to the guidelines given by Kicad
pub fn encode_string(s: &str) -> String {
    // fn rule_4(s:&str) -> bool {
    // s.contains('-') && s.len() > 1 && s.as_bytes()[0] != 45
    // }
    if s.contains('(') || s.contains(' ') || s.contains(')') || s.contains('\t') ||
       s.contains('{') || s.contains('}') || s.contains('}') || s.contains('%') ||
       s.is_empty()
    // || rule_4(s)
    {
        format!("\"{}\"", s)
    } else {
        String::from(s)
    }
}

impl Sexp {
    /// create an empty symbolic-expression
    #[deprecated(since="4.0.0", note="please use `Sexp::default()` instead")]
    pub fn new_empty() -> Sexp {
        Sexp::Empty
    }

    /// create a String type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_string<T>(s: T) -> Sexp
        where T: fmt::Display
    {
        format!("{}", s).into()
    }

    /// create a list type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_list(v: Vec<Sexp>) -> Sexp {
        Sexp::List(v)
    }

    /// create an empty list type symbolic-expression
    pub fn start(name: &str) -> Sexp {
        let mut v = vec![];
        v.push(Sexp::String(name.into()));
        Sexp::List(v)
    }

    /// push an element in a list
    pub fn push<T: Into<Sexp>>(&mut self, element: T) {
        match *self {
            Sexp::List(ref mut v) => v.push(element.into()),
            _ => panic!("Only use push on lists!"),
        }
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, the remainder is filled in via the provided
    /// fill function
    pub fn new_named_list<F>(name: &str, fill: F) -> Sexp
        where F: Fn(&mut Vec<Sexp>)
    {
        let mut v = vec![];
        v.push(name.into());
        fill(&mut v);
        Sexp::List(v)
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_named<T>(name: &str, value: T) -> Sexp
        where T: fmt::Display
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, and the second is another symbolic-expression
    /// created via the IntoSexp trait
    #[deprecated(since="4.0.5", note="please use `.into()` on a tuple instead")]
    pub fn new_named_sexp<T>(name: &str, value: &T) -> Sexp
        where T: IntoSexp
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(value.into_sexp());
        Sexp::List(v)
    }

    /// if the expression is a list, take out the list and swap it with Empty
    pub fn take_list(&mut self) -> Result<Vec<Sexp>> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::List(v) => Ok(v),
            _ => Err(format!("Not a list: {}", e).into()),
        }
    }

    /// if the expression is a string, take it out and swap it with Empty
    pub fn take_string(&mut self) -> Result<String> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::String(s) => Ok(s),
            _ => Err(format!("Not a string: {}", e).into()),
        }
    }

    /// create a symbolic-expression via the IntoSexp trait
    pub fn from<T: IntoSexp>(t: &T) -> Sexp {
        t.into_sexp()
    }

    /// access the symbolic-expression as if it is a List
    pub fn list(&self) -> Result<&Vec<Sexp>> {
        match *self {
            Sexp::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self).into()),
        }
    }

    /// access the symbolic-expression as if it is a String
    pub fn string(&self) -> Result<&String> {
        match *self {
            Sexp::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self).into()),
        }
    }

    /// is this expression a string
    pub fn is_string(&self) -> bool {
        match *self {
            Sexp::String(_) => true,
            _ => false,
        }
    }

    /// is this expression a list
    pub fn is_list(&self) -> bool {
        match *self {
            Sexp::List(_) => true,
            _ => false,
        }
    }

    /// access the symbolic-expression as if it is a String
    /// that is a f64
    pub fn f(&self) -> Result<f64> {
        let s = self.string()?;
        let f = f64::from_str(s)?;
        Ok(f)
    }

    /// access the symbolic-expression as if it is a String
    /// that is an i64
    pub fn i(&self) -> Result<i64> {
        let s = self.string()?;
        let i = i64::from_str(s)?;
        Ok(i)
    }

    /// access the symbolic-expression as if it is a List
    /// assuming the first element is a String and return that
    pub fn list_name(&self) -> Result<&String> {
        let l = self.list()?;
        let l = &l[..];
        let a = l[0].string()?;
        Ok(a)
    }

    /// access the symbolic-expression as if it is a named List
    /// where the name is provided and returns the remaining elements
    /// after the name as a slice
    pub fn slice_atom(&self, s: &str) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list {} doesn't start with {}, but with {}", self, s, st).into());
        };
        Ok(&v[1..])
    }

    /// access the symbolic-expression as if it is a named List
    /// with two elements where the name is provided and returns
    /// the next element in the list
    pub fn named_value(&self, s: &str) -> Result<&Sexp> {
        let v = self.list()?;
        if v.len() != 2 {
            return Err(format!("list {} is not a named_value", s).into());
        }
        let l = self.slice_atom(s)?;
        Ok(&l[0])
    }

    /// as named_value but converted to i64
    pub fn named_value_i(&self, s: &str) -> Result<i64> {
        self.named_value(s)?.i()
    }

    /// as named_value but converted to f64
    pub fn named_value_f(&self, s: &str) -> Result<f64> {
        self.named_value(s)?.f()
    }

    /// as named_value but converted to String
    pub fn named_value_string(&self, s: &str) -> Result<&String> {
        self.named_value(s)?.string()
    }

    /// get the symbolic-expression as a list which starts
    /// with a string that indicates the name and has num more
    /// elements, returns those elements
    pub fn slice_atom_num(&self, s: &str, num: usize) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st).into());
        };
        if v.len() != (num + 1) {
            return Err(format!("list ({}) doesn't have {} elements but {}",
                               s,
                               num,
                               v.len() - 1)
                .into());
        }
        Ok(&v[1..])
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
        match *self {
            Sexp::String(ref s) => write!(f, "{}", encode_string(s)),
            Sexp::List(ref v) => {
                write!(f, "(")?;
                let l = v.len();
                for (i, x) in v.iter().enumerate() {
                    if i < l - 1 {
                        write!(f, "{} ", x)?;
                    } else {
                        write!(f, "{}", x)?;
                    }
                }
                write!(f, ")")
            }
            Sexp::Empty => Ok(()),
        }
    }
}

impl Default for Sexp {
    fn default() -> Sexp {
        Sexp::Empty
    }
}
<|endoftext|>


<commit_before>// (c) 2016-2017 Productize SPRL <joost@productize.be>

use std::fmt;
use std::result;
use std::str::FromStr;
use std::mem;

use Result;

/// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    /// convert self into a Sexp
    fn into_sexp(&self) -> Sexp;
}

impl Into<Sexp> for Vec<Sexp> {
    fn into(self: Vec<Sexp>) -> Sexp {
        Sexp::List(self)
    }
}

impl Into<Sexp> for String {
    fn into(self: String) -> Sexp {
        Sexp::String(self)
    }
}

impl<'a> From<&'a str> for Sexp {
    fn from(t: &str) -> Sexp {
        Sexp::String(t.into())
    }
}

impl<'a> From<&'a String> for Sexp {
    fn from(t: &String) -> Sexp {
        Sexp::String(t.clone())
    }
}

impl Into<Sexp> for i64 {
    fn into(self: i64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl Into<Sexp> for f64 {
    fn into(self: f64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl<'a> From<(&'a str, Sexp)> for Sexp {
    fn from(kv: (&str, Sexp)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(value);
        v.into()
    }
}

impl<'a, T: fmt::Display> From<(&'a str, &'a T)> for Sexp {
    fn from(kv: (&str, &T)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }
}

impl Into<Result<i64> > for Sexp {
    fn into(self:Sexp) -> Result<i64> {
        self.i()
    }
}

impl Into<Result<f64> > for Sexp {
    fn into(self:Sexp) -> Result<f64> {
        self.f()
    }
}

impl Into<Result<String> > for Sexp {
    fn into(self:Sexp) -> Result<String> {
        Ok(self.string()?.clone())
    }
}

/// a symbolic-expression
/// Can be a string or a list or nothing
#[derive(Debug, Clone, PartialEq)]
pub enum Sexp {
    /// plain String symbolic-expression
    String(String),
    /// list symbolic-expression
    List(Vec<Sexp>),
    /// empty, trivial symbolic-expression
    Empty,
}

// Following the KiCad file formats specification chapter 4.4 - Identifiers and Strings:
// A string may not contain an actual newline or carriage return,
// but it may use an escape sequence to encode a // newline, such as \n.
// If a string has any of the following conditions, then it must be quoted
// with a leading and trailing double quote
// character, otherwise it is acceptable to not quote the string:
// 1. has one or more of the following 4 separator bytes: ASCII space,
// tab, '(', or ')'.
// 2. has one or more of the following bytes: '%', '{', or '}'.
// 3. has a length of zero bytes, and you need a place holder for the field,
//  then use "".
// 4. includes a byte of '-', and this byte is not in the first position of
// the string.
//

// Joost remark: kicad no longer seems to follow Rule 4.
// so we don't either

/// encode a string according to the guidelines given by Kicad
pub fn encode_string(s: &str) -> String {
    // fn rule_4(s:&str) -> bool {
    // s.contains('-') && s.len() > 1 && s.as_bytes()[0] != 45
    // }
    if s.contains('(') || s.contains(' ') || s.contains(')') || s.contains('\t') ||
       s.contains('{') || s.contains('}') || s.contains('}') || s.contains('%') ||
       s.is_empty()
    // || rule_4(s)
    {
        format!("\"{}\"", s)
    } else {
        String::from(s)
    }
}

impl Sexp {
    /// create an empty symbolic-expression
    #[deprecated(since="4.0.0", note="please use `Sexp::default()` instead")]
    pub fn new_empty() -> Sexp {
        Sexp::Empty
    }

    /// create a String type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_string<T>(s: T) -> Sexp
        where T: fmt::Display
    {
        format!("{}", s).into()
    }

    /// create a list type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_list(v: Vec<Sexp>) -> Sexp {
        Sexp::List(v)
    }

    /// create an empty list type symbolic-expression
    pub fn start(name: &str) -> Sexp {
        let mut v = vec![];
        v.push(Sexp::String(name.into()));
        Sexp::List(v)
    }

    /// push an element in a list
    pub fn push<T: Into<Sexp>>(&mut self, element: T) {
        match *self {
            Sexp::List(ref mut v) => v.push(element.into()),
            _ => panic!("Only use push on lists!"),
        }
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, the remainder is filled in via the provided
    /// fill function
    pub fn new_named_list<F>(name: &str, fill: F) -> Sexp
        where F: Fn(&mut Vec<Sexp>)
    {
        let mut v = vec![];
        v.push(name.into());
        fill(&mut v);
        Sexp::List(v)
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_named<T>(name: &str, value: T) -> Sexp
        where T: fmt::Display
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, and the second is another symbolic-expression
    /// created via the IntoSexp trait
    #[deprecated(since="4.0.5", note="please use `.into()` on a tuple instead")]
    pub fn new_named_sexp<T>(name: &str, value: &T) -> Sexp
        where T: IntoSexp
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(value.into_sexp());
        Sexp::List(v)
    }

    /// if the expression is a list, take out the list and swap it with Empty
    pub fn take_list(&mut self) -> Result<Vec<Sexp>> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::List(v) => Ok(v),
            _ => Err(format!("Not a list: {}", e).into()),
        }
    }

    /// if the expression is a string, take it out and swap it with Empty
    pub fn take_string(&mut self) -> Result<String> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::String(s) => Ok(s),
            _ => Err(format!("Not a string: {}", e).into()),
        }
    }

    /// create a symbolic-expression via the IntoSexp trait
    pub fn from<T: IntoSexp>(t: &T) -> Sexp {
        t.into_sexp()
    }

    /// access the symbolic-expression as if it is a List
    pub fn list(&self) -> Result<&Vec<Sexp>> {
        match *self {
            Sexp::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self).into()),
        }
    }

    /// access the symbolic-expression as if it is a String
    pub fn string(&self) -> Result<&String> {
        match *self {
            Sexp::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self).into()),
        }
    }

    /// is this expression a string
    pub fn is_string(&self) -> bool {
        match *self {
            Sexp::String(_) => true,
            _ => false,
        }
    }

    /// is this expression a list
    pub fn is_list(&self) -> bool {
        match *self {
            Sexp::List(_) => true,
            _ => false,
        }
    }

    /// access the symbolic-expression as if it is a String
    /// that is a f64
    pub fn f(&self) -> Result<f64> {
        let s = self.string()?;
        let f = f64::from_str(s)?;
        Ok(f)
    }

    /// access the symbolic-expression as if it is a String
    /// that is an i64
    pub fn i(&self) -> Result<i64> {
        let s = self.string()?;
        let i = i64::from_str(s)?;
        Ok(i)
    }

    /// access the symbolic-expression as if it is a List
    /// assuming the first element is a String and return that
    pub fn list_name(&self) -> Result<&String> {
        let l = self.list()?;
        let l = &l[..];
        let a = l[0].string()?;
        Ok(a)
    }

    /// access the symbolic-expression as if it is a named List
    /// where the name is provided and returns the remaining elements
    /// after the name as a slice
    pub fn slice_atom(&self, s: &str) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list {} doesn't start with {}, but with {}", self, s, st).into());
        };
        Ok(&v[1..])
    }

    /// access the symbolic-expression as if it is a named List
    /// with two elements where the name is provided and returns
    /// the next element in the list
    pub fn named_value(&self, s: &str) -> Result<&Sexp> {
        let v = self.list()?;
        if v.len() != 2 {
            return Err(format!("list {} is not a named_value", s).into());
        }
        let l = self.slice_atom(s)?;
        Ok(&l[0])
    }

    /// as named_value but converted to i64
    pub fn named_value_i(&self, s: &str) -> Result<i64> {
        self.named_value(s)?.i()
    }

    /// as named_value but converted to f64
    pub fn named_value_f(&self, s: &str) -> Result<f64> {
        self.named_value(s)?.f()
    }

    /// as named_value but converted to String
    pub fn named_value_string(&self, s: &str) -> Result<&String> {
        self.named_value(s)?.string()
    }

    /// get the symbolic-expression as a list which starts
    /// with a string that indicates the name and has num more
    /// elements, returns those elements
    pub fn slice_atom_num(&self, s: &str, num: usize) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st).into());
        };
        if v.len() != (num + 1) {
            return Err(format!("list ({}) doesn't have {} elements but {}",
                               s,
                               num,
                               v.len() - 1)
                .into());
        }
        Ok(&v[1..])
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
        match *self {
            Sexp::String(ref s) => write!(f, "{}", encode_string(s)),
            Sexp::List(ref v) => {
                write!(f, "(")?;
                let l = v.len();
                for (i, x) in v.iter().enumerate() {
                    if i < l - 1 {
                        write!(f, "{} ", x)?;
                    } else {
                        write!(f, "{}", x)?;
                    }
                }
                write!(f, ")")
            }
            Sexp::Empty => Ok(()),
        }
    }
}

impl Default for Sexp {
    fn default() -> Sexp {
        Sexp::Empty
    }
}
<commit_msg>from test<commit_after>// (c) 2016-2017 Productize SPRL <joost@productize.be>

use std::fmt;
use std::result;
use std::str::FromStr;
use std::mem;

use Result;

/// like Into trait but works from a ref avoiding consumption or expensive clone
pub trait IntoSexp {
    /// convert self into a Sexp
    fn into_sexp(&self) -> Sexp;
}

impl Into<Sexp> for Vec<Sexp> {
    fn into(self: Vec<Sexp>) -> Sexp {
        Sexp::List(self)
    }
}

impl Into<Sexp> for String {
    fn into(self: String) -> Sexp {
        Sexp::String(self)
    }
}

impl<'a> From<&'a str> for Sexp {
    fn from(t: &str) -> Sexp {
        Sexp::String(t.into())
    }
}

impl<'a> From<&'a String> for Sexp {
    fn from(t: &String) -> Sexp {
        Sexp::String(t.clone())
    }
}

impl Into<Sexp> for i64 {
    fn into(self: i64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl Into<Sexp> for f64 {
    fn into(self: f64) -> Sexp {
        Sexp::String(format!("{}", self))
    }
}

impl<'a> From<(&'a str, Sexp)> for Sexp {
    fn from(kv: (&str, Sexp)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(value);
        v.into()
    }
}

impl<'a, T: fmt::Display> From<(&'a str, &'a T)> for Sexp {
    fn from(kv: (&str, &T)) -> Sexp {
        let (name, value) = kv;
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }
}

impl<'a> From<&'a Sexp> for Result<i64> {
    fn from(s:&Sexp) -> Result<i64> {
        s.i()
    }
}

impl Into<Result<f64> > for Sexp {
    fn into(self:Sexp) -> Result<f64> {
        self.f()
    }
}

impl Into<Result<String> > for Sexp {
    fn into(self:Sexp) -> Result<String> {
        Ok(self.string()?.clone())
    }
}

/// a symbolic-expression
/// Can be a string or a list or nothing
#[derive(Debug, Clone, PartialEq)]
pub enum Sexp {
    /// plain String symbolic-expression
    String(String),
    /// list symbolic-expression
    List(Vec<Sexp>),
    /// empty, trivial symbolic-expression
    Empty,
}

// Following the KiCad file formats specification chapter 4.4 - Identifiers and Strings:
// A string may not contain an actual newline or carriage return,
// but it may use an escape sequence to encode a // newline, such as \n.
// If a string has any of the following conditions, then it must be quoted
// with a leading and trailing double quote
// character, otherwise it is acceptable to not quote the string:
// 1. has one or more of the following 4 separator bytes: ASCII space,
// tab, '(', or ')'.
// 2. has one or more of the following bytes: '%', '{', or '}'.
// 3. has a length of zero bytes, and you need a place holder for the field,
//  then use "".
// 4. includes a byte of '-', and this byte is not in the first position of
// the string.
//

// Joost remark: kicad no longer seems to follow Rule 4.
// so we don't either

/// encode a string according to the guidelines given by Kicad
pub fn encode_string(s: &str) -> String {
    // fn rule_4(s:&str) -> bool {
    // s.contains('-') && s.len() > 1 && s.as_bytes()[0] != 45
    // }
    if s.contains('(') || s.contains(' ') || s.contains(')') || s.contains('\t') ||
       s.contains('{') || s.contains('}') || s.contains('}') || s.contains('%') ||
       s.is_empty()
    // || rule_4(s)
    {
        format!("\"{}\"", s)
    } else {
        String::from(s)
    }
}

impl Sexp {
    /// create an empty symbolic-expression
    #[deprecated(since="4.0.0", note="please use `Sexp::default()` instead")]
    pub fn new_empty() -> Sexp {
        Sexp::Empty
    }

    /// create a String type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_string<T>(s: T) -> Sexp
        where T: fmt::Display
    {
        format!("{}", s).into()
    }

    /// create a list type symbolic-expression
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_list(v: Vec<Sexp>) -> Sexp {
        Sexp::List(v)
    }

    /// create an empty list type symbolic-expression
    pub fn start(name: &str) -> Sexp {
        let mut v = vec![];
        v.push(Sexp::String(name.into()));
        Sexp::List(v)
    }

    /// push an element in a list
    pub fn push<T: Into<Sexp>>(&mut self, element: T) {
        match *self {
            Sexp::List(ref mut v) => v.push(element.into()),
            _ => panic!("Only use push on lists!"),
        }
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, the remainder is filled in via the provided
    /// fill function
    pub fn new_named_list<F>(name: &str, fill: F) -> Sexp
        where F: Fn(&mut Vec<Sexp>)
    {
        let mut v = vec![];
        v.push(name.into());
        fill(&mut v);
        Sexp::List(v)
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name
    #[deprecated(since="4.0.3", note="please use `.into()` instead")]
    pub fn new_named<T>(name: &str, value: T) -> Sexp
        where T: fmt::Display
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(format!("{}", value).into());
        v.into()
    }

    /// create a list type symbolic-expression where
    /// the first element of the list is a string that indicates
    /// the name, and the second is another symbolic-expression
    /// created via the IntoSexp trait
    #[deprecated(since="4.0.5", note="please use `.into()` on a tuple instead")]
    pub fn new_named_sexp<T>(name: &str, value: &T) -> Sexp
        where T: IntoSexp
    {
        let mut v = vec![];
        v.push(name.into());
        v.push(value.into_sexp());
        Sexp::List(v)
    }

    /// if the expression is a list, take out the list and swap it with Empty
    pub fn take_list(&mut self) -> Result<Vec<Sexp>> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::List(v) => Ok(v),
            _ => Err(format!("Not a list: {}", e).into()),
        }
    }

    /// if the expression is a string, take it out and swap it with Empty
    pub fn take_string(&mut self) -> Result<String> {
        let mut e = Sexp::Empty;
        mem::swap(&mut e, self);
        match e {
            Sexp::String(s) => Ok(s),
            _ => Err(format!("Not a string: {}", e).into()),
        }
    }

    /// create a symbolic-expression via the IntoSexp trait
    pub fn from<T: IntoSexp>(t: &T) -> Sexp {
        t.into_sexp()
    }

    /// access the symbolic-expression as if it is a List
    pub fn list(&self) -> Result<&Vec<Sexp>> {
        match *self {
            Sexp::List(ref v) => Ok(v),
            _ => Err(format!("not a list: {}", self).into()),
        }
    }

    /// access the symbolic-expression as if it is a String
    pub fn string(&self) -> Result<&String> {
        match *self {
            Sexp::String(ref s) => Ok(s),
            _ => Err(format!("not a string: {}", self).into()),
        }
    }

    /// is this expression a string
    pub fn is_string(&self) -> bool {
        match *self {
            Sexp::String(_) => true,
            _ => false,
        }
    }

    /// is this expression a list
    pub fn is_list(&self) -> bool {
        match *self {
            Sexp::List(_) => true,
            _ => false,
        }
    }

    /// access the symbolic-expression as if it is a String
    /// that is a f64
    pub fn f(&self) -> Result<f64> {
        let s = self.string()?;
        let f = f64::from_str(s)?;
        Ok(f)
    }

    /// access the symbolic-expression as if it is a String
    /// that is an i64
    pub fn i(&self) -> Result<i64> {
        let s = self.string()?;
        let i = i64::from_str(s)?;
        Ok(i)
    }

    /// access the symbolic-expression as if it is a List
    /// assuming the first element is a String and return that
    pub fn list_name(&self) -> Result<&String> {
        let l = self.list()?;
        let l = &l[..];
        let a = l[0].string()?;
        Ok(a)
    }

    /// access the symbolic-expression as if it is a named List
    /// where the name is provided and returns the remaining elements
    /// after the name as a slice
    pub fn slice_atom(&self, s: &str) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list {} doesn't start with {}, but with {}", self, s, st).into());
        };
        Ok(&v[1..])
    }

    /// access the symbolic-expression as if it is a named List
    /// with two elements where the name is provided and returns
    /// the next element in the list
    pub fn named_value(&self, s: &str) -> Result<&Sexp> {
        let v = self.list()?;
        if v.len() != 2 {
            return Err(format!("list {} is not a named_value", s).into());
        }
        let l = self.slice_atom(s)?;
        Ok(&l[0])
    }

    /// as named_value but converted to i64
    pub fn named_value_i(&self, s: &str) -> Result<i64> {
        self.named_value(s)?.i()
    }

    /// as named_value but converted to f64
    pub fn named_value_f(&self, s: &str) -> Result<f64> {
        self.named_value(s)?.f()
    }

    /// as named_value but converted to String
    pub fn named_value_string(&self, s: &str) -> Result<&String> {
        self.named_value(s)?.string()
    }

    /// get the symbolic-expression as a list which starts
    /// with a string that indicates the name and has num more
    /// elements, returns those elements
    pub fn slice_atom_num(&self, s: &str, num: usize) -> Result<&[Sexp]> {
        let v = self.list()?;
        let v2 = &v[..];
        let st = v2[0].string()?;
        if st != s {
            return Err(format!("list doesn't start with {}, but with {}", s, st).into());
        };
        if v.len() != (num + 1) {
            return Err(format!("list ({}) doesn't have {} elements but {}",
                               s,
                               num,
                               v.len() - 1)
                .into());
        }
        Ok(&v[1..])
    }
}

impl fmt::Display for Sexp {
    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
        match *self {
            Sexp::String(ref s) => write!(f, "{}", encode_string(s)),
            Sexp::List(ref v) => {
                write!(f, "(")?;
                let l = v.len();
                for (i, x) in v.iter().enumerate() {
                    if i < l - 1 {
                        write!(f, "{} ", x)?;
                    } else {
                        write!(f, "{}", x)?;
                    }
                }
                write!(f, ")")
            }
            Sexp::Empty => Ok(()),
        }
    }
}

impl Default for Sexp {
    fn default() -> Sexp {
        Sexp::Empty
    }
}
<|endoftext|>


<commit_before>use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use pg::rows::Row;

use app::App;
use http;
use schema::*;
use util::{CargoResult, human};
use {Model, User, Crate};

#[derive(Insertable, Associations, Identifiable)]
#[belongs_to(Crate)]
#[belongs_to(User, foreign_key="owner_id")]
#[belongs_to(Team, foreign_key="owner_id")]
#[table_name="crate_owners"]
#[primary_key(crate_id, owner_id, owner_kind)]
pub struct CrateOwner {
    pub crate_id: i32,
    pub owner_id: i32,
    pub created_by: i32,
    pub owner_kind: i32,
}

#[repr(u32)]
pub enum OwnerKind {
    User = 0,
    Team = 1,
}

/// Unifies the notion of a User or a Team.
pub enum Owner {
    User(User),
    Team(Team),
}

/// For now, just a Github Team. Can be upgraded to other teams
/// later if desirable.
#[derive(Queryable, Identifiable)]
pub struct Team {
    /// Unique table id
    pub id: i32,
    /// "github:org:team"
    /// An opaque unique ID, that was at one point parsed out to query Github.
    /// We only query membership with github using the github_id, though.
    /// This is the only name we should ever talk to Cargo about.
    pub login: String,
    /// We're assuming these are stable
    pub github_id: i32,
    /// Sugary goodness
    pub name: Option<String>,
    pub avatar: Option<String>,

}

#[derive(RustcEncodable)]
pub struct EncodableOwner {
    pub id: i32,
    pub login: String,
    pub kind: String,
    pub email: Option<String>,
    pub url: Option<String>,
    pub name: Option<String>,
    pub avatar: Option<String>,
}

/// Access rights to the crate (publishing and ownership management)
/// NOTE: The order of these variants matters!
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum Rights {
    None,
    Publish,
    Full,
}

impl Team {
    /// Tries to create the Team in the DB (assumes a `:` has already been found).
    pub fn create(app: &App,
                  conn: &PgConnection,
                  login: &str,
                  req_user: &User)
                  -> CargoResult<Self> {
        // must look like system:xxxxxxx
        let mut chunks = login.split(':');
        match chunks.next().unwrap() {
            // github:rust-lang:owners
            "github" => {
                // Ok to unwrap since we know one ":" is contained
                let org = chunks.next().unwrap();
                let team = chunks.next().ok_or_else(||
                    human("missing github team argument; \
                            format is github:org:team")
                )?;
                Team::create_github_team(app, conn, login, org, team, req_user)
            }
            _ => {
                Err(human("unknown organization handler, \
                            only 'github:org:team' is supported"))
            }
        }
    }

    /// Tries to create a Github Team from scratch. Assumes `org` and `team` are
    /// correctly parsed out of the full `name`. `name` is passed as a
    /// convenience to avoid rebuilding it.
    pub fn create_github_team(app: &App, conn: &PgConnection, login: &str,
                              org_name: &str, team_name: &str, req_user: &User)
                              -> CargoResult<Self> {
        // GET orgs/:org/teams
        // check that `team` is the `slug` in results, and grab its data

        // "sanitization"
        fn whitelist(c: &char) -> bool {
            match *c {
                'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' => false,
                _ => true
            }
        }

        if let Some(c) = org_name.chars().find(whitelist) {
            return Err(human(&format_args!("organization cannot contain special \
                                        characters like {}", c)));
        }

        #[derive(RustcDecodable)]
        struct GithubTeam {
            slug: String,   // the name we want to find
            id: i32,        // unique GH id (needed for membership queries)
            name: Option<String>,   // Pretty name
        }

        // FIXME: we just set per_page=100 and don't bother chasing pagination
        // links. A hundred teams should be enough for any org, right?
        let url = format!("/orgs/{}/teams?per_page=100", org_name);
        let token = http::token(req_user.gh_access_token.clone());
        let (handle, data) = http::github(app, &url, &token)?;
        let teams: Vec<GithubTeam> = http::parse_github_response(handle, &data)?;

        let team = teams.into_iter().find(|team| team.slug == team_name)
            .ok_or_else(|| {
                human(&format_args!("could not find the github team {}/{}",
                              org_name, team_name))
            })?;

        if !team_with_gh_id_contains_user(app, team.id, req_user)? {
            return Err(human("only members of a team can add it as an owner"));
        }

        #[derive(RustcDecodable)]
        struct Org {
            avatar_url: Option<String>,
        }

        let url = format!("/orgs/{}", org_name);
        let (handle, resp) = http::github(app, &url, &token)?;
        let org: Org = http::parse_github_response(handle, &resp)?;

        Team::insert(conn, login, team.id, team.name, org.avatar_url)
    }

    pub fn insert(conn: &PgConnection,
                  login: &str,
                  github_id: i32,
                  name: Option<String>,
                  avatar: Option<String>)
                  -> CargoResult<Self> {
        #[derive(Insertable)]
        #[table_name="teams"]
        struct NewTeam<'a> {
            login: &'a str,
            github_id: i32,
            name: Option<String>,
            avatar: Option<String>,
        }
        let new_team = NewTeam {
            login: login,
            github_id: github_id,
            name: name,
            avatar: avatar,
        };
        diesel::insert(&new_team).into(teams::table)
            .get_result(conn)
            .map_err(Into::into)
    }

    /// Phones home to Github to ask if this User is a member of the given team.
    /// Note that we're assuming that the given user is the one interested in
    /// the answer. If this is not the case, then we could accidentally leak
    /// private membership information here.
    pub fn contains_user(&self, app: &App, user: &User) -> CargoResult<bool> {
        team_with_gh_id_contains_user(app, self.github_id, user)
    }
}

fn team_with_gh_id_contains_user(app: &App, github_id: i32, user: &User)
                                                -> CargoResult<bool> {
    // GET teams/:team_id/memberships/:user_name
    // check that "state": "active"

    #[derive(RustcDecodable)]
    struct Membership {
        state: String,
    }

    let url = format!("/teams/{}/memberships/{}",
                        &github_id, &user.gh_login);
    let token = http::token(user.gh_access_token.clone());
    let (mut handle, resp) = http::github(app, &url, &token)?;

    // Officially how `false` is returned
    if handle.response_code().unwrap() == 404 {
        return Ok(false)
    }

    let membership: Membership = http::parse_github_response(handle, &resp)?;

    // There is also `state: pending` for which we could possibly give
    // some feedback, but it's not obvious how that should work.
    Ok(membership.state == "active")
}

impl Model for Team {
    fn from_row(row: &Row) -> Self {
        Team {
            id: row.get("id"),
            name: row.get("name"),
            github_id: row.get("github_id"),
            login: row.get("login"),
            avatar: row.get("avatar"),
        }
    }

    fn table_name(_: Option<Self>) -> &'static str { "teams" }
}

impl Owner {
    /// Finds the owner by name, failing out if it doesn't exist.
    /// May be a user's GH login, or a full team name. This is case
    /// sensitive.
    pub fn find_by_login(conn: &PgConnection,
                         name: &str) -> CargoResult<Owner> {
        if name.contains(':') {
            teams::table.filter(teams::login.eq(name))
                .first(conn)
                .map(Owner::Team)
                .map_err(|_|
                    human(&format_args!("could not find team with name {}", name))
                )
        } else {
            users::table.filter(users::gh_login.eq(name))
                .first(conn)
                .map(Owner::User)
                .map_err(|_|
                    human(&format_args!("could not find user with login `{}`", name))
                )
        }
    }

    pub fn kind(&self) -> i32 {
        match *self {
            Owner::User(_) => OwnerKind::User as i32,
            Owner::Team(_) => OwnerKind::Team as i32,
        }
    }

    pub fn login(&self) -> &str {
        match *self {
            Owner::User(ref user) => &user.gh_login,
            Owner::Team(ref team) => &team.login,
        }
    }

    pub fn id(&self) -> i32 {
        match *self {
            Owner::User(ref user) => user.id,
            Owner::Team(ref team) => team.id,
        }
    }

    pub fn encodable(self) -> EncodableOwner {
        match self {
            Owner::User(User { id, email, name, gh_login, gh_avatar, .. }) => {
                let url = format!("https://github.com/{}", gh_login);
                EncodableOwner {
                    id: id,
                    login: gh_login,
                    email: email,
                    avatar: gh_avatar,
                    url: Some(url),
                    name: name,
                    kind: String::from("user"),
                }
            }
            Owner::Team(Team { id, name, login, avatar, .. }) => {
                let url = {
                    let mut parts = login.split(':');
                    parts.next(); // discard github
                    format!("https://github.com/orgs/{}/teams/{}",
                            parts.next().unwrap(), parts.next().unwrap())
                };
                EncodableOwner {
                    id: id,
                    login: login,
                    email: None,
                    url: Some(url),
                    avatar: avatar,
                    name: name,
                    kind: String::from("team"),
                }
            }
        }
    }
}

/// Given this set of owners, determines the strongest rights the
/// given user has.
///
/// Shortcircuits on `Full` because you can't beat it. In practice we'll always
/// see `[user, user, user, ..., team, team, team]`, so we could shortcircuit on
/// `Publish` as well, but this is a non-obvious invariant so we don't bother.
/// Sweet free optimization if teams are proving burdensome to check.
/// More than one team isn't really expected, though.
pub fn rights(app: &App, owners: &[Owner], user: &User) -> CargoResult<Rights> {
    let mut best = Rights::None;
    for owner in owners {
        match *owner {
            Owner::User(ref other_user) => if other_user.id == user.id {
                return Ok(Rights::Full);
            },
            Owner::Team(ref team) => if team.contains_user(app, user)? {
                best = Rights::Publish;
            },
        }
    }
    Ok(best)
}

<commit_msg>Upsert based on GitHub team ID in case of org/team renames<commit_after>use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;
use pg::rows::Row;

use app::App;
use http;
use schema::*;
use util::{CargoResult, human};
use {Model, User, Crate};

#[derive(Insertable, Associations, Identifiable)]
#[belongs_to(Crate)]
#[belongs_to(User, foreign_key="owner_id")]
#[belongs_to(Team, foreign_key="owner_id")]
#[table_name="crate_owners"]
#[primary_key(crate_id, owner_id, owner_kind)]
pub struct CrateOwner {
    pub crate_id: i32,
    pub owner_id: i32,
    pub created_by: i32,
    pub owner_kind: i32,
}

#[repr(u32)]
pub enum OwnerKind {
    User = 0,
    Team = 1,
}

/// Unifies the notion of a User or a Team.
pub enum Owner {
    User(User),
    Team(Team),
}

/// For now, just a Github Team. Can be upgraded to other teams
/// later if desirable.
#[derive(Queryable, Identifiable)]
pub struct Team {
    /// Unique table id
    pub id: i32,
    /// "github:org:team"
    /// An opaque unique ID, that was at one point parsed out to query Github.
    /// We only query membership with github using the github_id, though.
    /// This is the only name we should ever talk to Cargo about.
    pub login: String,
    /// We're assuming these are stable
    pub github_id: i32,
    /// Sugary goodness
    pub name: Option<String>,
    pub avatar: Option<String>,

}

#[derive(RustcEncodable)]
pub struct EncodableOwner {
    pub id: i32,
    pub login: String,
    pub kind: String,
    pub email: Option<String>,
    pub url: Option<String>,
    pub name: Option<String>,
    pub avatar: Option<String>,
}

/// Access rights to the crate (publishing and ownership management)
/// NOTE: The order of these variants matters!
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub enum Rights {
    None,
    Publish,
    Full,
}

impl Team {
    /// Tries to create the Team in the DB (assumes a `:` has already been found).
    pub fn create(app: &App,
                  conn: &PgConnection,
                  login: &str,
                  req_user: &User)
                  -> CargoResult<Self> {
        // must look like system:xxxxxxx
        let mut chunks = login.split(':');
        match chunks.next().unwrap() {
            // github:rust-lang:owners
            "github" => {
                // Ok to unwrap since we know one ":" is contained
                let org = chunks.next().unwrap();
                let team = chunks.next().ok_or_else(||
                    human("missing github team argument; \
                            format is github:org:team")
                )?;
                Team::create_github_team(app, conn, login, org, team, req_user)
            }
            _ => {
                Err(human("unknown organization handler, \
                            only 'github:org:team' is supported"))
            }
        }
    }

    /// Tries to create a Github Team from scratch. Assumes `org` and `team` are
    /// correctly parsed out of the full `name`. `name` is passed as a
    /// convenience to avoid rebuilding it.
    pub fn create_github_team(app: &App, conn: &PgConnection, login: &str,
                              org_name: &str, team_name: &str, req_user: &User)
                              -> CargoResult<Self> {
        // GET orgs/:org/teams
        // check that `team` is the `slug` in results, and grab its data

        // "sanitization"
        fn whitelist(c: &char) -> bool {
            match *c {
                'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' => false,
                _ => true
            }
        }

        if let Some(c) = org_name.chars().find(whitelist) {
            return Err(human(&format_args!("organization cannot contain special \
                                        characters like {}", c)));
        }

        #[derive(RustcDecodable)]
        struct GithubTeam {
            slug: String,   // the name we want to find
            id: i32,        // unique GH id (needed for membership queries)
            name: Option<String>,   // Pretty name
        }

        // FIXME: we just set per_page=100 and don't bother chasing pagination
        // links. A hundred teams should be enough for any org, right?
        let url = format!("/orgs/{}/teams?per_page=100", org_name);
        let token = http::token(req_user.gh_access_token.clone());
        let (handle, data) = http::github(app, &url, &token)?;
        let teams: Vec<GithubTeam> = http::parse_github_response(handle, &data)?;

        let team = teams.into_iter().find(|team| team.slug == team_name)
            .ok_or_else(|| {
                human(&format_args!("could not find the github team {}/{}",
                              org_name, team_name))
            })?;

        if !team_with_gh_id_contains_user(app, team.id, req_user)? {
            return Err(human("only members of a team can add it as an owner"));
        }

        #[derive(RustcDecodable)]
        struct Org {
            avatar_url: Option<String>,
        }

        let url = format!("/orgs/{}", org_name);
        let (handle, resp) = http::github(app, &url, &token)?;
        let org: Org = http::parse_github_response(handle, &resp)?;

        Team::insert(conn, login, team.id, team.name, org.avatar_url)
    }

    pub fn insert(conn: &PgConnection,
                  login: &str,
                  github_id: i32,
                  name: Option<String>,
                  avatar: Option<String>)
                  -> CargoResult<Self> {
        use diesel::pg::upsert::*;

        #[derive(Insertable, AsChangeset)]
        #[table_name="teams"]
        struct NewTeam<'a> {
            login: &'a str,
            github_id: i32,
            name: Option<String>,
            avatar: Option<String>,
        }
        let new_team = NewTeam {
            login: login,
            github_id: github_id,
            name: name,
            avatar: avatar,
        };

        diesel::insert(
            &new_team.on_conflict(teams::github_id, do_update().set(&new_team))
        ).into(teams::table)
            .get_result(conn)
            .map_err(Into::into)
    }

    /// Phones home to Github to ask if this User is a member of the given team.
    /// Note that we're assuming that the given user is the one interested in
    /// the answer. If this is not the case, then we could accidentally leak
    /// private membership information here.
    pub fn contains_user(&self, app: &App, user: &User) -> CargoResult<bool> {
        team_with_gh_id_contains_user(app, self.github_id, user)
    }
}

fn team_with_gh_id_contains_user(app: &App, github_id: i32, user: &User)
                                                -> CargoResult<bool> {
    // GET teams/:team_id/memberships/:user_name
    // check that "state": "active"

    #[derive(RustcDecodable)]
    struct Membership {
        state: String,
    }

    let url = format!("/teams/{}/memberships/{}",
                        &github_id, &user.gh_login);
    let token = http::token(user.gh_access_token.clone());
    let (mut handle, resp) = http::github(app, &url, &token)?;

    // Officially how `false` is returned
    if handle.response_code().unwrap() == 404 {
        return Ok(false)
    }

    let membership: Membership = http::parse_github_response(handle, &resp)?;

    // There is also `state: pending` for which we could possibly give
    // some feedback, but it's not obvious how that should work.
    Ok(membership.state == "active")
}

impl Model for Team {
    fn from_row(row: &Row) -> Self {
        Team {
            id: row.get("id"),
            name: row.get("name"),
            github_id: row.get("github_id"),
            login: row.get("login"),
            avatar: row.get("avatar"),
        }
    }

    fn table_name(_: Option<Self>) -> &'static str { "teams" }
}

impl Owner {
    /// Finds the owner by name, failing out if it doesn't exist.
    /// May be a user's GH login, or a full team name. This is case
    /// sensitive.
    pub fn find_by_login(conn: &PgConnection,
                         name: &str) -> CargoResult<Owner> {
        if name.contains(':') {
            teams::table.filter(teams::login.eq(name))
                .first(conn)
                .map(Owner::Team)
                .map_err(|_|
                    human(&format_args!("could not find team with name {}", name))
                )
        } else {
            users::table.filter(users::gh_login.eq(name))
                .first(conn)
                .map(Owner::User)
                .map_err(|_|
                    human(&format_args!("could not find user with login `{}`", name))
                )
        }
    }

    pub fn kind(&self) -> i32 {
        match *self {
            Owner::User(_) => OwnerKind::User as i32,
            Owner::Team(_) => OwnerKind::Team as i32,
        }
    }

    pub fn login(&self) -> &str {
        match *self {
            Owner::User(ref user) => &user.gh_login,
            Owner::Team(ref team) => &team.login,
        }
    }

    pub fn id(&self) -> i32 {
        match *self {
            Owner::User(ref user) => user.id,
            Owner::Team(ref team) => team.id,
        }
    }

    pub fn encodable(self) -> EncodableOwner {
        match self {
            Owner::User(User { id, email, name, gh_login, gh_avatar, .. }) => {
                let url = format!("https://github.com/{}", gh_login);
                EncodableOwner {
                    id: id,
                    login: gh_login,
                    email: email,
                    avatar: gh_avatar,
                    url: Some(url),
                    name: name,
                    kind: String::from("user"),
                }
            }
            Owner::Team(Team { id, name, login, avatar, .. }) => {
                let url = {
                    let mut parts = login.split(':');
                    parts.next(); // discard github
                    format!("https://github.com/orgs/{}/teams/{}",
                            parts.next().unwrap(), parts.next().unwrap())
                };
                EncodableOwner {
                    id: id,
                    login: login,
                    email: None,
                    url: Some(url),
                    avatar: avatar,
                    name: name,
                    kind: String::from("team"),
                }
            }
        }
    }
}

/// Given this set of owners, determines the strongest rights the
/// given user has.
///
/// Shortcircuits on `Full` because you can't beat it. In practice we'll always
/// see `[user, user, user, ..., team, team, team]`, so we could shortcircuit on
/// `Publish` as well, but this is a non-obvious invariant so we don't bother.
/// Sweet free optimization if teams are proving burdensome to check.
/// More than one team isn't really expected, though.
pub fn rights(app: &App, owners: &[Owner], user: &User) -> CargoResult<Rights> {
    let mut best = Rights::None;
    for owner in owners {
        match *owner {
            Owner::User(ref other_user) => if other_user.id == user.id {
                return Ok(Rights::Full);
            },
            Owner::Team(ref team) => if team.contains_user(app, user)? {
                best = Rights::Publish;
            },
        }
    }
    Ok(best)
}

<|endoftext|>


<commit_before>use std::cmp::{Ordering};
use std::collections::{BinaryHeap};

#[derive(Eq, PartialEq, PartialOrd, Debug)]
pub struct Length(pub String);

impl Ord for Length {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.len().cmp(&other.0.len())
    }
}

pub struct HeapDrain<'a, T> where T: 'a + Ord {
    heap: &'a mut BinaryHeap<T>,
}

pub trait HeapPopDrain<T> where T: Ord {
    fn pop_drain(&mut self) -> HeapDrain<T>;
}

impl<T> HeapPopDrain<T> for BinaryHeap<T> where T: Ord {
    fn pop_drain(&mut self) -> HeapDrain<T> {
        HeapDrain{
            heap: self,
        }
    }
}

impl<'a, T> Iterator for HeapDrain<'a, T> where T: 'a + Ord {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.heap.pop()
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_length() {
        use super::{Length};
        use std::cmp::{Ordering};

        let short = Length("short".to_string());
        let longer = Length("longer".to_string());
        let equal = Length("equal".to_string());

        assert_eq!(short.cmp(&longer), Ordering::Less);
        assert_eq!(longer.cmp(&short), Ordering::Greater);
        assert_eq!(short.cmp(&equal), Ordering::Equal);
    }

    #[test]
    fn test_heapdrain() {
        use super::{HeapPopDrain};
        use std::collections::{BinaryHeap};

        let mut heap = BinaryHeap::new();
        heap.push(3);
        heap.push(9);
        heap.push(6);

        let mut drain = heap.pop_drain();
        assert_eq!(drain.next(), Some(9));
        assert_eq!(drain.next(), Some(6));
        assert_eq!(drain.next(), Some(3));
        assert_eq!(drain.next(), None);
    }
}
<commit_msg>Add more tests.<commit_after>use std::cmp::{Ordering};
use std::collections::{BinaryHeap};

#[derive(Eq, PartialEq, PartialOrd, Debug)]
pub struct Length(pub String);

impl Ord for Length {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.len().cmp(&other.0.len())
    }
}

pub struct HeapDrain<'a, T> where T: 'a + Ord {
    heap: &'a mut BinaryHeap<T>,
}

pub trait HeapPopDrain<T> where T: Ord {
    fn pop_drain(&mut self) -> HeapDrain<T>;
}

impl<T> HeapPopDrain<T> for BinaryHeap<T> where T: Ord {
    fn pop_drain(&mut self) -> HeapDrain<T> {
        HeapDrain{
            heap: self,
        }
    }
}

impl<'a, T> Iterator for HeapDrain<'a, T> where T: 'a + Ord {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.heap.pop()
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_length() {
        use super::{Length};
        use std::cmp::{Ordering};

        let short = Length("short".to_string());
        let longer = Length("longer".to_string());
        let equal = Length("equal".to_string());

        assert_eq!(short.cmp(&longer), Ordering::Less);
        assert_eq!(longer.cmp(&short), Ordering::Greater);
        assert_eq!(short.cmp(&equal), Ordering::Equal);
    }

    #[test]
    fn test_heapdrain() {
        use super::{Length, HeapPopDrain};
        use std::collections::{BinaryHeap};

        {
            let mut heap = BinaryHeap::new();
            heap.push(3);
            heap.push(9);
            heap.push(6);

            let mut drain = heap.pop_drain();
            assert_eq!(drain.next(), Some(9));
            assert_eq!(drain.next(), Some(6));
            assert_eq!(drain.next(), Some(3));
            assert_eq!(drain.next(), None);
        }

        {
            let mut heap = BinaryHeap::new();
            heap.push(Length("short".to_string()));
            heap.push(Length("notequal".to_string()));
            heap.push(Length("longer".to_string()));

            let mut drain = heap.pop_drain();
            assert_eq!(drain.next(), Some(Length("notequal".to_string())));
            assert_eq!(drain.next(), Some(Length("longer".to_string())));
            assert_eq!(drain.next(), Some(Length("short".to_string())));
            assert_eq!(drain.next(), None);
        }
    }
}
<|endoftext|>