<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server = IrcServer::new("config.json").unwrap();
    server.identify().unwrap();

    let empty_map = HashMap::new();
    let options = if let Some(ref opts) = server.config().options {
        opts
    } else {
        &empty_map
    };

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server, &line.message, source, false, None)
                        } else if target.starts_with('#') {
                            info!("[{}] {}", target, line);
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data
                                           .entry(target.clone())
                                           .or_insert_with(|| ChannelData::new(&options))
                                           .add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = match response_is_action {
            false => line_with_nick,
            true => format!("\x01ACTION {}\x01", line_with_nick),
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        if self.current_topic.is_some() {
            self.end_topic();
        }

        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments.create(&CommentOptions { body: comment_text });
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>Silence warning.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let server = IrcServer::new("config.json").unwrap();
    server.identify().unwrap();

    let empty_map = HashMap::new();
    let options = if let Some(ref opts) = server.config().options {
        opts
    } else {
        &empty_map
    };

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server, &line.message, source, false, None)
                        } else if target.starts_with('#') {
                            info!("[{}] {}", target, line);
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data
                                           .entry(target.clone())
                                           .or_insert_with(|| ChannelData::new(&options))
                                           .add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = match response_is_action {
            false => line_with_nick,
            true => format!("\x01ACTION {}\x01", line_with_nick),
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        if self.current_topic.is_some() {
            self.end_topic();
        }

        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>#![feature(lang_items)]
#![feature(asm)]
#![feature(stmt_expr_attributes)]
#![feature(type_macros)]
#![feature(associated_consts)]
#![feature(braced_empty_structs)] // XXX: For now
#![feature(start)]
//#![feature(core_str_ext)]
//#![feature(ptr_as_ref)]
#![no_std]
#![allow(dead_code)]              // XXX: For now, because a lot of unused structs
extern crate rlibc;

mod xen;

pub use xen::poweroff;
pub use xen::console_io::STDOUT;
use xen::start_info::start_info_page;
use core::fmt::Write;

#[lang = "eh_personality"]
extern fn eh_personality() {}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_unwind(_fmt: core::fmt::Arguments, _file_line: &(&'static str, u32)) -> ! {
    xen::emergency_console::print(b"panic_fmt!\n\0");
    xen::crash();
}

fn print_init_info(){
    unsafe {
        let _ = writeln!(STDOUT, "Magic: {}", core::str::from_utf8(&(*start_info_page).magic).unwrap_or("ERROR"));
        let _ = writeln!(STDOUT, "nr_pages: {:#X}", &(*start_info_page).nr_pages);
        let _ = writeln!(STDOUT, "shared_info: {:#X}", &(*start_info_page).shared_info);
    }
}

#[start]
pub fn main(_argc: isize, _argv: *const *const u8) -> isize {
    xen::emergency_console::print(b"main!\n\0");
    let _ = writeln!(STDOUT, "Hello world!");
    print_init_info();
    0
}
<commit_msg>No need for unsafe anymore<commit_after>#![feature(lang_items)]
#![feature(asm)]
#![feature(stmt_expr_attributes)]
#![feature(type_macros)]
#![feature(associated_consts)]
#![feature(braced_empty_structs)] // XXX: For now
#![feature(start)]
//#![feature(core_str_ext)]
//#![feature(ptr_as_ref)]
#![no_std]
#![allow(dead_code)]              // XXX: For now, because a lot of unused structs
extern crate rlibc;

mod xen;

pub use xen::poweroff;
pub use xen::console_io::STDOUT;
use xen::start_info::start_info_page;
use core::fmt::Write;

#[lang = "eh_personality"]
extern fn eh_personality() {}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_unwind(_fmt: core::fmt::Arguments, _file_line: &(&'static str, u32)) -> ! {
    xen::emergency_console::print(b"panic_fmt!\n\0");
    xen::crash();
}

fn print_init_info(){
    let _ = writeln!(STDOUT, "Magic: {}", core::str::from_utf8(&start_info_page.magic).unwrap_or("ERROR"));
    let _ = writeln!(STDOUT, "nr_pages: {:#X}", start_info_page.nr_pages);
    let _ = writeln!(STDOUT, "shared_info: {:#X}", start_info_page.shared_info);
}

#[start]
pub fn main(_argc: isize, _argv: *const *const u8) -> isize {
    xen::emergency_console::print(b"main!\n\0");
    let _ = writeln!(STDOUT, "Hello world!");
    print_init_info();
    0
}
<|endoftext|>


<commit_before>extern crate toml;

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::collections::BTreeMap;

fn strip_toolchain(name: &toml::Value) -> String {
    name.as_str().unwrap().split('-').nth(0).unwrap().to_string()
}

fn read_settings() -> (String, BTreeMap<String, toml::Value>) {
    let mut settings_path = env::home_dir().unwrap();
    settings_path.push(".multirust/settings.toml");

    let mut file = File::open(&settings_path).expect("file open error");

    let mut buffer = String::new();
    file.read_to_string(&mut buffer).expect("read error");

    let value = toml::Parser::new(&buffer).parse().unwrap();
    let default = value.get("default_toolchain").unwrap().as_str().unwrap();
    let overrides = value.get("overrides").unwrap().as_table().unwrap();

    (default.to_owned(), overrides.to_owned())
}

fn main() {
    let cwd = env::current_dir().unwrap();
    let (default, overrides) = read_settings();

    let toolchain = match overrides.get(cwd.to_str().unwrap()) {
        Some(toolchain) => strip_toolchain(toolchain),
        None => default,
    };

    println!("{}", toolchain);
}
<commit_msg>Better error messages with expect instead of unwrap<commit_after>extern crate toml;

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::collections::BTreeMap;

fn strip_toolchain(name: &toml::Value) -> String {
    name.as_str().unwrap().split('-').nth(0).unwrap().to_string()
}

fn read_settings() -> (String, BTreeMap<String, toml::Value>) {
    let mut settings_path = env::home_dir().expect("Cannot get home directory");
    settings_path.push(".multirust/settings.toml");

    let mut file = File::open(&settings_path).expect("Error opening settings.toml file");

    let mut buffer = String::new();
    file.read_to_string(&mut buffer).expect("Error reading settings.toml");

    let value = toml::Parser::new(&buffer).parse().expect("Error parsing settings.toml");
    let default = value.get("default_toolchain").expect("Did not found default_toolchain in settings.toml").as_str().unwrap();
    let overrides = value.get("overrides").expect("Did not found overrides in settings.toml").as_table().unwrap();

    (default.to_owned(), overrides.to_owned())
}

fn main() {
    let cwd = env::current_dir().expect("Cannot get working directory");
    let cwd = cwd.to_str().expect("Cannot convert working directory to string");
    let (default, overrides) = read_settings();

    let toolchain = if let Some(toolchain) = overrides.get(cwd) {
        strip_toolchain(toolchain)
    } else {
        default
    };

    println!("{}", toolchain);
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate "rust-md2" as md2;

use md2::{S, S2, md2_compress};
use std::collections::HashMap;

pub fn main() {
    let rows = 14;
    let mut values = [[0u8, ..49], ..19];

    for row in range(1, rows + 1) {
      // Fill row of T1.
      for i in range(1, 17) {
          values[row][i] = S[values[row][i - 1] as uint] ^ values[row - 1][i];
      }

      // Last bytes are equal.
      values[row][32] = values[row][16];
      values[row][48] = values[row][16];

      // Next t value.
      values[row + 1][0] = values[row][48] + (row as u8) - 1;
    }

    // Compute triangles.
    for col in range(0, rows) {
      for row in range(0, rows - col - 1) {
        let row2 = rows - row;
        values[row2][32 - col - 1] = S2[(values[row2][32 - col] ^ values[row2 - 1][32 - col]) as uint];
        values[row2][48 - col - 1] = S2[(values[row2][48 - col] ^ values[row2 - 1][48 - col]) as uint];
      }
    }

    let mut collisions: HashMap<(u8,u8,u8),Vec<u16>> = HashMap::new();

    // TODO random bytes
    for bytes in range(0u32, 256 * 256) {
        // TODO set bytes
        values[14][17] = (bytes >> 8) as u8;
        values[14][18] = bytes as u8;
        values[14][33] = (bytes >> 8) as u8;
        values[14][34] = bytes as u8;

        for row in range(rows + 1, rows + 4) {
            // Fill row.
            for i in range(1, 49) {
                values[row][i] = S[values[row][i - 1] as uint] ^ values[row - 1][i];
            }

            // Next t value.
            values[row + 1][0] = values[row][48] + (row as u8) - 1;
        }

        let key = (values[16][0], values[17][0], values[18][0]);

        if !collisions.contains_key(&key) {
            collisions.insert(key, vec!());
        }

        match collisions.get_mut(&key) {
            Some(vec) => vec.push(bytes as u16),
            None => panic!("unreachable")
        }
    }

    // Compute original messages for each collision.
    let mut count = 0u;
    for collision in collisions.values().filter(|x| x.len() > 1) {
        count += 1;
        for bytes in collision.iter() {
            // TODO set bytes
            values[14][17] = (*bytes >> 8) as u8;
            values[14][18] = *bytes as u8;
            values[14][33] = (*bytes >> 8) as u8;
            values[14][34] = *bytes as u8;

            // Fill upper rectangles.
            for row in range(0, 14) {
                let row2 = rows - row;
                for col in range(17, 32 - row2 + 2) {
                    values[row2 - 1][col] = S[values[row2][col - 1] as uint] ^ values[row2][col];
                }
            }

            let empty = [0u8, ..16];
            let msg = values[0].slice(17, 33).iter().fold(String::new(), |a, &b| format!("{}{:02x}", a, b));
            let hash = md2_compress(&empty, values[0].slice(17, 33)).iter().fold(String::new(), |a, &b| format!("{}{:02x}", a, b));
            println!("msg = {}, hash = {}", msg, hash);
        }

        println!("");
    }

    println!("Found {} collisions.", count);
}
<commit_msg>make collision finding more generic wrt to k<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

extern crate "rust-md2" as md2;

use md2::{S, S2, md2_compress};
use std::collections::HashMap;
use std::num::Int;

pub fn main() {
    let rows = 14;
    let mut values = [[0u8, ..49], ..19];

    for row in range(1, rows + 1) {
      // Fill row of T1.
      for i in range(1, 17) {
          values[row][i] = S[values[row][i - 1] as uint] ^ values[row - 1][i];
      }

      // Last bytes are equal.
      values[row][32] = values[row][16];
      values[row][48] = values[row][16];

      // Next t value.
      values[row + 1][0] = values[row][48] + (row as u8) - 1;
    }

    // Compute triangles.
    for col in range(0, rows) {
      for row in range(0, rows - col - 1) {
        let row2 = rows - row;
        values[row2][32 - col - 1] = S2[(values[row2][32 - col] ^ values[row2 - 1][32 - col]) as uint];
        values[row2][48 - col - 1] = S2[(values[row2][48 - col] ^ values[row2 - 1][48 - col]) as uint];
      }
    }

    let mut collisions: HashMap<u64,Vec<u64>> = HashMap::new();

    // TODO random bytes
    for bytes in range(0u64, 256u64.pow(16 - rows)) {
        // TODO set bytes
        let mut shift = (16 - rows - 1) * 8;
        for col in range(17, 17 + 16 - rows) {
            values[rows][col] = (bytes >> shift) as u8;
            values[rows][col + 16] = (bytes >> shift) as u8;
            shift -= 8;
        }

        for row in range(rows + 1, 18) {
            // Fill row.
            for i in range(1, 49) {
                values[row][i] = S[values[row][i - 1] as uint] ^ values[row - 1][i];
            }

            // Next t value.
            values[row + 1][0] = values[row][48] + (row as u8) - 1;
        }

        let mut key = 0u64;
        let mut shift = (16 - rows) * 8;
        for row in range(rows + 2, 19) {
            key |= (values[row][0] as u64) << shift;
            shift -= 8;
        }

        if !collisions.contains_key(&key) {
            collisions.insert(key, vec!());
        }

        match collisions.get_mut(&key) {
            Some(vec) => vec.push(bytes),
            None => panic!("unreachable")
        }
    }

    // Compute original messages for each collision.
    let mut count = 0u;
    for collision in collisions.values().filter(|x| x.len() > 1) {
        count += 1;
        for bytes in collision.iter() {
            // TODO set bytes
            let mut shift = (16 - rows - 1) * 8;
            for col in range(17, 17 + 16 - rows) {
                values[rows][col] = (*bytes >> shift) as u8;
                values[rows][col + 16] = (*bytes >> shift) as u8;
                shift -= 8;
            }

            // Fill upper rectangles.
            for row in range(0, rows) {
                let row2 = rows - row;
                for col in range(17, 32 - row2 + 2) {
                    values[row2 - 1][col] = S[values[row2][col - 1] as uint] ^ values[row2][col];
                }
            }

            let empty = [0u8, ..16];
            let msg = values[0].slice(17, 33).iter().fold(String::new(), |a, &b| format!("{}{:02x}", a, b));
            let hash = md2_compress(&empty, values[0].slice(17, 33)).iter().fold(String::new(), |a, &b| format!("{}{:02x}", a, b));
            println!("msg = {}, hash = {}", msg, hash);
        }

        println!("");
    }

    println!("Found {} collisions.", count);
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

fn main() {
    println!("Hello, world!");
}
<commit_msg>main: simple cli mimicking golang coreos-metadata<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate clap;
#[macro_use]
extern crate slog;
extern crate slog_term;
extern crate slog_async;
#[macro_use]
extern crate slog_scope;

use std::fs::File;
use std::io::prelude::*;
use clap::{Arg, App};
use slog::Drain;

const CMDLINE_PATH: &'static str = "/proc/cmdline";
const CMDLINE_OEM_FLAG:&'static str = "coreos.oem.id";

#[derive(Debug)]
struct Config {
    provider: String,
    attributes_file: Option<String>,
    ssh_keys_user: Option<String>,
    hostname_file: Option<String>,
    network_units_dir: Option<String>,
}

fn main() {
    // setup logging
    let decorator = slog_term::TermDecorator::new().build();
    let drain = slog_term::FullFormat::new(decorator).build().fuse();
    let drain = slog_async::Async::new(drain).build().fuse();
    let log = slog::Logger::root(drain, slog_o!());
    let _guard = slog_scope::set_global_logger(log);

    debug!("Logging initialized");

    let config = init().unwrap();
    info!("{:?}", config)
}

fn init() -> Result<Config, String> {
    // setup cli
    let matches = App::new("coreos-metadata")
        .version(crate_version!())
        .arg(Arg::with_name("attributes")
             .long("attributes")
             .help("The file into which the metadata attributes are written")
             .takes_value(true))
        .arg(Arg::with_name("cmdline")
             .long("cmdline")
             .help("Read the cloud provider from the kernel cmdline"))
        .arg(Arg::with_name("hostname")
             .long("hostname")
             .help("The file into which the hostname should be written")
             .takes_value(true))
        .arg(Arg::with_name("network-units")
             .long("network-units")
             .help("The directory into which network units are written")
             .takes_value(true))
        .arg(Arg::with_name("provider")
             .long("provider")
             .help("The name of the cloud provider")
             .takes_value(true))
        .arg(Arg::with_name("ssh-keys")
             .long("ssh-keys")
             .help("Update SSH keys for the given user")
             .takes_value(true))
        .get_matches();

    // return configuration
    Ok(Config {
        provider: match matches.value_of("provider") {
            Some(provider) => String::from(provider),
            None => if matches.is_present("cmdline") {
                get_oem()?
            } else {
                return Err("Must set either --provider or --cmdline".to_string());
            }
        },
        attributes_file: matches.value_of("attributes").map(String::from),
        ssh_keys_user: matches.value_of("ssh-keys").map(String::from),
        hostname_file: matches.value_of("hostname").map(String::from),
        network_units_dir: matches.value_of("network-units").map(String::from),
    })
}

fn get_oem() -> Result<String, String> {
    // open the cmdline file
    let mut file = File::open(CMDLINE_PATH)
        .map_err(|err| format!("Failed to open cmdline file ({}) - {}", CMDLINE_PATH, err))?;

    // read the contents
    let mut contents = String::new();
    file.read_to_string(&mut contents)
        .map_err(|err| format!("Failed to read cmdline file ({}) - {}", CMDLINE_PATH, err))?;

    // split the contents into elements
    let params: Vec<Vec<&str>> = contents.split(' ')
        .map(|s| s.split('=').collect())
        .collect();

    // find the oem flag
    for p in params {
        if p.len() > 1 && p[0] == CMDLINE_OEM_FLAG {
            return Ok(String::from(p[1]));
        }
    }

    // if we didn't find the oem flag then explode
    // panic!("Couldn't find '{}' flag in cmdline file ({})", CMDLINE_OEM_FLAG, CMDLINE_PATH)
    Err(format!("Couldn't find '{}' flag in cmdline file ({})", CMDLINE_OEM_FLAG, CMDLINE_PATH))
}
<|endoftext|>


<commit_before>//! BSPC command to control BSPWM

extern crate libc;
extern crate unix_socket;

use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use std::env;
use std::io::{Read, Write};
use std::process;
use unix_socket::UnixStream;

const BUFSIZ: usize = libc::BUFSIZ as usize;
const FAILURE_MESSAGE: i32 = 7;
const SOCKET_ENV_VAR: &'static str = "BSPWM_SOCKET";

/// Prints error message and quits with error code.
///
/// # Examples
///
/// ```
/// err("Failed to do something important.", 4);
/// ```
fn err(message: &str, code: i32) {
    println!("{}", message);
    process::exit(code);
}

fn socket_file() -> String {
    format!("/tmp/bspwm{}_{}_{}-socket", "", 0, 0)
}

fn main() {
    let message = env::args().skip(1).collect::<Vec<String>>();
    if message.len() < 1 {
        err("No arguments given.", 1);
    }

    let mut i = 0;
    let mut buffer = [0; BUFSIZ];
    for string in message {
        for c in string.chars() {
            buffer[i] = c as u8;
            i += 1;
        }

        buffer[i] = 0;
        i += 1;
    }

    // An environment variable can be set by bspwm (or the user) to tell bspc
    // where the socket file is. If the variable is set, use it. If not, use
    // the new scheme.
    let stream_file = env::var(SOCKET_ENV_VAR)
                          .unwrap_or(socket_file());

    let stream = UnixStream::connect(stream_file);
    if stream.is_err() {
        err("Failed to connect to the socket.", 1);
    }

    let mut stream = stream.unwrap();

    stream.write(&buffer[..i]).unwrap();
    let size = stream.read(&mut buffer).unwrap();
    drop(stream);

    // Force a null character at the end of the message.
    buffer[size] = 0;

    let (status, offset) = match buffer[0] as i32 {
        FAILURE_MESSAGE => (EXIT_FAILURE, 1),
        _ => (EXIT_SUCCESS, 0),
    };

    let message = String::from_utf8_lossy(&buffer[offset..size]);
    print!("{}", message);

    process::exit(status);
}
<commit_msg>Refract<commit_after>//! BSPC command to control BSPWM

extern crate libc;
extern crate unix_socket;

use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use std::env;
use std::io::{Read, Write};
use std::process;
use unix_socket::UnixStream;

const BUFSIZ: usize = libc::BUFSIZ as usize;
const FAILURE_MESSAGE: i32 = 7;
const SOCKET_ENV_VAR: &'static str = "BSPWM_SOCKET";

/// Prints error message and quits with error code.
///
/// # Examples
///
/// ```
/// err("Failed to do something important.", 4);
/// ```
fn err(message: &str, code: i32) {
    println!("{}", message);
    process::exit(code);
}

fn socket_file() -> String {
    format!("/tmp/bspwm{}_{}_{}-socket", "", 0, 0)
}

fn main() {
    let message = env::args().skip(1).collect::<Vec<String>>();
    if message.len() < 1 {
        err("No arguments given.", 1);
    }

    let mut i = 0;
    let mut buffer = [0; BUFSIZ];
    for string in message {
        for c in string.chars() {
            buffer[i] = c as u8;
            i += 1;
        }

        buffer[i] = 0;
        i += 1;
    }

    // An environment variable can be set by bspwm (or the user) to tell bspc
    // where the socket file is. If the variable is set, use it. If not, use
    // the new scheme.
    let stream_file = env::var(SOCKET_ENV_VAR)
                          .unwrap_or(socket_file());

    let mut stream = {
        let maybe_stream = UnixStream::connect(stream_file);

        if let Err(_) = maybe_stream {
            err("Failed to connect to the socket.", 1);
        }

        maybe_stream.unwrap()
    };

    stream.write(&buffer[..i]).unwrap();
    let size = stream.read(&mut buffer).unwrap();
    drop(stream);

    // Force a null character at the end of the message.
    buffer[size] = 0;

    let (status, offset) = match buffer[0] as i32 {
        FAILURE_MESSAGE => (EXIT_FAILURE, 1),
        _ => (EXIT_SUCCESS, 0),
    };

    let message = String::from_utf8_lossy(&buffer[offset..size]);
    print!("{}", message);

    process::exit(status);
}
<|endoftext|>


<commit_before>use std::fs;
use std::fs::File;
use std::io::SeekFrom;
use std::io::Seek;
use std::io::Read;
use std::io::Write;
use std::io::Take;
use std::io::Error;
use std::io::ErrorKind;
use std::collections::HashMap;
use std::env;
use std::path::Path;

extern crate regex;
use regex::Regex;

static __VERSION__: &str = "0.1.4";

// Errors
static E_INVALIDHDR: &str = "Input file header mismatch.";
static E_INVALIDVER: &str = "Not supported version.";
static E_INVALIDMGC: &str = "Magic number read failed.";
static E_INVALIDKEY: &str = "Key not found.";

fn advance_magic(magic: &mut u32) -> u32 {
    let old = *magic;
    *magic = magic.wrapping_mul(7).wrapping_add(3);
    old
}

fn ru32(stream: &mut File, result: &mut u32) -> bool {
    let mut buf = [0; 4];
    if stream.read_exact(&mut buf).is_err() {
        return false;
    };
    *result = u32::from_le_bytes(buf);
    true
}

fn wu32(stream: &mut File, data: &u32) -> bool {
    let buf = data.to_le_bytes();
    stream.write_all(&buf).is_ok()
}

/// Calls read until the buffer is full or EOF.
fn read_until_full(stream: &mut Take<File>, buf: &mut [u8]) -> Result<usize, Error> {
    let mut nb = 0;
    loop {
        match stream.read(&mut buf[nb..]) {
            Ok(0) => return Ok(nb),
            Ok(n) => nb += n,
            Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
            Err(e) => return Err(e),
        }
    }
}

struct EntryData {
    offset: u32,
    magic: u32,
    size: u32,
}

struct Entry {
    offset: u32,
    magic: u32,
    stream: Take<File>,
}

impl Entry {
    fn write(&mut self, buf: &Take<File>) {

    }

    fn read(&mut self, buf: &mut [u8]) -> usize {
        let count = read_until_full(&mut self.stream, buf).unwrap();
        if count == 0 { return 0; }
        let buf = &mut buf[..count];

        let (prefix, middle, suffix) = unsafe { buf.align_to_mut::<u32>() };
        assert!(prefix.len() == 0); // assume buf is aligned
        assert!(self.offset % 4 == 0);

        for i in 0..middle.len() {
            let mut w = u32::from_le(middle[i]);
            w ^= advance_magic(&mut self.magic);
            middle[i] = w.to_le();
        }

        for i in 0..suffix.len() {
            suffix[i] ^= (self.magic >> (i * 8)) as u8;
        }

        self.offset += count as u32;
        count
    }
}

struct RGSSArchive {
    magic: u32,
    version: u8,
    entry: HashMap<String, EntryData>,
    stream: File,
}

impl RGSSArchive {
    fn create(location: &str, version: u8) -> Result<Self, Error> {
        let mut stream = File::create(location)?;
        if version < 1 || version > 3 {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDVER));
        }

        stream.write_all(&[b'R', b'G', b'S', b'S', b'A', b'D', version])?;

        let magic = if version == 3 { 0u32 } else { 0xDEADCAFEu32 };
        let entry = HashMap::<String, EntryData>::new();

        Ok(RGSSArchive { magic, version, entry, stream })
    }

    fn open(location: &str) -> Result<Self, Error> {
        let mut stream = File::open(location)?;

        let mut header = [0u8; 8];
        stream.read_exact(&mut header)?;

        if &header[..6] != b"RGSSAD" {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDHDR));
        }

        // Check rgssad file version.
        return match header[7] {
            1|2 => RGSSArchive::open_rgssad(stream, header[7]),
              3 => RGSSArchive::open_rgss3a(stream, header[7]),
              _ => Err(Error::new(ErrorKind::InvalidData, E_INVALIDVER)),
        }
    }

    fn open_rgssad(mut stream: File, version: u8) -> Result<Self, Error> {
        let mut magic = 0xDEADCAFEu32;
        let mut entry = HashMap::new();

        loop {
            let mut name_len: u32 = 0;
            if !ru32(&mut stream, &mut name_len) { break }
            name_len ^= advance_magic(&mut magic);

            let mut name_buf = vec![0u8; name_len as usize];
            stream.read_exact(&mut name_buf)?;
            for i in 0..(name_len as usize) {
                name_buf[i] ^= advance_magic(&mut magic) as u8;
                if name_buf[i] == b'\\' { name_buf[i] = b'/' }
            }
            let name_buf = String::from_utf8(name_buf);
            if name_buf.is_err() { break }
            let name_buf = name_buf.unwrap();

            let mut data = EntryData { size: 0, offset: 0, magic: 0 };
            if !ru32(&mut stream, &mut data.size) { break }
            data.size ^= advance_magic(&mut magic);
            data.offset = stream.seek(SeekFrom::Current(0))? as u32;
            data.magic = magic;

            stream.seek(SeekFrom::Current(data.size as i64))?;
            entry.insert(name_buf, data);
        }

        stream.seek(SeekFrom::Start(0))?;
        Ok(RGSSArchive { magic, version, entry, stream })
    }

    fn open_rgss3a(mut stream: File, version: u8) -> Result<Self, Error> {
        let mut magic = 0u32;
        let mut entry = HashMap::new();

        if !ru32(&mut stream, &mut magic) {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDMGC));
        }
        magic = magic.wrapping_mul(9).wrapping_add(3);

        loop {
            let mut offset: u32 = 0;
            let mut size: u32 = 0;
            let mut start_magic: u32 = 0;
            let mut name_len: u32 = 0;

            if !ru32(&mut stream, &mut offset) { break };
            offset ^= magic;

            if offset == 0 { break }

            if !ru32(&mut stream, &mut size) { break }
            size ^= magic;

            if !ru32(&mut stream, &mut start_magic) { break}
            start_magic ^= magic;

            if !ru32(&mut stream, &mut name_len) { break }
            name_len ^= magic;

            let mut name_buf = vec![0u8; name_len as usize];
            stream.read_exact(&mut name_buf)?;
            for i in 0..(name_len as usize) {
                name_buf[i] ^= (magic >> 8*(i%4)) as u8;
                if name_buf[i] == b'\\' { name_buf[i] = b'/' }
            }
            let name_buf = String::from_utf8(name_buf);
            if name_buf.is_err() { break }
            let name_buf = name_buf.unwrap();

            let data = EntryData { size, offset, magic: start_magic };

            entry.insert(name_buf, data);
        }

        stream.seek(SeekFrom::Start(0))?;
        Ok(RGSSArchive { magic, version, entry, stream })
    }

    fn get_key(&self, key: &str) -> Result<Entry, Error> {
        match self.entry.get(key) {
            Some(entry) => {
                let mut stream = self.stream.try_clone()?;
                stream.seek(SeekFrom::Start(entry.offset as u64))?;
                Ok(Entry {
                    offset: 0,
                    magic: entry.magic,
                    stream: stream.take(entry.size as u64),
                })
            }
            None => Err(Error::new(ErrorKind::InvalidData, E_INVALIDKEY)),
        }
    }

    // fn put_key(&self, key: &str, stream: &mut File) -> Result<Entry, Error> {
    //     match self.version {
    //         1|2 => self.put_key_rgssad(stream),
    //           3 => self.put_key_rgss3a(stream),
    //     }
    // }
}

