<commit_before>// see 'rustc -W help'
#![warn(missing_docs, unused_extern_crates, unused_results)]

//! Test all of the tests in chats/, which are .txt files formatted with IRC
//! input beginning with <, expected IRC output beginning with >, and expected
//! github output beginning with !.

use futures::prelude::*;
use futures::task::Poll;
use irc::client::prelude::{Client as IrcClient, Config as IrcConfig};
use lazy_static::lazy_static;
use log::{debug, info};
use pin_utils::pin_mut;
use std::cell::{Cell, RefCell};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::rc::Rc;
use std::str;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use tokio::runtime::Runtime;
use tokio::time::{Duration, Instant};
use wgmeeting_github_ircbot::*;

#[test]
fn test_chats() {
    env_logger::init();

    let chats_dir = Path::new(file!()).parent().unwrap().join("chats");
    info!("Going through {:?}", chats_dir);
    let mut fail_count = 0;
    for direntry in chats_dir.read_dir().unwrap() {
        if let Ok(direntry) = direntry {
            if !test_one_chat(direntry.path().as_path()) {
                fail_count += 1;
            }
        }
    }
    assert!(
        fail_count == 0,
        format!("{} chat test failure(s), see above", fail_count)
    );
}

fn test_one_chat(path: &Path) -> bool {
    info!("Testing {:?}", path);

    let mut rt = Runtime::new().unwrap();

    // All of the lines in the file, as a vec (lines, backwards) of vecs (bytes,
    // forwards).
    let mut chat_file_reversed_lines = {
        let mut file_bytes: Vec<u8> = Vec::new();
        let _size = File::open(path)
            .unwrap()
            .read_to_end(&mut file_bytes)
            .unwrap();
        file_bytes
    }
    .split(|byte| *byte == '\n' as u8)
    .map(|arr| arr.to_vec())
    .rev()
    .collect::<Vec<Vec<u8>>>();

    let actual_lines_cell = Rc::new(RefCell::new(Vec::<u8>::new()));
    let expected_lines_cell = Rc::new(RefCell::new(
        ">CAP END\r\n>NICK test-github-bot\r\n>USER dbaron-gh-bot 0 * :Bot \
         to add meeting minutes to github issues.\r\n"
            .bytes()
            .collect::<Vec<u8>>(),
    ));

    let is_finished_cell = Rc::new(Cell::new(false));

    struct WaitLinesData {
        expect_lines: i32,
        wait_deadline: Instant,
    };

    impl WaitLinesData {
        pub fn should_wait(&self) -> bool {
            let time_remains = self.wait_deadline > Instant::now();
            let result = self.expect_lines > 0 && time_remains;
            debug!(
                "should_wait: expect_lines={}, time_remains={} ==> {}",
                self.expect_lines, time_remains, result
            );
            if !time_remains {
                info!("wait for {} lines timed out", self.expect_lines);
            }
            result
        }
    };

    const WAIT_DURATION: Duration = Duration::from_millis(100u64);
    const SERVER_SHUTDOWN_DURATION: Duration = Duration::from_millis(10u64);

    let wait_lines_data_cell = Rc::new(RefCell::new(WaitLinesData {
        expect_lines: 3, // length of identify sequence
        wait_deadline: Instant::now() + WAIT_DURATION,
    }));

    // A stream of the file data, but which will be slowed down so that it is
    // Poll::Pending when we ought to be waiting for input from the IRC server
    // instead.
    let file_data_stream = stream::poll_fn({
        let wait_lines_data_cell = wait_lines_data_cell.clone();
        let expected_lines_cell = expected_lines_cell.clone();
        move |_cx| -> Poll<Option<Vec<u8>>> {
            debug!("in poll_fn");
            if wait_lines_data_cell.borrow().should_wait() {
                // FIXME: Is this timer really needed?
                debug!("starting should_wait timer");
                let _ = tokio::spawn(tokio::time::delay_for(Duration::from_millis(1)).then(|()| {
                    debug!("should_wait timer finished");
                    future::ok::<(), ()>(())
                }));

                Poll::Pending
            } else {
                let line_option = chat_file_reversed_lines.pop();
                if let Some(ref line) = line_option {
                    let mut expected_lines = expected_lines_cell.borrow_mut();
                    debug!(
                        "returning line -{} from file_data_stream's poll_fn: {}",
                        chat_file_reversed_lines.len(),
                        str::from_utf8(line).unwrap()
                    );
                    match line.first().map(|b| *b as char) {
                        // just skip blank lines (and the empty string after the last
                        // line, given the use of split)
                        None => (),
                        Some('<') => {
                            debug!(
                                "adding line to read buffer: {}",
                                str::from_utf8(line).unwrap()
                            );
                            let line = str::from_utf8(line).unwrap();
                            expected_lines.extend_from_slice(line.as_bytes());
                            expected_lines.append(&mut "\r\n".bytes().collect());
                        }
                        Some('>') => {
                            {
                                let mut wait_lines_data = wait_lines_data_cell.borrow_mut();
                                wait_lines_data.expect_lines = wait_lines_data.expect_lines + 1;
                                wait_lines_data.wait_deadline = Instant::now() + WAIT_DURATION;
                            }
                            debug!(
                                "adding line to expected results: {}",
                                str::from_utf8(line).unwrap()
                            );
                            expected_lines.extend(
                                str::from_utf8(line)
                                    .unwrap()
                                    .replace("[[CODE_DESCRIPTION]]", &*code_description())
                                    .bytes(),
                            );
                            expected_lines.append(&mut "\r\n".bytes().collect());
                        }
                        Some('!') => {
                            {
                                let mut wait_lines_data = wait_lines_data_cell.borrow_mut();
                                wait_lines_data.expect_lines = wait_lines_data.expect_lines + 1;
                                wait_lines_data.wait_deadline = Instant::now() + WAIT_DURATION;
                            }
                            // FIXME: Need to get these in the actual data, too!
                            info!(
                                "adding line to expected results: {}",
                                str::from_utf8(line).unwrap()
                            );
                            // for now, we send the github comments over IRC when
                            // testing, but we don't encode that into the chat
                            // format
                            expected_lines
                                .append(&mut ">PRIVMSG github-comments ".bytes().collect());
                            // Match use of ":" in the stringify function in irc-proto's
                            // src/command.rs.
                            if line.len() == 1 || line.contains(&0x20u8 /* space */) {
                                expected_lines.append(&mut ":".bytes().collect());
                            }
                            expected_lines.extend_from_slice(&line[1..]);
                            expected_lines.append(&mut "\r\n".bytes().collect());
                        }
                        _ => {
                            panic!(
                                //"Unexpected line in test file {:?}:\n{}",
                                // path,
                                "Unexpected line in test file:\n{}",
                                str::from_utf8(line).unwrap_or("[non-UTF-8 line]")
                            );
                        }
                    }
                }
                Poll::Ready(line_option)
            }
        }
    });

    // Note that this leaves the initial '<' in the line.
    let lines_to_write_stream =
        file_data_stream.filter(|line| future::ready(line.first() == Some(&('<' as u8))));

    let irc_config = IrcConfig {
        use_mock_connection: false,
        owners: vec![format!("dbaron")],
        nickname: Some(format!("test-github-bot")),
        alt_nicks: vec![format!("test-github-bot-"), format!("test-github-bot--")],
        username: Some(format!("dbaron-gh-bot")),
        realname: Some(format!("Bot to add meeting minutes to github issues.")),
        server: Some(format!("127.0.0.1")),
        port: Some(43210),
        use_tls: Some(false),
        encoding: Some(format!("UTF-8")),
        channels: vec![format!("#meetingbottest"), format!("#testchannel2")],
        user_info: Some(format!("Bot to add meeting minutes to github issues.")),

        // In testing mode, we send the github comments as IRC messages, so we
        // need to be able to handle more substantial bursts of messages
        // without delay.
        burst_window_length: Some(0),
        max_messages_in_burst: Some(50),
        ..Default::default()
    };
    lazy_static! {
        static ref BOT_CONFIG: BotConfig = BotConfig {
            source: "https://github.com/dbaron/wgmeeting-github-ircbot".to_string(),
            channels: vec![
                (format!("#meetingbottest"), ChannelConfig {
                    group: format!("Bot-Testing Working Group"),
                    github_repos_allowed: vec![
                        "dbaron/wgmeeting-github-ircbot".to_string(),
                        "dbaron/nonexistentrepo".to_string(),
                        "upsuper/*".to_string(),
                    ],
                }),
                (format!("#testchannel2"), ChannelConfig {
                    group: format!("Second Bot-Testing Working Group"),
                    github_repos_allowed: vec![
                        "dbaron/wgmeeting-github-ircbot".to_string(),
                    ],
                }),
            ].into_iter().collect(),
            // Use of a 0 value disables timeouts, which is needed to avoid intermittent
            // failures (using really-0 timeouts) or having the event loop wait until the
            // timeout completes (positive timeouts).
            activity_timeout_minutes: 0,
            owners: vec![format!("dbaron")],
            ..Default::default()
        };
    }

    let mut irc_state_ = IRCState::new(GithubType::MockGithubConnection);
    let irc_state = &mut irc_state_;

    let irc_server_addr: String = (*format!(
        "{}:{}",
        irc_config.server.as_ref().unwrap(),
        irc_config.port.as_ref().unwrap()
    ))
    .parse()
    .unwrap();
    let mut irc_server_tcp = rt.block_on(TcpListener::bind(&irc_server_addr)).unwrap();
    let irc_server_stream = irc_server_tcp
        .accept()
        .then({
            let actual_lines_cell = actual_lines_cell.clone();
            let is_finished_cell = is_finished_cell.clone();
            move |res| {
                let (tcp_stream, _socket_addr) = res.unwrap();
                tcp_stream.set_nodelay(true).unwrap();
                debug!("IRC server got incoming connection: nodelay={} recv_buffer_size={} send_buffer_size={}", tcp_stream.nodelay().unwrap(), tcp_stream.recv_buffer_size().unwrap(), tcp_stream.send_buffer_size().unwrap());
                let (reader, writer) = tcp_stream.into_split();
                let reader = BufReader::new(reader).lines();
                let writer_cell = Rc::new(RefCell::new(writer));
                let logged_lines_future = reader.take_until({
                    let is_finished_cell = is_finished_cell.clone();
                    future::poll_fn(move |_cx| {
                        if is_finished_cell.get() {
                            debug!("in take_until callback for messages stream: terminating");
                            Poll::Ready(())
                        } else {
                            debug!("in take_until callback for messages stream: continuing");
                            Poll::Pending
                        }
                    })
                }).for_each({
                    let actual_lines_cell = actual_lines_cell.clone();
                    move |line_result| {
                        let line = line_result.unwrap();
                        if !line.starts_with("PING ") {
                            debug!("IRC server read line: {}", line);
                            {
                                let mut wait_lines_data = wait_lines_data_cell.borrow_mut();
                                wait_lines_data.expect_lines = wait_lines_data.expect_lines - 1;
                            }
                            {
                                let mut actual_lines = actual_lines_cell.borrow_mut();

                                actual_lines.append(&mut ">".bytes().collect());
                                actual_lines.extend_from_slice(
                                    line.chars()
                                        .flat_map(|c| c.escape_default())
                                        .collect::<String>()
                                        .as_bytes(),
                                );
                                actual_lines.append(&mut "\r\n".bytes().collect());
                            }
                        }
                        future::ready(())
                    }
                });
                let send_lines_future = lines_to_write_stream
                    .then({
                        let actual_lines_cell = actual_lines_cell.clone();
                        let writer_cell = writer_cell.clone();
                        move |line| {
                            {
                                let mut actual_lines = actual_lines_cell.borrow_mut();

                                // note that line still begins with '<'
                                // FIXME: Clean up this total hack for \u{1} !
                                // (The other direction uses escape_default().)
                                let mut line_str = str::from_utf8(&line[1..])
                                    .unwrap()
                                    .replace("\\u{1}", "\u{1}");
                                debug!("IRC server writing line: {}", line_str);
                                line_str.push_str("\r\n");

                                actual_lines.extend_from_slice(&line);
                                actual_lines.append(&mut "\r\n".bytes().collect());

                                // FIXME: Find a better way to do this!
                                future::poll_fn({
                                    let writer_cell = writer_cell.clone();
                                    move |cx| {
                                        let mut writer = writer_cell.borrow_mut();
                                        let write_future = writer.write(line_str.as_bytes());
                                        pin_mut!(write_future);
                                        write_future.poll(cx)
                                    }
                                })
                            }
                        }
                    })
                    .for_each(|_item| future::ready(()))
                    .then(move |()| tokio::time::delay_for(SERVER_SHUTDOWN_DURATION))
                    .then(move |_timeout| {
                        debug!("SHUTTING DOWN THE SERVER");
                        is_finished_cell.set(true);
                        // tcp_stream.shutdown(std::net::Shutdown::Both)
                        // FIXME: This isn't enough.

                        // FIXME: Find a better way to do this!
                        future::poll_fn(move |cx| {
                            let mut writer = writer_cell.borrow_mut();
                            let shutdown_future = writer.shutdown();
                            pin_mut!(shutdown_future);
                            shutdown_future.poll(cx)
                        })
                    });
                future::join(logged_lines_future, send_lines_future)
            }
        });

    let irc_client: &'static mut _ = Box::leak(Box::new(
        rt.block_on(IrcClient::from_config(irc_config)).unwrap(),
    ));

    irc_client.identify().unwrap();

    let irc_stream = irc_client.stream().unwrap();

    // Work around https://github.com/rust-lang/rust/issues/42574 ?
    let irc_client_: &'static _ = irc_client;

    let irc_future = irc_stream
        .take_until({
            let is_finished_cell = is_finished_cell.clone();
            future::poll_fn(move |_cx| {
                if is_finished_cell.get() {
                    debug!("in take_until callback for messages stream: terminating");
                    Poll::Ready(())
                } else {
                    debug!("in take_until callback for messages stream: continuing");
                    Poll::Pending
                }
            })
        })
        .for_each(move |message_result| {
            debug!("got IRC message");
            process_irc_message(
                &irc_client_,
                irc_state,
                &BOT_CONFIG,
                message_result.unwrap(),
            );
            future::ready(())
        });

    debug!("starting rt.run");
    let _ = rt.block_on(future::join(irc_server_stream, irc_future));
    debug!("done rt.run");

    let actual_lines = actual_lines_cell.borrow();
    let actual_str = str::from_utf8(actual_lines.as_slice()).unwrap();
    let expected_lines = expected_lines_cell.borrow();
    let expected_str = str::from_utf8(expected_lines.as_slice()).unwrap();
    let pass = actual_str == expected_str;
    println!("\n{:?} {}", path, if pass { "PASS" } else { "FAIL" });
    if !pass {
        for d in diff::lines(expected_str, actual_str) {
            match d {
                diff::Result::Left(actual) => println!("-{}", actual),
                diff::Result::Both(actual, _) => println!(" {}", actual),
                diff::Result::Right(expected) => println!("+{}", expected),
            }
        }
    }

    pass
}
<commit_msg>Restructure test_chats using async fn, which makes it both simpler and clearer.<commit_after>// see 'rustc -W help'
#![warn(missing_docs, unused_extern_crates, unused_results)]

