<commit_before>#[macro_use] extern crate log;
extern crate env_logger;
extern crate hyper;
extern crate irc;
extern crate rust_playpen;
extern crate rustc_serialize;
extern crate toml;
extern crate url;

use std::fs::File;
use std::io::{self, Read};
use std::str;
use std::sync::Arc;
use std::thread;
use std::u16;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::process;

use hyper::client::Client;
use irc::client::prelude::*;
use rust_playpen::{ReleaseChannel, Cache, PLAYPEN_ENV_VAR_NAME};
use rustc_serialize::json;
use url::form_urlencoded;

static DEFAULT_CHANNEL: ReleaseChannel = ReleaseChannel::Stable;

const ENV: &'static str = "irc";

fn base_env() -> Vec<(String, String)> {
    vec![(PLAYPEN_ENV_VAR_NAME.into(), ENV.into())]
}

fn get_rust_versions(cache: &Cache) -> Vec<String> {
    let mut versions = Vec::new();
    // Note: Keep these in the same order as their discriminant values
    for channel in &[ReleaseChannel::Stable,
                     ReleaseChannel::Beta,
                     ReleaseChannel::Nightly] {
        let (status, output) = cache.exec(*channel,
                                          "rustc",
                                          vec![String::from("-V")],
                                          base_env(),
                                          String::new()).unwrap();
        assert!(status.success(), "couldn't get version (this currently needs to run as root)");
        let version = str::from_utf8(&output).unwrap();
        // Strip the trailing newline
        let version = String::from(version.lines().next().unwrap());
        debug!("got {:?} Rust version: {}", channel, version);
        versions.push(version);
    }

    versions
}

struct Playbot {
    conn: IrcServer,
    rust_versions: Vec<String>,
    shorten_key: String,
    cache: Arc<Cache>,
}

impl Playbot {
    /// Shortens a playpen URL containing the given code.
    ///
    /// Returns the short URL.
    fn pastebin(&self, code: &str) -> hyper::Result<String> {
        let playpen_url = format!("https://play.rust-lang.org/?run=1&code={}",
            form_urlencoded::byte_serialize(code.as_bytes()).collect::<String>());
        let client = Client::new();
        let url = format!(
            "https://api-ssl.bitly.com/v3/shorten?access_token={}&longUrl={}",
            form_urlencoded::byte_serialize(self.shorten_key.as_bytes()).collect::<String>(),
            form_urlencoded::byte_serialize(playpen_url.as_bytes()).collect::<String>());
        let mut response = try!(client.get(&url).send());
        let mut body = String::new();
        try!(response.read_to_string(&mut body));
        let value: json::Json = json::Json::from_str(&body).unwrap();
        let obj = value.as_object().unwrap();
        if obj["status_txt"].as_string().unwrap() == "OK" {
            Ok(String::from(value.find_path(&["data", "url"]).unwrap().as_string().unwrap()))
        } else {
            Err(io::Error::new(io::ErrorKind::Other,
                               format!("server responded with: {}", body)).into())
        }
    }

    fn run_code(&mut self,
                full_code: &str,
                channel: ReleaseChannel)
                -> io::Result<String> {
        let (_status, output) = try!(self.cache.exec(channel,
                                                     "/usr/local/bin/evaluate.sh",
                                                     Vec::new(),
                                                     base_env(),
                                                     String::from(full_code)));

        let output_merged = output.splitn(2, |b| *b == b'\xff')
                                  .map(|sub| String::from_utf8_lossy(sub).into_owned())
                                  .collect::<String>();
        Ok(output_merged)
    }