fn usage() {
    println!("Extract rgssad/rgss2a/rgss3a files.
Commands:
    help
    version
    list        <filename>
    unpack      <filename> <location> [<filter>]");
}

fn list(archive: RGSSArchive) {
    for (name, data) in archive.entry {
        println!("{}: EntryData {{ size: {}, offset: {}, magic: {} }}", name, data.size, data.offset, data.magic);
    }
}

fn pack(src: &str, out: &str, version: u8) {
    fn walkdir(archive: &mut RGSSArchive, d: &Path, r: &Path) {
        for entry in fs::read_dir(&d).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            if path.is_dir() {
                walkdir(archive, &path, r);
            } else {
                let key = path.strip_prefix(r).unwrap().to_str().unwrap();
                let mut stream = File::open(&path).unwrap();

                // TODO: implement this.
                //archive.put_key(key, stream);
            }
        }
    };

    let root = Path::new(src);
    if !root.is_dir() {
        println!("FAILED: source is not a directory."); return
    }

    let mut archive = match RGSSArchive::create(out, version) {
        Ok(x) => x,
        Err(e) => {
            println!("FAILED: unable to create output file. {}", e); return
        }
    };
    walkdir(&mut archive, root, root);
}

fn unpack(archive: RGSSArchive, dir: &str, filter: &str) {
    fn create(location: String) -> File {
        let path = Path::new(location.as_str());
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        File::create(path.to_str().unwrap()).unwrap()
    }

    let entries = archive.entry.iter();
    let filter = match Regex::new(filter) {
        Ok(re) => re,
        Err(_) => {
            println!("FAILED: Invalid regex filter: {}", filter); return
        }
    };

    let mut buf = [0u8; 8192];

    for (name, _) in entries {
        if !filter.is_match(name) { continue }

        println!("Extracting: {}", name);
        let entry = archive.get_key(name);
        if let Err(err) = entry {
            println!("FAILED: read entry failed, {}", err.to_string()); return;
        }
        let mut entry = entry.unwrap();

        let mut file = create(format!("{}/{}", dir, name));
        loop {
            let count = entry.read(&mut buf);
            if count == 0 { break }
            if let Err(err) = file.write(&buf[..count]) {
                println!("FAILED: key save failed, {}", err.to_string()); return;
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 { usage(); return }
    match args[1].as_str() {
        "help" => usage(),
        "version" => {
            assert!(args.len() == 2);
            println!("version: {}", __VERSION__);
        },
        "list" => {
            assert!(args.len() == 3);
            let archive = RGSSArchive::open(args[2].as_str());
            if let Err(err) = archive {
                println!("FAILED: file parse failed, {}", err.to_string()); return;
            }
            let archive = archive.unwrap();

            list(archive);
        },
        "unpack" => {
            assert!(args.len() > 3 && args.len() < 6);
            let archive = RGSSArchive::open(args[2].as_str());
            if let Err(err) = archive {
                println!("FAILED: file parse failed, {}", err.to_string()); return;
            }
            let archive = archive.unwrap();
            unpack(archive, args[3].as_str(), if args.len() == 5 { args[4].as_str() } else { ".*" });
        },
        "pack" => {
            assert!(args.len() > 3 && args.len() < 6);
            let mut version = 1u8;
            if args.len() == 5 {
                version = match args[4].parse() {
                    Ok(v) => v,
                    Err(_) => {
                        println!("FAILED: {}", E_INVALIDVER); return
                    }
                }
            };
            pack(args[2].as_str(), args[3].as_str(), version);
        },
        _ => usage(),
    }
}
<commit_msg>Add Coder<commit_after>use std::fs;
use std::fs::File;
use std::io::SeekFrom;
use std::io::Seek;
use std::io::Read;
use std::io::Write;
use std::io::Error;
use std::io::ErrorKind;
use std::collections::HashMap;
use std::env;
use std::path::Path;

extern crate regex;
use regex::Regex;

static __VERSION__: &str = "0.1.4";

// Errors
static E_INVALIDHDR: &str = "Input file header mismatch.";
static E_INVALIDVER: &str = "Not supported version.";
static E_INVALIDMGC: &str = "Magic number read failed.";

fn advance_magic(magic: &mut u32) -> u32 {
    let old = *magic;
    *magic = magic.wrapping_mul(7).wrapping_add(3);
    old
}

fn ru32(stream: &mut File, result: &mut u32) -> bool {
    let mut buf = [0; 4];
    if stream.read_exact(&mut buf).is_err() {
        return false;
    };
    *result = u32::from_le_bytes(buf);
    true
}

fn wu32(stream: &mut File, data: &u32) -> bool {
    let buf = data.to_le_bytes();
    stream.write_all(&buf).is_ok()
}

/// Calls read until the buffer is full or EOF.
fn read_until_full(stream: &mut File, buf: &mut [u8]) -> Result<usize, Error> {
    let mut nb = 0;
    loop {
        match stream.read(&mut buf[nb..]) {
            Ok(0) => return Ok(nb),
            Ok(n) => nb += n,
            Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
            Err(e) => return Err(e),
        }
    }
}

struct EntryData {
    offset: u32,
    magic: u32,
    size: u32,
}

struct Coder {
    buf: Vec<u8>,
}

impl Coder {
    /// Encrypts/decrypts file data from stream_in to stream_out.
    fn copy(
        &mut self,
        stream_in: &mut File,
        stream_out: &mut File,
        data: &EntryData,
    ) -> Result<(), Error> {
        assert!(self.buf.len() % 4 == 0); // needed for alignment

        stream_in.seek(SeekFrom::Start(data.offset as u64))?;

        let mut magic = data.magic;
        let mut size = data.size; // remaining bytes to read
        loop {
            let limit = self.buf.len().min(size as usize);
            let count = read_until_full(stream_in, &mut self.buf[..limit])?;
            if count == 0 { return Ok(()) }
            let buf = &mut self.buf[..count];

            let (prefix, middle, suffix) = unsafe { buf.align_to_mut::<u32>() };
            assert!(prefix.len() == 0); // assume buf is aligned

            for i in 0..middle.len() {
                let mut w = u32::from_le(middle[i]);
                w ^= advance_magic(&mut magic);
                middle[i] = w.to_le();
            }

            for i in 0..suffix.len() {
                suffix[i] ^= (magic >> (i * 8)) as u8;
            }

            size -= count as u32;
            stream_out.write_all(buf)?;
        }
    }
}

struct RGSSArchive {
    magic: u32,
    version: u8,
    entry: HashMap<String, EntryData>,
    stream: File,
}

impl RGSSArchive {
    fn create(location: &str, version: u8) -> Result<Self, Error> {
        let mut stream = File::create(location)?;
        if version < 1 || version > 3 {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDVER));
        }

        stream.write_all(&[b'R', b'G', b'S', b'S', b'A', b'D', version])?;

        let magic = if version == 3 { 0u32 } else { 0xDEADCAFEu32 };
        let entry = HashMap::<String, EntryData>::new();

        Ok(RGSSArchive { magic, version, entry, stream })
    }

    fn open(location: &str) -> Result<Self, Error> {
        let mut stream = File::open(location)?;

        let mut header = [0u8; 8];
        stream.read_exact(&mut header)?;

        if &header[..6] != b"RGSSAD" {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDHDR));
        }

        // Check rgssad file version.
        return match header[7] {
            1|2 => RGSSArchive::open_rgssad(stream, header[7]),
              3 => RGSSArchive::open_rgss3a(stream, header[7]),
              _ => Err(Error::new(ErrorKind::InvalidData, E_INVALIDVER)),
        }
    }

    fn open_rgssad(mut stream: File, version: u8) -> Result<Self, Error> {
        let mut magic = 0xDEADCAFEu32;
        let mut entry = HashMap::new();

        loop {
            let mut name_len: u32 = 0;
            if !ru32(&mut stream, &mut name_len) { break }
            name_len ^= advance_magic(&mut magic);

            let mut name_buf = vec![0u8; name_len as usize];
            stream.read_exact(&mut name_buf)?;
            for i in 0..(name_len as usize) {
                name_buf[i] ^= advance_magic(&mut magic) as u8;
                if name_buf[i] == b'\\' { name_buf[i] = b'/' }
            }
            let name_buf = String::from_utf8(name_buf);
            if name_buf.is_err() { break }
            let name_buf = name_buf.unwrap();

            let mut data = EntryData { size: 0, offset: 0, magic: 0 };
            if !ru32(&mut stream, &mut data.size) { break }
            data.size ^= advance_magic(&mut magic);
            data.offset = stream.seek(SeekFrom::Current(0))? as u32;
            data.magic = magic;

            stream.seek(SeekFrom::Current(data.size as i64))?;
            entry.insert(name_buf, data);
        }

        stream.seek(SeekFrom::Start(0))?;
        Ok(RGSSArchive { magic, version, entry, stream })
    }

    fn open_rgss3a(mut stream: File, version: u8) -> Result<Self, Error> {
        let mut magic = 0u32;
        let mut entry = HashMap::new();

        if !ru32(&mut stream, &mut magic) {
            return Err(Error::new(ErrorKind::InvalidData, E_INVALIDMGC));
        }
        magic = magic.wrapping_mul(9).wrapping_add(3);

        loop {
            let mut offset: u32 = 0;
            let mut size: u32 = 0;
            let mut start_magic: u32 = 0;
            let mut name_len: u32 = 0;

            if !ru32(&mut stream, &mut offset) { break };
            offset ^= magic;

            if offset == 0 { break }

            if !ru32(&mut stream, &mut size) { break }
            size ^= magic;

            if !ru32(&mut stream, &mut start_magic) { break}
            start_magic ^= magic;

            if !ru32(&mut stream, &mut name_len) { break }
            name_len ^= magic;

            let mut name_buf = vec![0u8; name_len as usize];
            stream.read_exact(&mut name_buf)?;
            for i in 0..(name_len as usize) {
                name_buf[i] ^= (magic >> 8*(i%4)) as u8;
                if name_buf[i] == b'\\' { name_buf[i] = b'/' }
            }
            let name_buf = String::from_utf8(name_buf);
            if name_buf.is_err() { break }
            let name_buf = name_buf.unwrap();

            let data = EntryData { size, offset, magic: start_magic };

            entry.insert(name_buf, data);
        }

        stream.seek(SeekFrom::Start(0))?;
        Ok(RGSSArchive { magic, version, entry, stream })
    }

    // fn put_key(&self, key: &str, stream: &mut File) -> Result<Entry, Error> {
    //     match self.version {
    //         1|2 => self.put_key_rgssad(stream),
    //           3 => self.put_key_rgss3a(stream),
    //     }
    // }
}