//! Test all of the tests in chats/, which are .txt files formatted with IRC
//! input beginning with <, expected IRC output beginning with >, and expected
//! github output beginning with !.

use futures::prelude::*;
use futures::task::Poll;
use irc::client::prelude::{Client as IrcClient, Config as IrcConfig};
use lazy_static::lazy_static;
use log::{debug, info};
use std::cell::{Cell, RefCell};
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
use tokio::time::{Duration, Instant};
use wgmeeting_github_ircbot::*;

const MOCK_SERVER_HOST: &str = "127.0.0.1";
const MOCK_SERVER_PORT: u16 = 43210;

#[tokio::test]
async fn test_chats() -> Result<(), failure::Error> {
    env_logger::init();

    let chats_dir = Path::new(file!()).parent().unwrap().join("chats");
    info!("Going through {:?}", chats_dir);
    let mut fail_count = 0;
    for direntry in chats_dir.read_dir().unwrap() {
        if let Ok(direntry) = direntry {
            if !test_one_chat(direntry.path().as_path()).await? {
                fail_count += 1;
            }
        }
    }
    assert!(
        fail_count == 0,
        format!("{} chat test failure(s), see above", fail_count)
    );

    Ok(())
}

async fn test_one_chat(path: &Path) -> Result<bool, failure::Error> {
    info!("Testing {:?}", path);

    // We're given the path to a file (the chat file) that represents a dialog between the bot
    // and other users on the IRC server, and also contains the comments the bot makes on github
    // issues.

    // All of the lines in the chat file, as a vec (lines) of vecs (bytes).
    let chat_file_lines = {
        let mut file_bytes: Vec<u8> = Vec::new();
        let _size = File::open(path)
            .unwrap()
            .read_to_end(&mut file_bytes)
            .unwrap();
        file_bytes
    }
    .split(|byte| *byte == '\n' as u8)
    .map(|arr| arr.to_vec())
    .collect::<Vec<Vec<u8>>>();

    let is_finished = Cell::new(false);

    let server = mock_irc_server(&chat_file_lines, &is_finished);
    let bot = run_irc_bot(&is_finished);

    let (actual_lines, bot_result) = future::join(server, bot).await;
    let _ = bot_result?;
    let actual_lines = actual_lines?;

    let actual_str = str::from_utf8(actual_lines.as_slice()).unwrap();
    let expected_lines = chat_lines_to_expected_lines(&chat_file_lines);
    let expected_str = str::from_utf8(expected_lines.as_slice()).unwrap();
    let test_pass = actual_str == expected_str;
    println!("\n{:?} {}", path, if test_pass { "PASS" } else { "FAIL" });
    if !test_pass {
        for d in diff::lines(expected_str, actual_str) {
            match d {
                diff::Result::Left(actual) => println!("-{}", actual),
                diff::Result::Both(actual, _) => println!(" {}", actual),
                diff::Result::Right(expected) => println!("+{}", expected),
            }
        }
    }

    Ok(test_pass)
}

