<commit_before>use std::io::Write;
use std::io;
use std::str;

use utils::{find_byte, log_stderr_bytes};

#[derive(Debug)]
pub struct Msg {
    pub pfx     : Option<Pfx>,
    pub command : Command,
    pub params  : Vec<Vec<u8>>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),
    User {
        nick : String,
        /// user@host
        user : String,
    },
}

#[derive(Debug)]
pub enum Command {
    Str(String),
    Num(u16),
}

impl Msg {
    /// Parse a complete IRC message. NOTE: The string should NOT have CR-NL.
    pub fn parse(msg : &[u8]) -> Result<Msg, String> {
        if msg.len() == 0 {
            return Err("Empty msg".to_owned());
        }

        let mut slice = msg;

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

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

        let params = try!(parse_params(slice));

        Ok(Msg {
            pfx: pfx,
            command: command,
            params: params,
        })
    }

    ////////////////////////////////////////////////////////////////////////////
    // Message generation

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

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

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

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

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

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

fn reply_num(bs : &[u8]) -> Option<u16> {
    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
}

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

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_params(mut chrs : &[u8]) -> Result<Vec<Vec<u8>>, String> {
    if chrs.len() == 0 {
        return Err("parse_params: Empty slice of chars".to_owned());
    }

    if chrs[0] == b':' {
        let start_idx = 1; // drop the colon
        Ok(vec![
           (&chrs[ start_idx .. ]).to_owned()
        ])
    } else {
        let mut ret : Vec<Vec<u8>> = Vec::new();

        loop {
            match find_byte(chrs, b' ') {
                None => {
                    ret.push(chrs.to_owned());
                    break;
                },
                Some(end_idx) => {
                    ret.push((&chrs[ 0 .. end_idx ]).to_owned());
                    chrs = &chrs[ end_idx + 1 .. ]; // +1 to drop the space
                }
            }
        }

        Ok(ret)
    }
}

////////////////////////////////////////////////////////////////////////////////

#[test]
fn parse_error_1() {
    let msg = b"ERROR :Closing Link: 127.0.0.1 (Client Quit)";
    assert!(Msg::parse(msg).is_ok());
}
<commit_msg>drop the ':' from prefix<commit_after>use std::io::Write;
use std::io;
use std::str;

use utils::{find_byte, log_stderr_bytes};

#[derive(Debug)]
pub struct Msg {
    pub pfx     : Option<Pfx>,
    pub command : Command,
    pub params  : Vec<Vec<u8>>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Pfx {
    Server(String),
    User {
        nick : String,
        /// user@host
        user : String,
    },
}

#[derive(Debug)]
pub enum Command {
    Str(String),
    Num(u16),
}

impl Msg {
    /// Parse a complete IRC message. NOTE: The string should NOT have CR-NL.
    pub fn parse(msg : &[u8]) -> Result<Msg, String> {
        if msg.len() == 0 {
            return Err("Empty msg".to_owned());
        }

        let mut slice = msg;

        let pfx : Option<Pfx> = {
            if msg[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 {
                log_stderr_bytes("Can't parse msg prefix:", msg);
                None
            }
        };

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

        let params = try!(parse_params(slice));

        Ok(Msg {
            pfx: pfx,
            command: command,
            params: params,
        })
    }

    ////////////////////////////////////////////////////////////////////////////
    // Message generation

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

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

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

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

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

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

fn reply_num(bs : &[u8]) -> Option<u16> {
    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
}

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

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_params(mut chrs : &[u8]) -> Result<Vec<Vec<u8>>, String> {
    if chrs.len() == 0 {
        return Err("parse_params: Empty slice of chars".to_owned());
    }

    if chrs[0] == b':' {
        let start_idx = 1; // drop the colon
        Ok(vec![
           (&chrs[ start_idx .. ]).to_owned()
        ])
    } else {
        let mut ret : Vec<Vec<u8>> = Vec::new();

        loop {
            match find_byte(chrs, b' ') {
                None => {
                    ret.push(chrs.to_owned());
                    break;
                },
                Some(end_idx) => {
                    ret.push((&chrs[ 0 .. end_idx ]).to_owned());
                    chrs = &chrs[ end_idx + 1 .. ]; // +1 to drop the space
                }
            }
        }

        Ok(ret)
    }
}

////////////////////////////////////////////////////////////////////////////////

#[test]
fn parse_error_1() {
    let msg = b"ERROR :Closing Link: 127.0.0.1 (Client Quit)";
    assert!(Msg::parse(msg).is_ok());
}

{'fraction_non_alphanumeric': 0.19564563058091702, 'fraction_numerical': 0.013344035316544597, 'mean_word_length': 1.4636678200692042, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1393564', 'n_tokens_mistral': 3421, 'n_tokens_neox': 3281, 'n_words': 789}