fn usage() {
    println!("Extract rgssad/rgss2a/rgss3a files.
Commands:
    help
    version
    list        <filename>
    unpack      <filename> <location> [<filter>]");
}

fn list(archive: RGSSArchive) {
    for (name, data) in archive.entry {
        println!("{}: EntryData {{ size: {}, offset: {}, magic: {} }}", name, data.size, data.offset, data.magic);
    }
}

fn pack(src: &str, out: &str, version: u8) {
    fn walkdir(archive: &mut RGSSArchive, d: &Path, r: &Path) {
        for entry in fs::read_dir(&d).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            if path.is_dir() {
                walkdir(archive, &path, r);
            } else {
                let key = path.strip_prefix(r).unwrap().to_str().unwrap();
                let mut stream = File::open(&path).unwrap();

                // TODO: implement this.
                //archive.put_key(key, stream);
            }
        }
    };

    let root = Path::new(src);
    if !root.is_dir() {
        println!("FAILED: source is not a directory."); return
    }

    let mut archive = match RGSSArchive::create(out, version) {
        Ok(x) => x,
        Err(e) => {
            println!("FAILED: unable to create output file. {}", e); return
        }
    };
    walkdir(&mut archive, root, root);
}

fn unpack(mut archive: RGSSArchive, dir: &str, filter: &str) {
    fn create(location: String) -> File {
        let path = Path::new(location.as_str());
        fs::create_dir_all(path.parent().unwrap()).unwrap();
        File::create(path.to_str().unwrap()).unwrap()
    }

    let entries = archive.entry.iter();
    let filter = match Regex::new(filter) {
        Ok(re) => re,
        Err(_) => {
            println!("FAILED: Invalid regex filter: {}", filter); return
        }
    };

    let mut coder = Coder { buf: vec![0u8; 8192] };

    for (name, data) in entries {
        if !filter.is_match(name) { continue }

        println!("Extracting: {}", name);

        let mut file = create(format!("{}/{}", dir, name));
        if let Err(err) = coder.copy(&mut archive.stream, &mut file, data) {
            println!("FAILED: key save failed, {}", err.to_string()); return
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 { usage(); return }
    match args[1].as_str() {
        "help" => usage(),
        "version" => {
            assert!(args.len() == 2);
            println!("version: {}", __VERSION__);
        },
        "list" => {
            assert!(args.len() == 3);
            let archive = RGSSArchive::open(args[2].as_str());
            if let Err(err) = archive {
                println!("FAILED: file parse failed, {}", err.to_string()); return;
            }
            let archive = archive.unwrap();

            list(archive);
        },
        "unpack" => {
            assert!(args.len() > 3 && args.len() < 6);
            let archive = RGSSArchive::open(args[2].as_str());
            if let Err(err) = archive {
                println!("FAILED: file parse failed, {}", err.to_string()); return;
            }
            let archive = archive.unwrap();
            unpack(archive, args[3].as_str(), if args.len() == 5 { args[4].as_str() } else { ".*" });
        },
        "pack" => {
            assert!(args.len() > 3 && args.len() < 6);
            let mut version = 1u8;
            if args.len() == 5 {
                version = match args[4].parse() {
                    Ok(v) => v,
                    Err(_) => {
                        println!("FAILED: {}", E_INVALIDVER); return
                    }
                }
            };
            pack(args[2].as_str(), args[3].as_str(), version);
        },
        _ => usage(),
    }
}
<|endoftext|>


<commit_before>pub mod api;
pub mod db;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use crate::{
    api::{Error, PersonalQuery, PersonalResponse},
    db::Database,
    indexer::{IndexerOpt, IndexerStub},
    model::{AnnoLichess, PersonalEntry, PersonalKeyBuilder},
    opening::Openings,
};
use axum::{
    extract::{Extension, Query},
    handler::get,
    response::Json,
    AddExtensionLayer, Router,
};
use clap::Clap;
use shakmaty::{fen::Fen, variant::VariantPosition, zobrist::Zobrist, CastlingMode};
use std::{io::Cursor, net::SocketAddr, path::PathBuf, sync::Arc};

#[derive(Clap)]
struct Opt {
    #[clap(long = "bind", default_value = "127.0.0.1:9000")]
    bind: SocketAddr,
    #[clap(long = "db", default_value = "_db")]
    db: PathBuf,
    #[clap(flatten)]
    indexer: IndexerOpt,
}

#[tokio::main]
async fn main() {
    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::new()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handle) = IndexerStub::spawn(db.clone(), opt.indexer);

    let app = Router::new()
        .route("/personal", get(personal))
        .layer(AddExtensionLayer::new(openings))
        .layer(AddExtensionLayer::new(db))
        .layer(AddExtensionLayer::new(indexer));

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .with_graceful_shutdown(async {
            tokio::signal::ctrl_c().await.expect("wait for ctrl-c");
        })
        .await
        .expect("bind");

    join_handle.await.expect("indexer");
}

async fn personal(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PersonalQuery>,
) -> Result<Json<PersonalResponse>, Error> {
    if dbg!(&query).update {
        let _status = indexer.index_player(query.player.clone()).await?;
    }

    let variant = query.variant.into();

    let mut pos = Zobrist::new(match query.fen {
        Some(fen) => VariantPosition::from_setup(variant, &Fen::from(fen), CastlingMode::Chess960)?,
        None => VariantPosition::new(variant),
    });

    let opening = openings.play_and_classify(&mut pos, query.play)?;

    let key = PersonalKeyBuilder::with_user_pov(&query.player.into(), query.color)
        .with_zobrist(variant, pos.zobrist_hash());

    let mut entry = PersonalEntry::default();
    let queryable = db.queryable();
    let mut end = rocksdb::ReadOptions::default();
    end.set_iterate_upper_bound(key.with_year(AnnoLichess::MAX));
    let iterator = queryable.db.iterator_cf_opt(
        queryable.cf_personal,
        end,
        rocksdb::IteratorMode::From(
            &key.with_year(AnnoLichess::from_year(query.since)),
            rocksdb::Direction::Forward,
        ),
    );
    for (_key, value) in iterator {
        let mut cursor = Cursor::new(value);
        entry
            .extend_from_reader(&mut cursor)
            .expect("deserialize personal entry");
    }

    Ok(Json(query.filter.respond(pos.into_inner(), entry, opening)))
}
<commit_msg>remove debug<commit_after>pub mod api;
pub mod db;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use crate::{
    api::{Error, PersonalQuery, PersonalResponse},
    db::Database,
    indexer::{IndexerOpt, IndexerStub},
    model::{AnnoLichess, PersonalEntry, PersonalKeyBuilder},
    opening::Openings,
};
use axum::{
    extract::{Extension, Query},
    handler::get,
    response::Json,
    AddExtensionLayer, Router,
};
use clap::Clap;
use shakmaty::{fen::Fen, variant::VariantPosition, zobrist::Zobrist, CastlingMode};
use std::{io::Cursor, net::SocketAddr, path::PathBuf, sync::Arc};

#[derive(Clap)]
struct Opt {
    #[clap(long = "bind", default_value = "127.0.0.1:9000")]
    bind: SocketAddr,
    #[clap(long = "db", default_value = "_db")]
    db: PathBuf,
    #[clap(flatten)]
    indexer: IndexerOpt,
}

#[tokio::main]
async fn main() {
    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::new()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handle) = IndexerStub::spawn(db.clone(), opt.indexer);

    let app = Router::new()
        .route("/personal", get(personal))
        .layer(AddExtensionLayer::new(openings))
        .layer(AddExtensionLayer::new(db))
        .layer(AddExtensionLayer::new(indexer));

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .with_graceful_shutdown(async {
            tokio::signal::ctrl_c().await.expect("wait for ctrl-c");
        })
        .await
        .expect("bind");

    join_handle.await.expect("indexer");
}