/// Run the fake IRC server for the chat test, driving the dialog based on the chat file.
/// Record the entire conversation and return that recording for comparison with the expected
/// result.
async fn mock_irc_server(chat_file_lines: &Vec<Vec<u8>>, is_finished: &Cell<bool>) -> Result<Vec<u8>, failure::Error> {
    let actual_lines = RefCell::new(Vec::<u8>::new());

    struct WaitLinesData {
        expect_lines: i32,
        wait_deadline: Instant,
    };

    impl WaitLinesData {
        pub fn should_wait(&self) -> bool {
            let time_remains = self.wait_deadline > Instant::now();
            let result = self.expect_lines > 0 && time_remains;
            debug!(
                "should_wait: expect_lines={}, time_remains={} ==> {}",
                self.expect_lines, time_remains, result
            );
            if !time_remains {
                info!("wait for {} lines timed out", self.expect_lines);
            }
            result
        }
    };

    const WAIT_DURATION: Duration = Duration::from_millis(100u64);
    const SERVER_SHUTDOWN_DURATION: Duration = Duration::from_millis(10u64);

    let wait_lines_data = RefCell::new(WaitLinesData {
        expect_lines: 3, // length of identify sequence
        wait_deadline: Instant::now() + WAIT_DURATION,
    });

    let irc_server_addr = format!("{}:{}", MOCK_SERVER_HOST, MOCK_SERVER_PORT);
    let mut irc_server_listener = TcpListener::bind(&irc_server_addr).await?;
    let (mut tcp_stream, _socket_addr) = irc_server_listener.accept().await?;
    tcp_stream.set_nodelay(true).unwrap();
    debug!("IRC server got incoming connection: nodelay={} recv_buffer_size={} send_buffer_size={}", tcp_stream.nodelay().unwrap(), tcp_stream.recv_buffer_size().unwrap(), tcp_stream.send_buffer_size().unwrap());
    let (reader, mut writer) = tcp_stream.split();

    fn first_char(line: &Vec<u8>) -> Option<char> {
        line.first().map(|b| *b as char)
    }

    let reader_future = async {
        let mut lines = BufReader::new(reader).lines();
        while let Some(line) = lines.next_line().await? {
            if line.starts_with("PING ") {
                continue
            }
            debug!("IRC server read line: {}", line);

            {
                let mut wait_lines_data = wait_lines_data.borrow_mut();
                wait_lines_data.expect_lines = wait_lines_data.expect_lines - 1;
            }

            {
                let mut actual_lines = actual_lines.borrow_mut();
                actual_lines.append(&mut ">".bytes().collect());
                actual_lines.extend_from_slice(
                    line.chars()
                        .flat_map(|c| c.escape_default())
                        .collect::<String>()
                        .as_bytes(),
                );
                actual_lines.append(&mut "\r\n".bytes().collect());
            }
        }

        Ok::<(), failure::Error>(())
    };

    let writer_future = async {
        for line in chat_file_lines.iter() {
            let ch = first_char(line);
            if ch == Some('>') || ch == Some('!') {
                // This is a line we should expect to recieve from the bot.  Note this in
                // |wait_lines_data|, which |reader_future| will use to adjust its timing.
                let mut wait_lines_data = wait_lines_data.borrow_mut();
                wait_lines_data.expect_lines = wait_lines_data.expect_lines + 1;
                wait_lines_data.wait_deadline = Instant::now() + WAIT_DURATION;
            }

            if ch != Some('<') {
                continue
            }

            while wait_lines_data.borrow().should_wait() {
                tokio::time::delay_for(Duration::from_millis(1)).await;
            }

            // note that line still begins with '<'
            // FIXME: Clean up this total hack for \u{1} !
            // (The other direction uses escape_default().)
            let mut line_str = str::from_utf8(&line[1..]).unwrap().replace("\\u{1}", "\u{1}");
            debug!("IRC server writing line: {}", line_str);
            line_str.push_str("\r\n");

            {
                let mut actual_lines = actual_lines.borrow_mut();
                actual_lines.extend_from_slice(&line);
                actual_lines.append(&mut "\r\n".bytes().collect());
            }

            let _ = writer.write_all(line_str.as_bytes()).await?;
        }

        tokio::time::delay_for(SERVER_SHUTDOWN_DURATION).await;

        debug!("SHUTTING DOWN THE SERVER");
        is_finished.set(true);
        // This seems (to my surprise) to be good enough to make the reader terminate as well.
        writer.shutdown().await?;

        Ok::<(), failure::Error>(())
    };

    let (reader_result, writer_result) = future::join(reader_future, writer_future).await;
    reader_result?;
    writer_result?;

    Ok(actual_lines.into_inner())
}

/// Run the IRC bot side of the chat test (i.e., the code we're testing).
async fn run_irc_bot(is_finished: &Cell<bool>) -> Result<(), failure::Error> {
    let irc_config = IrcConfig {
        use_mock_connection: false,
        owners: vec![format!("dbaron")],
        nickname: Some(format!("test-github-bot")),
        alt_nicks: vec![format!("test-github-bot-"), format!("test-github-bot--")],
        username: Some(format!("dbaron-gh-bot")),
        realname: Some(format!("Bot to add meeting minutes to github issues.")),
        server: Some(format!("{}", MOCK_SERVER_HOST)),
        port: Some(MOCK_SERVER_PORT),
        use_tls: Some(false),
        encoding: Some(format!("UTF-8")),
        channels: vec![format!("#meetingbottest"), format!("#testchannel2")],
        user_info: Some(format!("Bot to add meeting minutes to github issues.")),

        // In testing mode, we send the github comments as IRC messages, so we
        // need to be able to handle more substantial bursts of messages
        // without delay.
        burst_window_length: Some(0),
        max_messages_in_burst: Some(50),
        ..Default::default()
    };
    lazy_static! {
        static ref BOT_CONFIG: BotConfig = BotConfig {
            source: "https://github.com/dbaron/wgmeeting-github-ircbot".to_string(),
            channels: vec![
                (format!("#meetingbottest"), ChannelConfig {
                    group: format!("Bot-Testing Working Group"),
                    github_repos_allowed: vec![
                        "dbaron/wgmeeting-github-ircbot".to_string(),
                        "dbaron/nonexistentrepo".to_string(),
                        "upsuper/*".to_string(),
                    ],
                }),
                (format!("#testchannel2"), ChannelConfig {
                    group: format!("Second Bot-Testing Working Group"),
                    github_repos_allowed: vec![
                        "dbaron/wgmeeting-github-ircbot".to_string(),
                    ],
                }),
            ].into_iter().collect(),
            // Use of a 0 value disables timeouts, which is needed to avoid intermittent
            // failures (using really-0 timeouts) or having the event loop wait until the
            // timeout completes (positive timeouts).
            activity_timeout_minutes: 0,
            owners: vec![format!("dbaron")],
            ..Default::default()
        };
    }

    let mut irc_state = IRCState::new(GithubType::MockGithubConnection);

    let irc_client: &'static mut _ = Box::leak(Box::new(
        IrcClient::from_config(irc_config).await?,
    ));

    irc_client.identify()?;

    let finished_cb = {
        future::poll_fn(move |_cx| {
            if is_finished.get() {
                debug!("in take_until callback for messages stream: terminating");
                Poll::Ready(())
            } else {
                debug!("in take_until callback for messages stream: continuing");
                Poll::Pending
            }
        })
    };

    let mut irc_stream = irc_client.stream()?.take_until(finished_cb);
    while let Some(message) = irc_stream.next().await.transpose()? {
        process_irc_message(irc_client, &mut irc_state, &BOT_CONFIG, message);
    }

    Ok(())
}

/// Convert the lines in the chat file to the dialog that the test should expect to have been
/// recorded by the IRC server.
fn chat_lines_to_expected_lines(chat_file_lines: &Vec<Vec<u8>>) -> Vec<u8> {
    let mut expected_lines = ">CAP END\r\n>NICK test-github-bot\r\n>USER dbaron-gh-bot 0 * :Bot to add meeting minutes to github issues.\r\n".bytes().collect::<Vec<u8>>();

    for line in chat_file_lines.iter() {
        match line.first().map(|b| *b as char) {
            // just skip blank lines (and the empty string after the last
            // line, given the use of split)
            None => (),
            Some('<') => {
                let line = str::from_utf8(line).unwrap();
                expected_lines.extend_from_slice(line.as_bytes());
                expected_lines.append(&mut "\r\n".bytes().collect());
            }
            Some('>') => {
                expected_lines.extend(
                    str::from_utf8(line)
                        .unwrap()
                        .replace("[[CODE_DESCRIPTION]]", &*code_description())
                        .bytes(),
                );
                expected_lines.append(&mut "\r\n".bytes().collect());
            }
            Some('!') => {
                // for now, we send the github comments over IRC when
                // testing, but we don't encode that into the chat
                // format
                expected_lines
                    .append(&mut ">PRIVMSG github-comments ".bytes().collect());
                // Match use of ":" in the stringify function in irc-proto's
                // src/command.rs.
                if line.len() == 1 || line.contains(&0x20u8 /* space */) {
                    expected_lines.append(&mut ":".bytes().collect());
                }
                expected_lines.extend_from_slice(&line[1..]);
                expected_lines.append(&mut "\r\n".bytes().collect());
            }
            _ => {
                panic!(
                    //"Unexpected line in test file {:?}:\n{}",
                    // path,
                    "Unexpected line in test file:\n{}",
                    str::from_utf8(line).unwrap_or("[non-UTF-8 line]")
                );
            }
        }
    }

    expected_lines
}
<|endoftext|>


<commit_before>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013-2015  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

//! This crate provides definitions for types that are intrinsic in
//! GObject introspection, unlike all other types that have a definition in
//! a GIR file. All crates generated from GObject introspection data should
//! use these shared definitions.
//!
//! Some types are omitted because a suitable equivalent is readily available
//! in Rust:
//!
//! 1. Fixed-size integer types. These have straightforward built-in
//!    counterparts in Rust.
//! 2. Strings can have GI types `utf8` or `filename`. In the FFI all string
//!    values are represented as raw pointers to `gchar`. A safe
//!    representation of C strings in idiomatic Rust bindings
//!    needs some wrapper types which are not defined here.

extern crate libc;

// The fundamental types are defined in Python module giscanner.ast

pub mod primitive {

    //! Definitions for primitive fundamental types.
    //!
    //! Values of these types don't have associated semantics or state
    //! that would necessitate some kind of wrapper types in safe Rust
    //! bindings. Therefore, these types can be used in both
    //! FFI declarations and idiomatic Rust bindings, unlikely to require
    //! name disambiguation with any generated or binding types.
    //!
    //! The untyped pointer types are also defined in this module,
    //! because safe Rust bindings are unlikely to need to reuse their names.

    #![allow(non_camel_case_types)]

    use libc;