    /// Parse a command sent to playbot (playbot's name needs to be stripped beforehand)
    ///
    /// Returns the response to send to the user (each line is a NOTICE)
    fn parse_and_run(&mut self, mut code: &str) -> io::Result<String> {
        let mut channel = DEFAULT_CHANNEL;

        if code.starts_with("--") {
            let mut parts = code.splitn(2, ' ');
            match parts.next() {
                Some("--stable") => { channel = ReleaseChannel::Stable },
                Some("--beta") => { channel = ReleaseChannel::Beta },
                Some("--nightly") => { channel = ReleaseChannel::Nightly },
                _ => return Ok(String::from("unrecognized release channel")),
            }
            code = parts.next().unwrap_or("");
        }

        let code = if self.conn.current_nickname().contains("mini") {
            String::from(code)
        } else {
            format!(r#"
#![allow(dead_code, unused_variables)]

static VERSION: &'static str = "{version}";

fn show<T: std::fmt::Debug>(e: T) {{ println!("{{:?}}", e) }}

fn main() {{
    show({{
        {code}
    }});
}}
"#, version = self.rust_versions[channel as usize], code = code)
        };

        let out = try!(self.run_code(&code, channel));
        if out.len() > 5000 {
            return Ok(String::from("output too long, bailing out :("));
        }

        // Print outputs up to 2 lines in length. Above that, print the first line followed by a
        // shortened playpen link.
        let lines: Vec<&str> = out.lines().collect();
        if lines.len() <= 2 {
            return Ok(lines.join("\n"));
        }

        // Take the first line and append the URL
        let response = lines[0];
        Ok(match self.pastebin(&code) {
            Ok(short_url) => format!("{}\n(output truncated; full output at {})",
                                                        response, short_url),
            Err(e) => {
                error!("shortening url failed: {}", e);
                format!("{}\n(output truncated; shortening URL failed)", response)
            }
        })
    }

    fn handle_cmd(&mut self, response_to: &str, msg: &str) {<commit_msg>playbot -- reduce maximum output size to 3000<commit_after>#[macro_use] extern crate log;
extern crate env_logger;
extern crate hyper;
extern crate irc;
extern crate rust_playpen;
extern crate rustc_serialize;
extern crate toml;
extern crate url;

use std::fs::File;
use std::io::{self, Read};
use std::str;
use std::sync::Arc;
use std::thread;
use std::u16;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::process;

use hyper::client::Client;
use irc::client::prelude::*;
use rust_playpen::{ReleaseChannel, Cache, PLAYPEN_ENV_VAR_NAME};
use rustc_serialize::json;
use url::form_urlencoded;

static DEFAULT_CHANNEL: ReleaseChannel = ReleaseChannel::Stable;

const ENV: &'static str = "irc";

fn base_env() -> Vec<(String, String)> {
    vec![(PLAYPEN_ENV_VAR_NAME.into(), ENV.into())]
}

fn get_rust_versions(cache: &Cache) -> Vec<String> {
    let mut versions = Vec::new();
    // Note: Keep these in the same order as their discriminant values
    for channel in &[ReleaseChannel::Stable,
                     ReleaseChannel::Beta,
                     ReleaseChannel::Nightly] {
        let (status, output) = cache.exec(*channel,
                                          "rustc",
                                          vec![String::from("-V")],
                                          base_env(),
                                          String::new()).unwrap();
        assert!(status.success(), "couldn't get version (this currently needs to run as root)");
        let version = str::from_utf8(&output).unwrap();
        // Strip the trailing newline
        let version = String::from(version.lines().next().unwrap());
        debug!("got {:?} Rust version: {}", channel, version);
        versions.push(version);
    }

    versions
}

struct Playbot {
    conn: IrcServer,
    rust_versions: Vec<String>,
    shorten_key: String,
    cache: Arc<Cache>,
}

impl Playbot {
    /// Shortens a playpen URL containing the given code.
    ///
    /// Returns the short URL.
    fn pastebin(&self, code: &str) -> hyper::Result<String> {
        let playpen_url = format!("https://play.rust-lang.org/?run=1&code={}",
            form_urlencoded::byte_serialize(code.as_bytes()).collect::<String>());
        let client = Client::new();
        let url = format!(
            "https://api-ssl.bitly.com/v3/shorten?access_token={}&longUrl={}",
            form_urlencoded::byte_serialize(self.shorten_key.as_bytes()).collect::<String>(),
            form_urlencoded::byte_serialize(playpen_url.as_bytes()).collect::<String>());
        let mut response = try!(client.get(&url).send());
        let mut body = String::new();
        try!(response.read_to_string(&mut body));
        let value: json::Json = json::Json::from_str(&body).unwrap();
        let obj = value.as_object().unwrap();
        if obj["status_txt"].as_string().unwrap() == "OK" {
            Ok(String::from(value.find_path(&["data", "url"]).unwrap().as_string().unwrap()))
        } else {
            Err(io::Error::new(io::ErrorKind::Other,
                               format!("server responded with: {}", body)).into())
        }
    }

    fn run_code(&mut self,
                full_code: &str,
                channel: ReleaseChannel)
                -> io::Result<String> {
        let (_status, output) = try!(self.cache.exec(channel,
                                                     "/usr/local/bin/evaluate.sh",
                                                     Vec::new(),
                                                     base_env(),
                                                     String::from(full_code)));

        let output_merged = output.splitn(2, |b| *b == b'\xff')
                                  .map(|sub| String::from_utf8_lossy(sub).into_owned())
                                  .collect::<String>();
        Ok(output_merged)
    }

    /// Parse a command sent to playbot (playbot's name needs to be stripped beforehand)
    ///
    /// Returns the response to send to the user (each line is a NOTICE)
    fn parse_and_run(&mut self, mut code: &str) -> io::Result<String> {
        let mut channel = DEFAULT_CHANNEL;

        if code.starts_with("--") {
            let mut parts = code.splitn(2, ' ');
            match parts.next() {
                Some("--stable") => { channel = ReleaseChannel::Stable },
                Some("--beta") => { channel = ReleaseChannel::Beta },
                Some("--nightly") => { channel = ReleaseChannel::Nightly },
                _ => return Ok(String::from("unrecognized release channel")),
            }
            code = parts.next().unwrap_or("");
        }

        let code = if self.conn.current_nickname().contains("mini") {
            String::from(code)
        } else {
            format!(r#"
#![allow(dead_code, unused_variables)]

static VERSION: &'static str = "{version}";

fn show<T: std::fmt::Debug>(e: T) {{ println!("{{:?}}", e) }}

fn main() {{
    show({{
        {code}
    }});
}}
"#, version = self.rust_versions[channel as usize], code = code)
        };

        let out = try!(self.run_code(&code, channel));
        if out.len() > 3000 {
            return Ok(String::from("output too long, bailing out :("));
        }

        // Print outputs up to 2 lines in length. Above that, print the first line followed by a
        // shortened playpen link.
        let lines: Vec<&str> = out.lines().collect();
        if lines.len() <= 2 {
            return Ok(lines.join("\n"));
        }

        // Take the first line and append the URL
        let response = lines[0];
        Ok(match self.pastebin(&code) {
            Ok(short_url) => format!("{}\n(output truncated; full output at {})",
                                                        response, short_url),
            Err(e) => {
                error!("shortening url failed: {}", e);
                format!("{}\n(output truncated; shortening URL failed)", response)
            }
        })
    }

    fn handle_cmd(&mut self, response_to: &str, msg: &str) {
{'fraction_non_alphanumeric': 0.15210893390900032, 'fraction_numerical': 0.0028229823978744603, 'mean_word_length': 2.0127563781890947, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 53, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 40, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1633676', 'n_tokens_mistral': 3397, 'n_tokens_neox': 3330, 'n_words': 802}