async fn personal(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PersonalQuery>,
) -> Result<Json<PersonalResponse>, Error> {
    if query.update {
        let _status = indexer.index_player(query.player.clone()).await?;
    }

    let variant = query.variant.into();

    let mut pos = Zobrist::new(match query.fen {
        Some(fen) => VariantPosition::from_setup(variant, &Fen::from(fen), CastlingMode::Chess960)?,
        None => VariantPosition::new(variant),
    });

    let opening = openings.play_and_classify(&mut pos, query.play)?;

    let key = PersonalKeyBuilder::with_user_pov(&query.player.into(), query.color)
        .with_zobrist(variant, pos.zobrist_hash());

    let mut entry = PersonalEntry::default();
    let queryable = db.queryable();
    let mut end = rocksdb::ReadOptions::default();
    end.set_iterate_upper_bound(key.with_year(AnnoLichess::MAX));
    let iterator = queryable.db.iterator_cf_opt(
        queryable.cf_personal,
        end,
        rocksdb::IteratorMode::From(
            &key.with_year(AnnoLichess::from_year(query.since)),
            rocksdb::Direction::Forward,
        ),
    );
    for (_key, value) in iterator {
        let mut cursor = Cursor::new(value);
        entry
            .extend_from_reader(&mut cursor)
            .expect("deserialize personal entry");
    }

    Ok(Json(query.filter.respond(pos.into_inner(), entry, opening)))
}
<|endoftext|>