    pub type gboolean       = libc::c_int;
    pub type gchar          = libc::c_char;
    pub type guchar         = libc::c_uchar;
    pub type gshort         = libc::c_short;
    pub type gushort        = libc::c_ushort;
    pub type gint           = libc::c_int;
    pub type guint          = libc::c_uint;
    pub type glong          = libc::c_long;
    pub type gulong         = libc::c_ulong;
    pub type gsize          = libc::size_t;
    pub type gssize         = libc::ssize_t;
    pub type gintptr        = libc::intptr_t;
    pub type guintptr       = libc::uintptr_t;
    pub type gfloat         = libc::c_float;
    pub type gdouble        = libc::c_double;
    pub type gunichar       = u32;
    pub type gpointer       = *mut   libc::c_void;
    pub type gconstpointer  = *const libc::c_void;

    pub const FALSE: gboolean = 0;
    pub const TRUE : gboolean = 1;
}

// For -sys crates only dealing with FFI types, all public names defined
// in this crate should be conflict-free, so we make them available at the
// crate level handy for glob-importing.
pub use primitive::{gboolean, gchar, guchar, gshort, gushort, gint, guint};
pub use primitive::{glong, gulong, gsize, gssize, gintptr, guintptr};
pub use primitive::{gfloat, gdouble, gunichar, gpointer, gconstpointer};
pub use primitive::{FALSE, TRUE};

/// An integer type that designates GObject type identifiers.
///
/// `GType` is considered a fundamental type, regardless of being also
/// introspected both in GLib and GObject. Unlike the primitive types,
/// valid values of `GType` can only result from type registration,
/// so the safe bindings are likely to provide their own `GType` wrapper.
pub type GType = gsize;
<commit_msg>A FIXME comment about the glob-importing issue<commit_after>// This file is part of Grust, GObject introspection bindings for Rust
//
// Copyright (C) 2013-2015  Mikhail Zabaluev <mikhail.zabaluev@gmail.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

//! This crate provides definitions for types that are intrinsic in
//! GObject introspection, unlike all other types that have a definition in
//! a GIR file. All crates generated from GObject introspection data should
//! use these shared definitions.
//!
//! Some types are omitted because a suitable equivalent is readily available
//! in Rust:
//!
//! 1. Fixed-size integer types. These have straightforward built-in
//!    counterparts in Rust.
//! 2. Strings can have GI types `utf8` or `filename`. In the FFI all string
//!    values are represented as raw pointers to `gchar`. A safe
//!    representation of C strings in idiomatic Rust bindings
//!    needs some wrapper types which are not defined here.

extern crate libc;

// The fundamental types are defined in Python module giscanner.ast

pub mod primitive {

    //! Definitions for primitive fundamental types.
    //!
    //! Values of these types don't have associated semantics or state
    //! that would necessitate some kind of wrapper types in safe Rust
    //! bindings. Therefore, these types can be used in both
    //! FFI declarations and idiomatic Rust bindings, unlikely to require
    //! name disambiguation with any generated or binding types.
    //!
    //! The untyped pointer types are also defined in this module,
    //! because safe Rust bindings are unlikely to need to reuse their names.

    #![allow(non_camel_case_types)]

    use libc;

    pub type gboolean       = libc::c_int;
    pub type gchar          = libc::c_char;
    pub type guchar         = libc::c_uchar;
    pub type gshort         = libc::c_short;
    pub type gushort        = libc::c_ushort;
    pub type gint           = libc::c_int;
    pub type guint          = libc::c_uint;
    pub type glong          = libc::c_long;
    pub type gulong         = libc::c_ulong;
    pub type gsize          = libc::size_t;
    pub type gssize         = libc::ssize_t;
    pub type gintptr        = libc::intptr_t;
    pub type guintptr       = libc::uintptr_t;
    pub type gfloat         = libc::c_float;
    pub type gdouble        = libc::c_double;
    pub type gunichar       = u32;
    pub type gpointer       = *mut   libc::c_void;
    pub type gconstpointer  = *const libc::c_void;

    pub const FALSE: gboolean = 0;
    pub const TRUE : gboolean = 1;
}

// For -sys crates only dealing with FFI types, all public names defined
// in this crate should be conflict-free, so we make them available at the
// crate level handy for glob-importing.
// FIXME: Can't glob-import due to https://github.com/rust-lang/rust/issues/4865,
// which is fixed by Rust 1.4.0
pub use primitive::{gboolean, gchar, guchar, gshort, gushort, gint, guint};
pub use primitive::{glong, gulong, gsize, gssize, gintptr, guintptr};
pub use primitive::{gfloat, gdouble, gunichar, gpointer, gconstpointer};
pub use primitive::{FALSE, TRUE};

/// An integer type that designates GObject type identifiers.
///
/// `GType` is considered a fundamental type, regardless of being also
/// introspected both in GLib and GObject. Unlike the primitive types,
/// valid values of `GType` can only result from type registration,
/// so the safe bindings are likely to provide their own `GType` wrapper.
pub type GType = gsize;
<|endoftext|>


<commit_before>// The MIT License (MIT)

// Copyright (c) 2015 Y. T. Chung <zonyitoo@gmail.com>

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//! BSON definition

use std::fmt::{Display, Error, Formatter};
use std::mem;

use chrono::{DateTime, Timelike, UTC};
use chrono::offset::TimeZone;
use rustc_serialize::json;
use rustc_serialize::hex::{FromHex, ToHex};

use oid;
use ordered::OrderedDocument;
use spec::{ElementType, BinarySubtype};

/// Possible BSON value types.
#[derive(Debug,Clone,PartialEq)]
pub enum Bson {
    FloatingPoint(f64),
    String(String),
    Array(Array),
    Document(Document),
    Boolean(bool),
    Null,
    RegExp(String, String),
    JavaScriptCode(String),
    JavaScriptCodeWithScope(String, Document),
    I32(i32),
    I64(i64),
    TimeStamp(i64),
    Binary(BinarySubtype, Vec<u8>),
    ObjectId(oid::ObjectId),
    UtcDatetime(DateTime<UTC>),
}

/// Alias for `Vec<Bson>`.
pub type Array = Vec<Bson>;
/// Alias for `OrderedDocument`.
pub type Document = OrderedDocument;

impl Display for Bson {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        match self {
            &Bson::FloatingPoint(f) => write!(fmt, "{}", f),
            &Bson::String(ref s) => write!(fmt, "\"{}\"", s),
            &Bson::Array(ref vec) => {
                try!(write!(fmt, "["));

                let mut first = true;
                for bson in vec.iter() {
                    if !first {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", bson));
                    first = false;
                }

                write!(fmt, "]")
            }
            &Bson::Document(ref doc) => write!(fmt, "{}", doc),
            &Bson::Boolean(b) => write!(fmt, "{}", b),
            &Bson::Null => write!(fmt, "null"),
            &Bson::RegExp(ref pat, ref opt) => write!(fmt, "/{}/{}", pat, opt),
            &Bson::JavaScriptCode(ref s) |
            &Bson::JavaScriptCodeWithScope(ref s, _) => fmt.write_str(&s),
            &Bson::I32(i) => write!(fmt, "{}", i),
            &Bson::I64(i) => write!(fmt, "{}", i),
            &Bson::TimeStamp(i) => {
                let time = (i >> 32) as i32;
                let inc = (i & 0xFFFFFFFF) as i32;

                write!(fmt, "Timestamp({}, {})", time, inc)
            }
            &Bson::Binary(t, ref vec) =>
                write!(fmt, "BinData({}, 0x{})", u8::from(t), vec.to_hex()),
            &Bson::ObjectId(ref id) => write!(fmt, "ObjectId(\"{}\")", id),
            &Bson::UtcDatetime(date_time) => write!(fmt, "Date(\"{}\")", date_time)
        }
    }
}

impl From<f32> for Bson {
    fn from(a: f32) -> Bson {
        Bson::FloatingPoint(a as f64)
    }
}

impl From<f64> for Bson {
    fn from(a: f64) -> Bson {
        Bson::FloatingPoint(a)
    }
}

impl<'a> From<&'a str> for Bson {
    fn from(s: &str) -> Bson {
        Bson::String(s.to_owned())
    }
}

impl From<String> for Bson {
    fn from(a: String) -> Bson {
        Bson::String(a)
    }
}

impl<'a> From<&'a String> for Bson {
    fn from(a: &'a String) -> Bson {
        Bson::String(a.to_owned())
    }
}

impl From<Array> for Bson {
    fn from(a: Array) -> Bson {
        Bson::Array(a)
    }
}

impl From<Document> for Bson {
    fn from(a: Document) -> Bson {
        Bson::Document(a)
    }
}

impl From<bool> for Bson {
    fn from(a: bool) -> Bson {
        Bson::Boolean(a)
    }
}

impl From<(String, String)> for Bson {
    fn from(a: (String, String)) -> Bson {
        let (a1, a2) = a;
        Bson::RegExp(a1.to_owned(), a2.to_owned())
    }
}

impl From<(String, Document)> for Bson {
    fn from(a: (String, Document)) -> Bson {
        let (a1, a2) = a;
        Bson::JavaScriptCodeWithScope(a1, a2)
    }
}

impl From<(BinarySubtype, Vec<u8>)> for Bson {
    fn from(a: (BinarySubtype, Vec<u8>)) -> Bson {
        let (a1, a2) = a;
        Bson::Binary(a1, a2)
    }
}

impl From<i32> for Bson {
    fn from(a: i32) -> Bson {
        Bson::I32(a)
    }
}

impl From<i64> for Bson {
    fn from(a: i64) -> Bson {
        Bson::I64(a)
    }
}

impl From<u32> for Bson {
    fn from(a: u32) -> Bson {
        Bson::I32(a as i32)
    }
}

impl From<u64> for Bson {
    fn from(a: u64) -> Bson {
        Bson::I64(a as i64)
    }
}