<commit_before>#![feature(macro_rules)]

extern crate getopts;
use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::collections::hashmap::{Occupied, Vacant};
use std::io;
use std::io::fs::PathExtensions;

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_")
    }));
    env
}

fn from_env<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<&'a String> {
    env.find_equiv(&key)
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    from_env(env, key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

macro_rules! expect_or_this(
    ($iterator:expr, $this: expr) => (
        match $iterator {
            Some(x) => x,
            None => $this
        }
    )
)

fn build_index(outfile: &Path, dir: &Path) {
    if !dir.is_dir() {
        fail!("MATES_DIR must be a directory.");
    };

    let mut outf = io::File::create(outfile);
    let entries = match io::fs::readdir(dir) {
        Ok(x) => x,
        Err(f) => { fail!(f.desc) }
    };
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }
        let itemstr = match io::File::open(entry).read_to_string() {
            Ok(x) => x,
            Err(f) => { fail!(format!("Failed to open {}: {}", entry.display(), f.desc)) }
        };
        let item = parse_item(&itemstr);
        let name = item.fullname();
        match item.emails() {
            Some(emails) => {
                for email in emails.iter() {
                    outf.write_str(format!("{}\t{}\n", email.value, name).as_slice());
                };
            },
            None => ()
        };
    };
}