impl From<[u8; 12]> for Bson {
    fn from(a: [u8; 12]) -> Bson {
        Bson::ObjectId(oid::ObjectId::with_bytes(a))
    }
}

impl From<oid::ObjectId> for Bson {
    fn from(a: oid::ObjectId) -> Bson {
        Bson::ObjectId(a.to_owned())
    }
}

impl From<DateTime<UTC>> for Bson {
    fn from(a: DateTime<UTC>) -> Bson {
        Bson::UtcDatetime(a)
    }
}

impl Bson {
    /// Get the `ElementType` of this value.
    pub fn element_type(&self) -> ElementType {
        match self {
            &Bson::FloatingPoint(..) => ElementType::FloatingPoint,
            &Bson::String(..) => ElementType::Utf8String,
            &Bson::Array(..) => ElementType::Array,
            &Bson::Document(..) => ElementType::EmbeddedDocument,
            &Bson::Boolean(..) => ElementType::Boolean,
            &Bson::Null => ElementType::NullValue,
            &Bson::RegExp(..) => ElementType::RegularExpression,
            &Bson::JavaScriptCode(..) => ElementType::JavaScriptCode,
            &Bson::JavaScriptCodeWithScope(..) => ElementType::JavaScriptCodeWithScope,
            &Bson::I32(..) => ElementType::Integer32Bit,
            &Bson::I64(..) => ElementType::Integer64Bit,
            &Bson::TimeStamp(..) => ElementType::TimeStamp,
            &Bson::Binary(..) => ElementType::Binary,
            &Bson::ObjectId(..) => ElementType::ObjectId,
            &Bson::UtcDatetime(..) => ElementType::UtcDatetime,
        }
    }

    /// Convert this value to the best approximate `Json`.
    pub fn to_json(&self) -> json::Json {
        match self {
            &Bson::FloatingPoint(v) => json::Json::F64(v),
            &Bson::String(ref v) => json::Json::String(v.clone()),
            &Bson::Array(ref v) =>
                json::Json::Array(v.iter().map(|x| x.to_json()).collect()),
            &Bson::Document(ref v) =>
                json::Json::Object(v.iter().map(|(k, v)| (k.clone(), v.to_json())).collect()),
            &Bson::Boolean(v) => json::Json::Boolean(v),
            &Bson::Null => json::Json::Null,
            &Bson::RegExp(ref pat, ref opt) => {
                let mut re = json::Object::new();
                re.insert("pattern".to_owned(), json::Json::String(pat.clone()));
                re.insert("options".to_owned(), json::Json::String(opt.clone()));

                json::Json::Object(re)
            },
            &Bson::JavaScriptCode(ref code) => json::Json::String(code.clone()),
            &Bson::JavaScriptCodeWithScope(ref code, ref scope) => {
                let mut obj = json::Object::new();
                obj.insert("code".to_owned(), json::Json::String(code.clone()));

                let scope_obj =
                    scope.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();

                obj.insert("scope".to_owned(), json::Json::Object(scope_obj));

                json::Json::Object(obj)
            },
            &Bson::I32(v) => json::Json::I64(v as i64),
            &Bson::I64(v) => json::Json::I64(v),
            &Bson::TimeStamp(v) => json::Json::I64(v),
            &Bson::Binary(t, ref v) => {
                let mut obj = json::Object::new();
                let tval: u8 = From::from(t);
                obj.insert("type".to_owned(), json::Json::I64(tval as i64));
                obj.insert("data".to_owned(), json::Json::String(v.to_hex()));

                json::Json::Object(obj)
            },
            &Bson::ObjectId(ref v) => json::Json::String(v.bytes().to_hex()),
            &Bson::UtcDatetime(ref v) => json::Json::String(v.to_string()),
        }
    }

    /// Create a `Bson` from a `Json`.
    pub fn from_json(j: &json::Json) -> Bson {
        match j {
            &json::Json::I64(x) => Bson::I64(x),
            &json::Json::U64(x) => Bson::I64(x as i64),
            &json::Json::F64(x) => Bson::FloatingPoint(x),
            &json::Json::String(ref x) => Bson::String(x.clone()),
            &json::Json::Boolean(x) => Bson::Boolean(x),
            &json::Json::Array(ref x) => Bson::Array(x.iter().map(Bson::from_json).collect()),
            &json::Json::Object(ref x) => Bson::Document(x.iter().map(|(k, v)| (k.clone(), Bson::from_json(v))).collect()),
            &json::Json::Null => Bson::Null,
        }
    }

    pub fn to_extended_document(&self) -> Document {
        match *self {
            Bson::RegExp(ref pat, ref opt) => {
                doc! {
                    "$regex" => (pat.clone()),
                    "$options" => (opt.clone())
                }
            }
            Bson::JavaScriptCode(ref code) => {
                doc! {
                    "$code" => (code.clone())
                }
            }
            Bson::JavaScriptCodeWithScope(ref code, ref scope) => {
                doc! {
                    "$code" => (code.clone()),
                    "$scope" => (scope.clone())
                }
            }
            Bson::TimeStamp(v) => {
                let raw: [i32; 2] = unsafe { mem::transmute(v) };
                doc! {
                    "t" => (raw[0]),
                    "i" => (raw[1])
                }
            }
            Bson::Binary(t, ref v) => {
                let tval: u8 = From::from(t);
                doc! {
                    "$binary" => (v.to_hex()),
                    "type" => (tval as i64)
                }
            }
            Bson::ObjectId(ref v) => {
                doc! {
                    "$oid" => (v.to_string())
                }
            }
            Bson::UtcDatetime(ref v) => {
                doc! {
                    "$date" => {
                        "$numberLong" => ((v.timestamp() * 1000) + (v.nanosecond() / 1000000) as i64)
                    }
                }
            }
            // TODO: Actual error
            _ => unreachable!()
        }
    }

    pub fn from_extended_document(values: Document) -> Result<Bson, Error> {
        if values.contains_key("$regex") {
            if let Some(&Bson::String(ref pat)) = values.get("$regex") {
                if let Some(&Bson::String(ref opt)) = values.get("$options") {
                    return Ok(Bson::RegExp(pat.to_owned(), opt.to_owned()));
                }
            }

        } else if let Some(&Bson::String(ref code)) = values.get("$code") {
            if let Some(&Bson::Document(ref scope)) = values.get("$scope") {
                return Ok(Bson::JavaScriptCodeWithScope(code.to_owned(), scope.to_owned()));
            } else {
                return Ok(Bson::JavaScriptCode(code.to_owned()));
            }

        } else if let Some(&Bson::I32(t)) = values.get("t") {
            if let Some(&Bson::I32(i)) = values.get("i") {
                let raw: [i32; 2] = [t, i];
                let timestamp: i64 = unsafe { mem::transmute(raw) };
                return Ok(Bson::TimeStamp(timestamp))
            }

        } else if let Some(&Bson::String(ref hex)) = values.get("$binary") {
            if let Some(&Bson::I64(t)) = values.get("type") {
                let ttype = t as u8;
                return Ok(Bson::Binary(From::from(ttype), hex.from_hex().unwrap()));
            }

        } else if let Some(&Bson::String(ref hex)) = values.get("$oid") {
            return Ok(Bson::ObjectId(oid::ObjectId::with_string(hex).unwrap()));

        } else if let Some(&Bson::Document(ref doc)) = values.get("$date") {
            if let Some(&Bson::I64(long)) = doc.get("$numberLong") {
                return Ok(Bson::UtcDatetime(UTC.timestamp(long / 1000, (long % 1000) as u32 * 1000000)));
            }
        }

        Ok(Bson::Document(values))
    }
}
<commit_msg>Remove unsafe implementation<commit_after>// The MIT License (MIT)

// Copyright (c) 2015 Y. T. Chung <zonyitoo@gmail.com>

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

//! BSON definition

use std::fmt::{Display, Error, Formatter};

use chrono::{DateTime, Timelike, UTC};
use chrono::offset::TimeZone;
use rustc_serialize::json;
use rustc_serialize::hex::{FromHex, ToHex};

use oid;
use ordered::OrderedDocument;
use spec::{ElementType, BinarySubtype};

/// Possible BSON value types.
#[derive(Debug,Clone,PartialEq)]
pub enum Bson {
    FloatingPoint(f64),
    String(String),
    Array(Array),
    Document(Document),
    Boolean(bool),
    Null,
    RegExp(String, String),
    JavaScriptCode(String),
    JavaScriptCodeWithScope(String, Document),
    I32(i32),
    I64(i64),
    TimeStamp(i64),
    Binary(BinarySubtype, Vec<u8>),
    ObjectId(oid::ObjectId),
    UtcDatetime(DateTime<UTC>),
}

/// Alias for `Vec<Bson>`.
pub type Array = Vec<Bson>;
/// Alias for `OrderedDocument`.
pub type Document = OrderedDocument;

impl Display for Bson {
    fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
        match self {
            &Bson::FloatingPoint(f) => write!(fmt, "{}", f),
            &Bson::String(ref s) => write!(fmt, "\"{}\"", s),
            &Bson::Array(ref vec) => {
                try!(write!(fmt, "["));

                let mut first = true;
                for bson in vec.iter() {
                    if !first {
                        try!(write!(fmt, ", "));
                    }

                    try!(write!(fmt, "{}", bson));
                    first = false;
                }

                write!(fmt, "]")
            }
            &Bson::Document(ref doc) => write!(fmt, "{}", doc),
            &Bson::Boolean(b) => write!(fmt, "{}", b),
            &Bson::Null => write!(fmt, "null"),
            &Bson::RegExp(ref pat, ref opt) => write!(fmt, "/{}/{}", pat, opt),
            &Bson::JavaScriptCode(ref s) |
            &Bson::JavaScriptCodeWithScope(ref s, _) => fmt.write_str(&s),
            &Bson::I32(i) => write!(fmt, "{}", i),
            &Bson::I64(i) => write!(fmt, "{}", i),
            &Bson::TimeStamp(i) => {
                let time = (i >> 32) as i32;
                let inc = (i & 0xFFFFFFFF) as i32;

                write!(fmt, "Timestamp({}, {})", time, inc)
            }
            &Bson::Binary(t, ref vec) =>
                write!(fmt, "BinData({}, 0x{})", u8::from(t), vec.to_hex()),
            &Bson::ObjectId(ref id) => write!(fmt, "ObjectId(\"{}\")", id),
            &Bson::UtcDatetime(date_time) => write!(fmt, "Date(\"{}\")", date_time)
        }
    }
}