struct PropertyValue {
    params: String,
    value: String,
}

struct Item {
    props: HashMap<String, Vec<PropertyValue>>
}

impl Item {
    fn single_value(&self, key: &String) -> Option<String> {
        match self.props.find(key) {
            Some(x) => { if x.len() > 0 { Some(x[0].value.clone()) } else { None } },
            None => { None }
        }
    }
    fn fullname(&self) -> String {
        match self.single_value(&"FN".into_string()) {
            Some(x) => { x }
            None => { "".into_string() }
        }
    }

    fn emails(&self) -> Option<&Vec<PropertyValue>> {
        self.props.find(&"EMAIL".into_string())
    }
}

fn parse_item(s: &String) -> Item {
    let mut linebuffer = String::new();
    let mut line: String;
    let mut is_continuation: bool;
    let mut rv = Item {
        props: HashMap::new()
    };

    for strline in s.as_slice().split('\n') {
        line = strline.into_string();
        is_continuation = false;
        while line.as_slice().char_at(0).is_whitespace() {
            is_continuation = true;
            line.remove(0);
        };

        if !is_continuation && linebuffer.len() > 0 {
            let (propkey, propvalue) = parse_line(&linebuffer);
            match rv.props.entry(propkey) {
                Occupied(values) => { values.into_mut().push(propvalue); },
                Vacant(values) => { values.set(vec![propvalue]); }
            };
            linebuffer.clear();
        };

        linebuffer.push_str(line.as_slice());
    };
    rv
}

fn parse_line(s: &String) -> (String, PropertyValue) {
    let mut kv_splitresult = s.as_slice().splitn(1, ':');
    let key_and_params = kv_splitresult.next().expect("");
    let value = expect_or_this!(kv_splitresult.next(), "");

    let mut kp_splitresult = key_and_params.splitn(1, ';');
    let key = kp_splitresult.next().expect("");
    let params = expect_or_this!(kp_splitresult.next(), "");

    (key.into_string(), PropertyValue {
        value: value.into_string(),
        params: params.into_string()
    })
}