impl From<f32> for Bson {
    fn from(a: f32) -> Bson {
        Bson::FloatingPoint(a as f64)
    }
}

impl From<f64> for Bson {
    fn from(a: f64) -> Bson {
        Bson::FloatingPoint(a)
    }
}

impl<'a> From<&'a str> for Bson {
    fn from(s: &str) -> Bson {
        Bson::String(s.to_owned())
    }
}

impl From<String> for Bson {
    fn from(a: String) -> Bson {
        Bson::String(a)
    }
}

impl<'a> From<&'a String> for Bson {
    fn from(a: &'a String) -> Bson {
        Bson::String(a.to_owned())
    }
}

impl From<Array> for Bson {
    fn from(a: Array) -> Bson {
        Bson::Array(a)
    }
}

impl From<Document> for Bson {
    fn from(a: Document) -> Bson {
        Bson::Document(a)
    }
}

impl From<bool> for Bson {
    fn from(a: bool) -> Bson {
        Bson::Boolean(a)
    }
}

impl From<(String, String)> for Bson {
    fn from(a: (String, String)) -> Bson {
        let (a1, a2) = a;
        Bson::RegExp(a1.to_owned(), a2.to_owned())
    }
}

impl From<(String, Document)> for Bson {
    fn from(a: (String, Document)) -> Bson {
        let (a1, a2) = a;
        Bson::JavaScriptCodeWithScope(a1, a2)
    }
}

impl From<(BinarySubtype, Vec<u8>)> for Bson {
    fn from(a: (BinarySubtype, Vec<u8>)) -> Bson {
        let (a1, a2) = a;
        Bson::Binary(a1, a2)
    }
}

impl From<i32> for Bson {
    fn from(a: i32) -> Bson {
        Bson::I32(a)
    }
}

impl From<i64> for Bson {
    fn from(a: i64) -> Bson {
        Bson::I64(a)
    }
}

impl From<u32> for Bson {
    fn from(a: u32) -> Bson {
        Bson::I32(a as i32)
    }
}

impl From<u64> for Bson {
    fn from(a: u64) -> Bson {
        Bson::I64(a as i64)
    }
}

impl From<[u8; 12]> for Bson {
    fn from(a: [u8; 12]) -> Bson {
        Bson::ObjectId(oid::ObjectId::with_bytes(a))
    }
}

impl From<oid::ObjectId> for Bson {
    fn from(a: oid::ObjectId) -> Bson {
        Bson::ObjectId(a.to_owned())
    }
}

impl From<DateTime<UTC>> for Bson {
    fn from(a: DateTime<UTC>) -> Bson {
        Bson::UtcDatetime(a)
    }
}

impl Bson {
    /// Get the `ElementType` of this value.
    pub fn element_type(&self) -> ElementType {
        match self {
            &Bson::FloatingPoint(..) => ElementType::FloatingPoint,
            &Bson::String(..) => ElementType::Utf8String,
            &Bson::Array(..) => ElementType::Array,
            &Bson::Document(..) => ElementType::EmbeddedDocument,
            &Bson::Boolean(..) => ElementType::Boolean,
            &Bson::Null => ElementType::NullValue,
            &Bson::RegExp(..) => ElementType::RegularExpression,
            &Bson::JavaScriptCode(..) => ElementType::JavaScriptCode,
            &Bson::JavaScriptCodeWithScope(..) => ElementType::JavaScriptCodeWithScope,
            &Bson::I32(..) => ElementType::Integer32Bit,
            &Bson::I64(..) => ElementType::Integer64Bit,
            &Bson::TimeStamp(..) => ElementType::TimeStamp,
            &Bson::Binary(..) => ElementType::Binary,
            &Bson::ObjectId(..) => ElementType::ObjectId,
            &Bson::UtcDatetime(..) => ElementType::UtcDatetime,
        }
    }

    /// Convert this value to the best approximate `Json`.
    pub fn to_json(&self) -> json::Json {
        match self {
            &Bson::FloatingPoint(v) => json::Json::F64(v),
            &Bson::String(ref v) => json::Json::String(v.clone()),
            &Bson::Array(ref v) =>
                json::Json::Array(v.iter().map(|x| x.to_json()).collect()),
            &Bson::Document(ref v) =>
                json::Json::Object(v.iter().map(|(k, v)| (k.clone(), v.to_json())).collect()),
            &Bson::Boolean(v) => json::Json::Boolean(v),
            &Bson::Null => json::Json::Null,
            &Bson::RegExp(ref pat, ref opt) => {
                let mut re = json::Object::new();
                re.insert("pattern".to_owned(), json::Json::String(pat.clone()));
                re.insert("options".to_owned(), json::Json::String(opt.clone()));

                json::Json::Object(re)
            },
            &Bson::JavaScriptCode(ref code) => json::Json::String(code.clone()),
            &Bson::JavaScriptCodeWithScope(ref code, ref scope) => {
                let mut obj = json::Object::new();
                obj.insert("code".to_owned(), json::Json::String(code.clone()));

                let scope_obj =
                    scope.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();

                obj.insert("scope".to_owned(), json::Json::Object(scope_obj));

                json::Json::Object(obj)
            },
            &Bson::I32(v) => json::Json::I64(v as i64),
            &Bson::I64(v) => json::Json::I64(v),
            &Bson::TimeStamp(v) => json::Json::I64(v),
            &Bson::Binary(t, ref v) => {
                let mut obj = json::Object::new();
                let tval: u8 = From::from(t);
                obj.insert("type".to_owned(), json::Json::I64(tval as i64));
                obj.insert("data".to_owned(), json::Json::String(v.to_hex()));

                json::Json::Object(obj)
            },
            &Bson::ObjectId(ref v) => json::Json::String(v.bytes().to_hex()),
            &Bson::UtcDatetime(ref v) => json::Json::String(v.to_string()),
        }
    }

    /// Create a `Bson` from a `Json`.
    pub fn from_json(j: &json::Json) -> Bson {
        match j {
            &json::Json::I64(x) => Bson::I64(x),
            &json::Json::U64(x) => Bson::I64(x as i64),
            &json::Json::F64(x) => Bson::FloatingPoint(x),
            &json::Json::String(ref x) => Bson::String(x.clone()),
            &json::Json::Boolean(x) => Bson::Boolean(x),
            &json::Json::Array(ref x) => Bson::Array(x.iter().map(Bson::from_json).collect()),
            &json::Json::Object(ref x) => Bson::Document(x.iter().map(|(k, v)| (k.clone(), Bson::from_json(v))).collect()),
            &json::Json::Null => Bson::Null,
        }
    }

    pub fn to_extended_document(&self) -> Document {
        match *self {
            Bson::RegExp(ref pat, ref opt) => {
                doc! {
                    "$regex" => (pat.clone()),
                    "$options" => (opt.clone())
                }
            }
            Bson::JavaScriptCode(ref code) => {
                doc! {
                    "$code" => (code.clone())
                }
            }
            Bson::JavaScriptCodeWithScope(ref code, ref scope) => {
                doc! {
                    "$code" => (code.clone()),
                    "$scope" => (scope.clone())
                }
            }
            Bson::TimeStamp(v) => {
                let time = (v >> 32) as i32;
                let inc = (v & 0xFFFFFFFF) as i32;

                doc! {
                    "t" => time,
                    "i" => inc
                }
            }
            Bson::Binary(t, ref v) => {
                let tval: u8 = From::from(t);
                doc! {
                    "$binary" => (v.to_hex()),
                    "type" => (tval as i64)
                }
            }
            Bson::ObjectId(ref v) => {
                doc! {
                    "$oid" => (v.to_string())
                }
            }
            Bson::UtcDatetime(ref v) => {
                doc! {
                    "$date" => {
                        "$numberLong" => ((v.timestamp() * 1000) + (v.nanosecond() / 1000000) as i64)
                    }
                }
            }
            // TODO: Actual error
            _ => unreachable!()
        }
    }

    pub fn from_extended_document(values: Document) -> Result<Bson, Error> {
        if values.contains_key("$regex") {
            if let Some(&Bson::String(ref pat)) = values.get("$regex") {
                if let Some(&Bson::String(ref opt)) = values.get("$options") {
                    return Ok(Bson::RegExp(pat.to_owned(), opt.to_owned()));
                }
            }

        } else if let Some(&Bson::String(ref code)) = values.get("$code") {
            if let Some(&Bson::Document(ref scope)) = values.get("$scope") {
                return Ok(Bson::JavaScriptCodeWithScope(code.to_owned(), scope.to_owned()));
            } else {
                return Ok(Bson::JavaScriptCode(code.to_owned()));
            }

        } else if let Some(&Bson::I32(t)) = values.get("t") {
            if let Some(&Bson::I32(i)) = values.get("i") {
                let timestamp = ((t as i64) << 32) + (i as i64);
                return Ok(Bson::TimeStamp(timestamp))
            }

        } else if let Some(&Bson::String(ref hex)) = values.get("$binary") {
            if let Some(&Bson::I64(t)) = values.get("type") {
                let ttype = t as u8;
                return Ok(Bson::Binary(From::from(ttype), hex.from_hex().unwrap()));
            }

        } else if let Some(&Bson::String(ref hex)) = values.get("$oid") {
            return Ok(Bson::ObjectId(oid::ObjectId::with_string(hex).unwrap()));

        } else if let Some(&Bson::Document(ref doc)) = values.get("$date") {
            if let Some(&Bson::I64(long)) = doc.get("$numberLong") {
                return Ok(Bson::UtcDatetime(UTC.timestamp(long / 1000, (long % 1000) as u32 * 1000000)));
            }
        }

        Ok(Bson::Document(values))
    }
}
<|endoftext|>


<commit_before>use primitives::{Consumed, Parser, ParseError, ParseResult, Error, State, Stream};
use combinator::{Expected, skip_many, SkipMany, ParserExt, With};
use std::marker::PhantomData;
use std::borrow::IntoCow;

macro_rules! impl_char_parser {
    ($name: ident ($($ty_var: ident),*), $inner_type: ty) => {
    #[derive(Clone)]
    pub struct $name<I $(,$ty_var)*>($inner_type, PhantomData<I>)
        where I: Stream<Item=char> $(, $ty_var : Parser<Input=I>)*;
    impl <I $(,$ty_var)*> Parser for $name<I $(,$ty_var)*>
        where I: Stream<Item=char> $(, $ty_var : Parser<Input=I>)* {
        type Input = I;
        type Output = <$inner_type as Parser>::Output;
        fn parse_state(&mut self, input: State<<Self as Parser>::Input>) -> ParseResult<<Self as Parser>::Output, <Self as Parser>::Input> {
            self.0.parse_state(input)
        }
    }
}
}

///Parses any character
pub fn any_char<I>(input: State<I>) -> ParseResult<char, I>
    where I: Stream<Item=char> {
    input.uncons_char()
}

#[derive(Clone)]
pub struct Satisfy<I, Pred> { pred: Pred, _marker: PhantomData<I> }

impl <I, Pred> Parser for Satisfy<I, Pred>
    where I: Stream<Item=char>, Pred: FnMut(char) -> bool {

    type Input = I;
    type Output = char;
    fn parse_state(&mut self, input: State<I>) -> ParseResult<char, I> {
        match input.clone().uncons_char() {
            Ok((c, s)) => {
                if (self.pred)(c) { Ok((c, s)) }
                else {
                    Err(Consumed::Empty(ParseError::new(input.position, Error::Unexpected(c))))
                }
            }
            Err(err) => Err(err)
        }
    }
}

///Parses a character and succeeds depending on the result of `pred`
///
/// ```
/// # extern crate "parser-combinators" as pc;
/// # use pc::*;
/// # fn main() {
/// let result = satisfy(|c| c == '!')
///     .parse("!")
///     .map(|x| x.0);
/// assert_eq!(result, Ok('!'));
/// # }
/// ```
pub fn satisfy<I, Pred>(pred: Pred) -> Satisfy<I, Pred>
    where I: Stream<Item=char>, Pred: FnMut(char) -> bool {
    Satisfy { pred: pred, _marker: PhantomData }
}