fn main() {
    let args = os::args();

    let program = args[0].as_slice();
    let opts = [
        optflag("i", "index", "Create index."),
        optflag("h", "help", "Print help."),
        optopt("m", "mutt-search", "Search in index, for mutt search.", "")
    ];

    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    let env = get_env();

    let print_usage = || {
        println!("{}", usage(program, opts));
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    if matches.opt_present("h") {
        print_usage();
    } else if matches.opt_present("index") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let mates_dir = expect_env(&env, "MATES_DIR");
        println!("Rebuilding index file \"{}\"...", index_file);
        build_index(
            &Path::new(index_file.as_slice()),
            &Path::new(mates_dir.as_slice())
        );
    } else if matches.opt_present("mutt-search") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let default_grep = "grep".into_string();
        let grep_cmd = expect_or_this!(from_env(&env, "MATES_GREP"), &default_grep);

        // FIXME: Better way to write this? We already checked for presence of mutt-search before
        let query = matches.opt_str("mutt-search").expect("This should never happen and yet it did.");
        match match io::Command::new(grep_cmd.as_slice())
            .arg(query.as_slice())
            .arg(index_file.as_slice())
            .stdout(io::process::InheritFd(1))
            .stderr(io::process::InheritFd(2))
            .spawn() {
                Ok(child) => child,
                Err(e) => fail!("Failed to execute grep command: {}", e),
            }.wait() {
                Ok(code) => {
                    if !code.success() {
                        fail!("Grep command exited with code {}, aborting.", code);
                    };
                },
                Err(e) => fail!("Failed to execute grep command: {}", e),
            };
    } else {
        print_usage();
    };
}
<commit_msg>Don't emit more output when grep fails<commit_after>#![feature(macro_rules)]

extern crate getopts;
use getopts::{optflag,optopt,getopts,usage};
use std::os;
use std::collections::HashMap;
use std::collections::hashmap::{Occupied, Vacant};
use std::io;
use std::io::fs::PathExtensions;

fn get_env() -> HashMap<String, String> {
    let mut env = HashMap::new();
    env.extend(os::env().into_iter().filter(|&(ref key, ref value)| {
        key.as_slice().starts_with("MATES_")
    }));
    env
}

fn from_env<'a>(env: &'a HashMap<String, String>, key: &str) -> Option<&'a String> {
    env.find_equiv(&key)
}

fn expect_env<'a>(env: &'a HashMap<String, String>, key: &str) -> &'a String {
    from_env(env, key).expect(
        format!("The {} environment variable must be set.", key).as_slice()
    )
}

macro_rules! expect_or_this(
    ($iterator:expr, $this: expr) => (
        match $iterator {
            Some(x) => x,
            None => $this
        }
    )
)

fn build_index(outfile: &Path, dir: &Path) {
    if !dir.is_dir() {
        fail!("MATES_DIR must be a directory.");
    };

    let mut outf = io::File::create(outfile);
    let entries = match io::fs::readdir(dir) {
        Ok(x) => x,
        Err(f) => { fail!(f.desc) }
    };
    for entry in entries.iter() {
        if !entry.is_file() {
            continue;
        }
        let itemstr = match io::File::open(entry).read_to_string() {
            Ok(x) => x,
            Err(f) => { fail!(format!("Failed to open {}: {}", entry.display(), f.desc)) }
        };
        let item = parse_item(&itemstr);
        let name = item.fullname();
        match item.emails() {
            Some(emails) => {
                for email in emails.iter() {
                    outf.write_str(format!("{}\t{}\n", email.value, name).as_slice());
                };
            },
            None => ()
        };
    };
}

struct PropertyValue {
    params: String,
    value: String,
}

struct Item {
    props: HashMap<String, Vec<PropertyValue>>
}

impl Item {
    fn single_value(&self, key: &String) -> Option<String> {
        match self.props.find(key) {
            Some(x) => { if x.len() > 0 { Some(x[0].value.clone()) } else { None } },
            None => { None }
        }
    }
    fn fullname(&self) -> String {
        match self.single_value(&"FN".into_string()) {
            Some(x) => { x }
            None => { "".into_string() }
        }
    }

    fn emails(&self) -> Option<&Vec<PropertyValue>> {
        self.props.find(&"EMAIL".into_string())
    }
}

fn parse_item(s: &String) -> Item {
    let mut linebuffer = String::new();
    let mut line: String;
    let mut is_continuation: bool;
    let mut rv = Item {
        props: HashMap::new()
    };

    for strline in s.as_slice().split('\n') {
        line = strline.into_string();
        is_continuation = false;
        while line.as_slice().char_at(0).is_whitespace() {
            is_continuation = true;
            line.remove(0);
        };

        if !is_continuation && linebuffer.len() > 0 {
            let (propkey, propvalue) = parse_line(&linebuffer);
            match rv.props.entry(propkey) {
                Occupied(values) => { values.into_mut().push(propvalue); },
                Vacant(values) => { values.set(vec![propvalue]); }
            };
            linebuffer.clear();
        };

        linebuffer.push_str(line.as_slice());
    };
    rv
}

fn parse_line(s: &String) -> (String, PropertyValue) {
    let mut kv_splitresult = s.as_slice().splitn(1, ':');
    let key_and_params = kv_splitresult.next().expect("");
    let value = expect_or_this!(kv_splitresult.next(), "");

    let mut kp_splitresult = key_and_params.splitn(1, ';');
    let key = kp_splitresult.next().expect("");
    let params = expect_or_this!(kp_splitresult.next(), "");

    (key.into_string(), PropertyValue {
        value: value.into_string(),
        params: params.into_string()
    })
}

fn main() {
    let args = os::args();

    let program = args[0].as_slice();
    let opts = [
        optflag("i", "index", "Create index."),
        optflag("h", "help", "Print help."),
        optopt("m", "mutt-search", "Search in index, for mutt search.", "")
    ];

    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    let env = get_env();

    let print_usage = || {
        println!("{}", usage(program, opts));
        println!("Environment variables:");
        println!("- MATES_INDEX: Path to index file, which is basically a cache of all");
        println!("               contacts.");
        println!("- MATES_DIR:   The vdir to use.");
        println!("- MATES_GREP:  The grep executable to use.");
    };

    if matches.opt_present("h") {
        print_usage();
    } else if matches.opt_present("index") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let mates_dir = expect_env(&env, "MATES_DIR");
        println!("Rebuilding index file \"{}\"...", index_file);
        build_index(
            &Path::new(index_file.as_slice()),
            &Path::new(mates_dir.as_slice())
        );
    } else if matches.opt_present("mutt-search") {
        let index_file = expect_env(&env, "MATES_INDEX");
        let default_grep = "grep".into_string();
        let grep_cmd = expect_or_this!(from_env(&env, "MATES_GREP"), &default_grep);

        // FIXME: Better way to write this? We already checked for presence of mutt-search before
        let query = matches.opt_str("mutt-search").expect("This should never happen and yet it did.");
        match match io::Command::new(grep_cmd.as_slice())
            .arg(query.as_slice())
            .arg(index_file.as_slice())
            .stdout(io::process::InheritFd(1))
            .stderr(io::process::InheritFd(2))
            .spawn() {
                Ok(child) => child,
                Err(e) => fail!("Failed to execute grep command: {}", e),
            }.wait() {
                Ok(code) => {
                    std::os::set_exit_status(match code {
                        io::process::ExitStatus(x) => x,
                        io::process::ExitSignal(x) => x,
                    });
                },
                Err(e) => fail!("Failed to execute grep command: {}", e),
            };
    } else {
        print_usage();
    };
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
#[macro_use]
extern crate error_chain;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate quick_xml;
extern crate rss;
extern crate atom_syndication as atom;
extern crate curl;
extern crate futures;
extern crate tokio_core;
extern crate tokio_curl;
extern crate telebot;

use std::io::prelude::*;
use std::rc::Rc;
use std::cell::RefCell;

use telebot::functions::*;
use tokio_core::reactor::Core;
use futures::Future;
use futures::Stream;

mod errors;
mod feed;
mod data;