impl_char_parser! { Digit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a digit from a stream containing characters
pub fn digit<I>() -> Digit<I>
    where I: Stream<Item=char> {
    Digit(satisfy(static_fn!((c, char) -> bool { c.is_digit(10) }))
         .expected("digit"), PhantomData)
}

impl_char_parser! { Space(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses whitespace
pub fn space<I>() -> Space<I>
    where I: Stream<Item=char> {
    Space(satisfy(char::is_whitespace as fn (char) -> bool)
        .expected("whitespace"), PhantomData)
}
impl_char_parser! { Spaces(), Expected<SkipMany<Space<I>>> }
///Skips over zero or more spaces
pub fn spaces<I>() -> Spaces<I>
    where I: Stream<Item=char> {
    Spaces(skip_many(space())
          .expected("whitespaces"), PhantomData)
}

impl_char_parser! { NewLine(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a newline character
pub fn newline<I>() -> NewLine<I>
    where I: Stream<Item=char> {
    NewLine(satisfy(static_fn!((ch, char) -> bool { ch == '\n' }))
           .expected("lf newline"), PhantomData)
}

impl_char_parser! { CrLf(), Expected<With<Satisfy<I, fn (char) -> bool>, NewLine<I>>> }
///Parses carriage return and newline, returning the newline character.
pub fn crlf<I>() -> CrLf<I>
    where I: Stream<Item=char> {
    CrLf(satisfy(static_fn!((ch, char) -> bool { ch == '\r' }))
        .with(newline())
        .expected("crlf newline"), PhantomData)
}

impl_char_parser! { Tab(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a tab character
pub fn tab<I>() -> Tab<I>
    where I: Stream<Item=char> {
    Tab(satisfy(static_fn!((ch, char) -> bool { ch == '\t' }))
       .expected("tab"), PhantomData)
}

impl_char_parser! { Upper(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an uppercase letter
pub fn upper<I>() -> Upper<I>
    where I: Stream<Item=char> {
    Upper(satisfy(static_fn!((ch, char) -> bool { ch.is_uppercase()}))
         .expected("uppercase letter"), PhantomData)
}

impl_char_parser! { Lower(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an uppercase letter
pub fn lower<I>() -> Lower<I>
    where I: Stream<Item=char> {
    Lower(satisfy(static_fn!((ch, char) -> bool { ch.is_lowercase() }))
         .expected("lowercase letter"), PhantomData)
}

impl_char_parser! { AlphaNum(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses either an alphabet letter or digit
pub fn alpha_num<I>() -> AlphaNum<I>
    where I: Stream<Item=char> {
    AlphaNum(satisfy(static_fn!((ch, char) -> bool { ch.is_alphanumeric() }))
            .expected("letter or digit"), PhantomData)
}

impl_char_parser! { Letter(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an alphabet letter
pub fn letter<I>() -> Letter<I>
    where I: Stream<Item=char> {
    Letter(satisfy(static_fn!((ch, char) -> bool { ch.is_alphabetic() }))
          .expected("letter"), PhantomData)
}

impl_char_parser! { OctDigit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an octal digit
pub fn oct_digit<I>() -> OctDigit<I>
    where I: Stream<Item=char> {
    OctDigit(satisfy(static_fn!((ch, char) -> bool { ch.is_digit(8) }))
            .expected("octal digit"), PhantomData)
}

impl_char_parser! { HexDigit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a hexdecimal digit with uppercase and lowercase
pub fn hex_digit<I>() -> HexDigit<I>
    where I: Stream<Item=char> {
    HexDigit(satisfy(static_fn!((ch, char) -> bool { ch.is_digit(0x10) }))
            .expected("hexadecimal digit"), PhantomData)
}

#[derive(Clone)]
pub struct String<I>(&'static str, PhantomData<I>);
impl <I> Parser for String<I>
    where I: Stream<Item=char> {
    type Input = I;
    type Output = &'static str;
    fn parse_state(&mut self, mut input: State<I>) -> ParseResult<&'static str, I> {
        let start = input.position;
        let mut consumed = false;
        for c in self.0.chars() {
            match input.uncons_char() {
                Ok((other, rest)) => {
                    if c != other {
                        let error = ParseError::new(start, Error::Expected(self.0.into_cow()));
                        return Err(if consumed { Consumed::Consumed(error) } else { Consumed::Empty(error) })
                    }
                    consumed = true;
                    input = rest.into_inner();
                }
                Err(error) => {
                    return error.combine(|mut error| {
                        error.position = start;
                        Err(if consumed { Consumed::Consumed(error) } else { Consumed::Empty(error) })
                    })
                }
            }
        }
        Ok((self.0, if consumed { Consumed::Consumed(input) } else { Consumed::Empty(input) }))
    }
}

///Parses the string `s`
///
/// ```
/// # extern crate "parser-combinators" as pc;
/// # use pc::*;
/// # fn main() {
/// let result = string("rust")
///     .parse("rust")
///     .map(|x| x.0);
/// assert_eq!(result, Ok("rust"));
/// # }
/// ```
pub fn string<I>(s: &'static str) -> String<I>
    where I: Stream<Item=char> {
    String(s, PhantomData)
}

#[cfg(test)]
mod tests {
    use super::*;
    use primitives::{Error, Parser, SourcePosition};
    use std::borrow::IntoCow;

    #[test]
    fn space_error() {
        let result = space()
            .parse("");
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().errors, vec![Error::Message("End of input".into_cow()), Error::Expected("whitespace".into_cow())]);

    }

    #[test]
    fn string_consumed() {
        let result = string("a").parse("b");
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().position, SourcePosition { line: 1, column: 1 });
    }
}
<commit_msg>Fix incorrect documentation for `lower`<commit_after>use primitives::{Consumed, Parser, ParseError, ParseResult, Error, State, Stream};
use combinator::{Expected, skip_many, SkipMany, ParserExt, With};
use std::marker::PhantomData;
use std::borrow::IntoCow;

macro_rules! impl_char_parser {
    ($name: ident ($($ty_var: ident),*), $inner_type: ty) => {
    #[derive(Clone)]
    pub struct $name<I $(,$ty_var)*>($inner_type, PhantomData<I>)
        where I: Stream<Item=char> $(, $ty_var : Parser<Input=I>)*;
    impl <I $(,$ty_var)*> Parser for $name<I $(,$ty_var)*>
        where I: Stream<Item=char> $(, $ty_var : Parser<Input=I>)* {
        type Input = I;
        type Output = <$inner_type as Parser>::Output;
        fn parse_state(&mut self, input: State<<Self as Parser>::Input>) -> ParseResult<<Self as Parser>::Output, <Self as Parser>::Input> {
            self.0.parse_state(input)
        }
    }
}
}

///Parses any character
pub fn any_char<I>(input: State<I>) -> ParseResult<char, I>
    where I: Stream<Item=char> {
    input.uncons_char()
}

#[derive(Clone)]
pub struct Satisfy<I, Pred> { pred: Pred, _marker: PhantomData<I> }

impl <I, Pred> Parser for Satisfy<I, Pred>
    where I: Stream<Item=char>, Pred: FnMut(char) -> bool {

    type Input = I;
    type Output = char;
    fn parse_state(&mut self, input: State<I>) -> ParseResult<char, I> {
        match input.clone().uncons_char() {
            Ok((c, s)) => {
                if (self.pred)(c) { Ok((c, s)) }
                else {
                    Err(Consumed::Empty(ParseError::new(input.position, Error::Unexpected(c))))
                }
            }
            Err(err) => Err(err)
        }
    }
}

///Parses a character and succeeds depending on the result of `pred`
///
/// ```
/// # extern crate "parser-combinators" as pc;
/// # use pc::*;
/// # fn main() {
/// let result = satisfy(|c| c == '!')
///     .parse("!")
///     .map(|x| x.0);
/// assert_eq!(result, Ok('!'));
/// # }
/// ```
pub fn satisfy<I, Pred>(pred: Pred) -> Satisfy<I, Pred>
    where I: Stream<Item=char>, Pred: FnMut(char) -> bool {
    Satisfy { pred: pred, _marker: PhantomData }
}

impl_char_parser! { Digit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a digit from a stream containing characters
pub fn digit<I>() -> Digit<I>
    where I: Stream<Item=char> {
    Digit(satisfy(static_fn!((c, char) -> bool { c.is_digit(10) }))
         .expected("digit"), PhantomData)
}

impl_char_parser! { Space(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses whitespace
pub fn space<I>() -> Space<I>
    where I: Stream<Item=char> {
    Space(satisfy(char::is_whitespace as fn (char) -> bool)
        .expected("whitespace"), PhantomData)
}
impl_char_parser! { Spaces(), Expected<SkipMany<Space<I>>> }
///Skips over zero or more spaces
pub fn spaces<I>() -> Spaces<I>
    where I: Stream<Item=char> {
    Spaces(skip_many(space())
          .expected("whitespaces"), PhantomData)
}

impl_char_parser! { NewLine(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a newline character
pub fn newline<I>() -> NewLine<I>
    where I: Stream<Item=char> {
    NewLine(satisfy(static_fn!((ch, char) -> bool { ch == '\n' }))
           .expected("lf newline"), PhantomData)
}

impl_char_parser! { CrLf(), Expected<With<Satisfy<I, fn (char) -> bool>, NewLine<I>>> }
///Parses carriage return and newline, returning the newline character.
pub fn crlf<I>() -> CrLf<I>
    where I: Stream<Item=char> {
    CrLf(satisfy(static_fn!((ch, char) -> bool { ch == '\r' }))
        .with(newline())
        .expected("crlf newline"), PhantomData)
}

impl_char_parser! { Tab(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a tab character
pub fn tab<I>() -> Tab<I>
    where I: Stream<Item=char> {
    Tab(satisfy(static_fn!((ch, char) -> bool { ch == '\t' }))
       .expected("tab"), PhantomData)
}

impl_char_parser! { Upper(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an uppercase letter
pub fn upper<I>() -> Upper<I>
    where I: Stream<Item=char> {
    Upper(satisfy(static_fn!((ch, char) -> bool { ch.is_uppercase()}))
         .expected("uppercase letter"), PhantomData)
}

impl_char_parser! { Lower(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an lowercase letter
pub fn lower<I>() -> Lower<I>
    where I: Stream<Item=char> {
    Lower(satisfy(static_fn!((ch, char) -> bool { ch.is_lowercase() }))
         .expected("lowercase letter"), PhantomData)
}

impl_char_parser! { AlphaNum(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses either an alphabet letter or digit
pub fn alpha_num<I>() -> AlphaNum<I>
    where I: Stream<Item=char> {
    AlphaNum(satisfy(static_fn!((ch, char) -> bool { ch.is_alphanumeric() }))
            .expected("letter or digit"), PhantomData)
}

impl_char_parser! { Letter(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an alphabet letter
pub fn letter<I>() -> Letter<I>
    where I: Stream<Item=char> {
    Letter(satisfy(static_fn!((ch, char) -> bool { ch.is_alphabetic() }))
          .expected("letter"), PhantomData)
}

impl_char_parser! { OctDigit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses an octal digit
pub fn oct_digit<I>() -> OctDigit<I>
    where I: Stream<Item=char> {
    OctDigit(satisfy(static_fn!((ch, char) -> bool { ch.is_digit(8) }))
            .expected("octal digit"), PhantomData)
}

impl_char_parser! { HexDigit(), Expected<Satisfy<I, fn (char) -> bool>> }
///Parses a hexdecimal digit with uppercase and lowercase
pub fn hex_digit<I>() -> HexDigit<I>
    where I: Stream<Item=char> {
    HexDigit(satisfy(static_fn!((ch, char) -> bool { ch.is_digit(0x10) }))
            .expected("hexadecimal digit"), PhantomData)
}

#[derive(Clone)]
pub struct String<I>(&'static str, PhantomData<I>);
impl <I> Parser for String<I>
    where I: Stream<Item=char> {
    type Input = I;
    type Output = &'static str;
    fn parse_state(&mut self, mut input: State<I>) -> ParseResult<&'static str, I> {
        let start = input.position;
        let mut consumed = false;
        for c in self.0.chars() {
            match input.uncons_char() {
                Ok((other, rest)) => {
                    if c != other {
                        let error = ParseError::new(start, Error::Expected(self.0.into_cow()));
                        return Err(if consumed { Consumed::Consumed(error) } else { Consumed::Empty(error) })
                    }
                    consumed = true;
                    input = rest.into_inner();
                }
                Err(error) => {
                    return error.combine(|mut error| {
                        error.position = start;
                        Err(if consumed { Consumed::Consumed(error) } else { Consumed::Empty(error) })
                    })
                }
            }
        }
        Ok((self.0, if consumed { Consumed::Consumed(input) } else { Consumed::Empty(input) }))
    }
}

///Parses the string `s`
///
/// ```
/// # extern crate "parser-combinators" as pc;
/// # use pc::*;
/// # fn main() {
/// let result = string("rust")
///     .parse("rust")
///     .map(|x| x.0);
/// assert_eq!(result, Ok("rust"));
/// # }
/// ```
pub fn string<I>(s: &'static str) -> String<I>
    where I: Stream<Item=char> {
    String(s, PhantomData)
}

#[cfg(test)]
mod tests {
    use super::*;
    use primitives::{Error, Parser, SourcePosition};
    use std::borrow::IntoCow;

    #[test]
    fn space_error() {
        let result = space()
            .parse("");
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().errors, vec![Error::Message("End of input".into_cow()), Error::Expected("whitespace".into_cow())]);

    }

    #[test]
    fn string_consumed() {
        let result = string("a").parse("b");
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().position, SourcePosition { line: 1, column: 1 });
    }
}
<|endoftext|>


<commit_before>#![feature(phase)]

#[phase(plugin, link)]
extern crate itertools;

use std::iter::AdditiveIterator;
use itertools::Itertools;

fn main() {
    println!("24 days of Rust - itertools (day 7)");
    let mut words = "hello supercalifragilisticexpialidocious programmer".words();
    words.apply(|word| println!("{} is {} characters long", word, word.len()));
    let even = range(1, 10u).map(|x| x * 2);
    let odd = range(1, 5u).map(|x| x * 2 + 1);
    println!("{}", even.interleave(odd).collect::<Vec<_>>());
    let it = range(1, 10u);
    println!("{}", it.intersperse(15u).collect::<Vec<_>>());
    let numbers = range(1, 4u);
    let chars = vec!['a', 'b', 'c'];
    for (i, c) in iproduct!(numbers, chars.iter()) {
        println!("{}: {}", i, c);
    }
    let log = "GET / 4096\nGET /home/ 16301\nPOST /home/ 49\nGET / 4096\n";
    let lines = log.lines();
    let rows = icompr!(line.words().collect::<Vec<_>>() for line in lines);
    let bytes = icompr!(row[2] for row in rows if row[0] != "POST");
    let total = icompr!(from_str::<uint>(b).unwrap() for b in bytes).sum();
    println!("Total GET throughput: {} bytes", total);
}
<commit_msg>Fixed deprecated from_str call.<commit_after>#![feature(phase)]

#[phase(plugin, link)]
extern crate itertools;

use std::iter::AdditiveIterator;
use itertools::Itertools;

fn main() {
    println!("24 days of Rust - itertools (day 7)");
    let mut words = "hello supercalifragilisticexpialidocious programmer".words();
    words.apply(|word| println!("{} is {} characters long", word, word.len()));
    let even = range(1, 10u).map(|x| x * 2);
    let odd = range(1, 5u).map(|x| x * 2 + 1);
    println!("{}", even.interleave(odd).collect::<Vec<_>>());
    let it = range(1, 10u);
    println!("{}", it.intersperse(15u).collect::<Vec<_>>());
    let numbers = range(1, 4u);
    let chars = vec!['a', 'b', 'c'];
    for (i, c) in iproduct!(numbers, chars.iter()) {
        println!("{}: {}", i, c);
    }
    let log = "GET / 4096\nGET /home/ 16301\nPOST /home/ 49\nGET / 4096\n";
    let lines = log.lines();
    let rows = icompr!(line.words().collect::<Vec<_>>() for line in lines);
    let bytes = icompr!(row[2] for row in rows if row[0] != "POST");
    let total = icompr!(b.parse::<uint>().unwrap() for b in bytes).sum();
    println!("Total GET throughput: {} bytes", total);
}
<|endoftext|>