fn log_error(e: &errors::Error) {
    warn!("error: {}", e);
    for e in e.iter().skip(1) {
        warn!("caused by: {}", e);
    }
    if let Some(backtrace) = e.backtrace() {
        warn!("backtrace: {:?}", backtrace);
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 3 {
        writeln!(&mut std::io::stderr(),
                 "Usage: {} DATAFILE TELEGRAM-BOT-TOKEN",
                 args[0])
            .unwrap();
        std::process::exit(1);
    }
    let datafile = &args[1];
    let token = &args[2];

    let db = Rc::new(RefCell::new(data::Database::open(datafile)
        .map_err(|e| {
            writeln!(&mut std::io::stderr(), "error: {}", e).unwrap();
            for e in e.iter().skip(1) {
                writeln!(&mut std::io::stderr(), "caused by: {}", e).unwrap();
            }
            if let Some(backtrace) = e.backtrace() {
                writeln!(&mut std::io::stderr(), "backtrace: {:?}", backtrace).unwrap();
            }
            std::process::exit(1);
        })
        .unwrap()));

    env_logger::init().unwrap();

    let mut lp = Core::new().unwrap();
    let bot = telebot::RcBot::new(lp.handle(), token).update_interval(200);

    {
        let db = db.clone();
        let handle = bot.new_cmd("/rss")
            .and_then(move |(bot, msg)| match db.borrow().get_subscribed_feeds(msg.chat.id) {
                Some(feeds) => {
                    let mut text = String::from("订阅列表:");
                    for feed in feeds {
                        text.push_str(&format!("\n{}", feed.title, feed.link));
                    }
                    bot.message(msg.chat.id, text)
                        .parse_mode("HTML")
                        .disable_web_page_preview(true)
                        .send()
                }
                None => bot.message(msg.chat.id, "订阅列表为空".to_string()).send(),
            });
        bot.register(handle);
    }
    {
        let db = db.clone();
        let handle = bot.new_cmd("/sub")
            .and_then(move |(bot, msg)| {
                let mut text = msg.text.unwrap().to_owned();
                if text.is_empty() {
                    text = "<empty>".into();
                }

                match db.borrow_mut().subscribe(msg.chat.id, &text, &rss::Channel::default()) {
                    Ok(_) => bot.message(msg.chat.id, text).send(),
                    Err(errors::Error(errors::ErrorKind::AlreadySubscribed, _)) => {
                        bot.message(msg.chat.id, "已订阅过的 Feed".to_string()).send()
                    }
                    Err(e) => {
                        log_error(&e);
                        bot.message(msg.chat.id, format!("error: {}", e)).send()
                    }
                }
            });
        bot.register(handle);
    }
    {
        let db = db.clone();
        let handle = bot.new_cmd("/unsub")
            .and_then(move |(bot, msg)| {
                let mut text = msg.text.unwrap().to_owned();
                if text.is_empty() {
                    text = "<empty>".into();
                }

                match db.borrow_mut().unsubscribe(msg.chat.id, &text) {
                    Ok(_) => bot.message(msg.chat.id, text).send(),
                    Err(errors::Error(errors::ErrorKind::NotSubscribed, _)) => {
                        bot.message(msg.chat.id, "未订阅过的 Feed".to_string()).send()
                    }
                    Err(e) => {
                        log_error(&e);
                        bot.message(msg.chat.id, format!("error: {}", e)).send()
                    }
                }
            });
        bot.register(handle);
    }

    loop {
        if let Err(err) = bot.run(&mut lp) {
            error!("{:?}", err);
        }
    }
}
<commit_msg>add command arguments parser<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
#[macro_use]
extern crate error_chain;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate quick_xml;
extern crate rss;
extern crate atom_syndication as atom;
extern crate curl;
extern crate futures;
extern crate tokio_core;
extern crate tokio_curl;
extern crate telebot;

use std::io::prelude::*;
use std::rc::Rc;
use std::cell::RefCell;

use telebot::functions::*;
use tokio_core::reactor::Core;
use futures::Future;
use futures::Stream;

mod errors;
mod feed;
mod data;

fn log_error(e: &errors::Error) {
    warn!("error: {}", e);
    for e in e.iter().skip(1) {
        warn!("caused by: {}", e);
    }
    if let Some(backtrace) = e.backtrace() {
        warn!("backtrace: {:?}", backtrace);
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 3 {
        writeln!(&mut std::io::stderr(),
                 "Usage: {} DATAFILE TELEGRAM-BOT-TOKEN",
                 args[0])
            .unwrap();
        std::process::exit(1);
    }
    let datafile = &args[1];
    let token = &args[2];

    let db = Rc::new(RefCell::new(data::Database::open(datafile)
        .map_err(|e| {
            writeln!(&mut std::io::stderr(), "error: {}", e).unwrap();
            for e in e.iter().skip(1) {
                writeln!(&mut std::io::stderr(), "caused by: {}", e).unwrap();
            }
            if let Some(backtrace) = e.backtrace() {
                writeln!(&mut std::io::stderr(), "backtrace: {:?}", backtrace).unwrap();
            }
            std::process::exit(1);
        })
        .unwrap()));

    env_logger::init().unwrap();

    let mut lp = Core::new().unwrap();
    let bot = telebot::RcBot::new(lp.handle(), token).update_interval(200);

    {
        let db = db.clone();
        let handle = bot.new_cmd("/rss")
            .and_then(move |(bot, msg)| {
                let text = msg.text.unwrap();
                let args: Vec<&str> = text.split_whitespace().collect();
                let raw: bool;
                let subscriber: i64;
                match args.len() {
                    0 => {
                        raw = false;
                        subscriber = msg.chat.id;
                    }
                    1 => {
                        if args[0] == "raw" {
                            raw = true;
                            subscriber = msg.chat.id;
                        } else {
                            raw = false;
                            let channel = args[0];
                            subscriber = msg.chat.id;
                        }
                    }
                    2 => {
                        raw = true;
                        let channel = args[0];
                        subscriber = msg.chat.id;
                    }
                    _ => {
                        return bot.message(msg.chat.id,
                                     "使用方法： /rss <Channel ID> <raw>".to_string())
                            .send()
                    }
                }

                match db.borrow().get_subscribed_feeds(subscriber) {
                    Some(feeds) => {
                        let mut text = String::from("订阅列表:");
                        if raw {
                            for feed in feeds {
                                text.push_str(&format!("\n{}",
                                                       feed.title,
                                                       feed.link));
                            }
                            bot.message(msg.chat.id, text)
                                .parse_mode("HTML")
                                .disable_web_page_preview(true)
                                .send()
                        } else {
                            for feed in feeds {
                                text.push_str(&format!("\n{}: {}", feed.title, feed.link));
                            }
                            bot.message(msg.chat.id, text)
                                .disable_web_page_preview(true)
                                .send()
                        }
                    }
                    None => bot.message(msg.chat.id, "订阅列表为空".to_string()).send(),
                }
            });
        bot.register(handle);
    }
    {
        let db = db.clone();
        let handle = bot.new_cmd("/sub")
            .and_then(move |(bot, msg)| {
                let text = msg.text.unwrap();
                let args: Vec<&str> = text.split_whitespace().collect();
                let feed_link: &str;
                let subscriber: i64;
                match args.len() {
                    1 => {
                        feed_link = args[0];
                        subscriber = msg.chat.id;
                    }
                    2 => {
                        let channel = args[0];
                        subscriber = msg.chat.id;
                        feed_link = args[1];
                    }
                    _ => {
                        return bot.message(msg.chat.id,
                                     "使用方法： /sub [Channel ID] <RSS URL>".to_string())
                            .send()
                    }
                }

                match db.borrow_mut().subscribe(subscriber, feed_link, &rss::Channel::default()) {
                    Ok(_) => bot.message(msg.chat.id, "订阅成功".to_string()).send(),
                    Err(errors::Error(errors::ErrorKind::AlreadySubscribed, _)) => {
                        bot.message(msg.chat.id, "已订阅过的 RSS".to_string()).send()
                    }
                    Err(e) => {
                        log_error(&e);
                        bot.message(msg.chat.id, format!("error: {}", e)).send()
                    }
                }
            });
        bot.register(handle);
    }
    {
        let db = db.clone();
        let handle = bot.new_cmd("/unsub")
            .and_then(move |(bot, msg)| {
                let text = msg.text.unwrap();
                let args: Vec<&str> = text.split_whitespace().collect();
                let feed_link: &str;
                let subscriber: i64;
                match args.len() {
                    1 => {
                        feed_link = args[0];
                        subscriber = msg.chat.id;
                    }
                    2 => {
                        let channel = args[0];
                        subscriber = msg.chat.id;
                        feed_link = args[1];
                    }
                    _ => {
                        return bot.message(msg.chat.id,
                                     "使用方法： /unsub [Channel ID] <RSS URL>".to_string())
                            .send()
                    }
                }

                match db.borrow_mut().unsubscribe(subscriber, feed_link) {
                    Ok(_) => bot.message(msg.chat.id, "退订成功".to_string()).send(),
                    Err(errors::Error(errors::ErrorKind::NotSubscribed, _)) => {
                        bot.message(msg.chat.id, "未订阅过的 Feed".to_string()).send()
                    }
                    Err(e) => {
                        log_error(&e);
                        bot.message(msg.chat.id, format!("error: {}", e)).send()
                    }
                }
            });
        bot.register(handle);
    }

    loop {
        if let Err(err) = bot.run(&mut lp) {
            error!("{:?}", err);
        }
    }
}
<|endoftext|>