<commit_before>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::path::Path;
use std::process::{exit, Command};
use std::env;

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("Skeleton")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Skeleton project manager")
        .args_from_usage("-l, --lang=<LANG>  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let mut config_path = match env::home_dir() {
        Some(path) => {
            match path.to_str() {
                Some(p) => p.to_string(),
                None => {
                    println!("Could not resolve $HOME");
                    exit(1);
                }
            }
        }
        None => {
            println!("Could not resolve $HOME");
            exit(1);
        }
    };

    config_path.push_str("/.skeleton/");
    config_path.push_str(lang);
    config_path.push_str(".toml");

    let config: Config = match parse_config(config_path) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };

    if let Some(subcmd) = matches.subcommand_matches("new") {
        let name = subcmd.value_of("NAME").unwrap();
        if file_exists(&name.to_string()) {
            println!("A file or directory with that name already exists");
            exit(1);
        } else {
            match create_dir(name.clone()) {
                Ok(_) => println!("Created directory '{}'", name),
                Err(e) => {
                    println!("Could not create direcotry '{}'. {:?}", name, e);
                    exit(1);
                }
            }
            if let Err(_) = env::set_current_dir(Path::new(&*name)) {
                println!("Could not change directory!");
                exit(1);
            }
        }
    }

    if let Some(dirs) = config.mkdir {
        for dir in dirs {
            if !file_exists(&dir) {
                match create_dir(dir.clone()) {
                    Ok(_) => println!("Created directory '{}'", dir),
                    Err(e) => println!("Could not create directory '{}'. {:?}", dir, e),
                }
            } else {
                println!("Directory '{}' already exists", dir);
            }
        }
    }

    if let Some(gi) = config.gitignore {
        let ign = gitignore::get_gitignore(gi).unwrap();
        let file = OpenOptions::new()
            .create_new(true)
            .append(true)
            .open("gitignore");
        if let Ok(mut file) = file {
            writeln!(file, "{}", ign);
        } else {
            println!("Could not write .gitignore");
        }
    }

    if let Some(fnames) = config.touch {
        for file in fnames {
            if !create_file(&file) {
                println!("Could not create file '{}'", file);
            }
        }
    }

    if let Some(cmds) = config.exec {
        for cmd in cmds {
            println!("Executing '{}'", cmd);
            match exec(&cmd) {
                Ok((out, err)) => {
                    println!("stdout: {}", out);
                    println!("stderr: {}", err);
                }
                Err(e) => println!("Failed to execute: {:?}", e),
            }
        }
    }
}

fn file_exists(name: &String) -> bool {
    Path::new(&name).exists()
}

fn exec(cmd: &String) -> Result<(String, String)> {
    #[cfg(unix)]
    let shell = "sh";
    #[cfg(unix)]
    let arg1 = "-c";
    #[cfg(windows)]
    let shell = "cmd.exe";
    #[cfg(windows)]
    let arg1 = "/C";

    let out = match Command::new(shell).arg(arg1).arg(cmd).output() {
        Ok(o) => o,
        Err(e) => return Err(e),
    };

    let stdout = match String::from_utf8(out.stdout) {
        Ok(so) => so,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    let stderr = match String::from_utf8(out.stderr) {
        Ok(se) => se,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    Ok((stdout, stderr))
}

fn create_file(file_name: &String) -> bool {
    !file_exists(&file_name) &&
    match OpenOptions::new()
              .append(true)
              .create(true)
              .open(file_name) {
        Ok(_) => true,
        Err(_) => false,
    }
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}

#[test]
fn test_file_exists() {
    assert!(file_exists(&"test/foo.toml".to_string()));
    assert!(!file_exists(&"test/none".to_string()));
}

#[test]
fn test_parse_config() {
    let conf = parse_config("test/foo.toml".to_string()).unwrap();
    let gi = conf.gitignore.unwrap();
    assert_eq!(gi, vec!["rust".to_string(), "vim".to_string()]);

    let mkd = conf.mkdir.unwrap();
    assert_eq!(mkd, vec!["b".to_string()]);

    let touch = conf.touch.unwrap();
    assert_eq!(touch, vec!["a".to_string()]);

    let exec = conf.exec.unwrap();
    assert_eq!(exec, vec!["touch asdf".to_string()]);
}

#[test]
fn test_exec() {
    let (stdout, stderr) = exec(&"echo -n test".to_string()).unwrap();
    assert_eq!(stdout, "test".to_string());
    assert_eq!(stderr, "".to_string());
    let (stdout, stderr) = exec(&"echo -n test 1>&2".to_string()).unwrap();
    assert_eq!(stdout, "".to_string());
    assert_eq!(stderr, "test".to_string());
}
<commit_msg>Check if directory is empty<commit_after>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir, read_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::path::Path;
use std::process::{exit, Command};
use std::env;

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("Skeleton")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Skeleton project manager")
        .args_from_usage("-l, --lang=<LANG>  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let mut config_path = match env::home_dir() {
        Some(path) => {
            match path.to_str() {
                Some(p) => p.to_string(),
                None => {
                    println!("Could not resolve $HOME");
                    exit(1);
                }
            }
        }
        None => {
            println!("Could not resolve $HOME");
            exit(1);
        }
    };

    config_path.push_str("/.skeleton/");
    config_path.push_str(lang);
    config_path.push_str(".toml");

    let config: Config = match parse_config(config_path) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };

    if let Some(subcmd) = matches.subcommand_matches("new") {
        let name = subcmd.value_of("NAME").unwrap();
        if file_exists(&name.to_string()) {
            println!("A file or directory with that name already exists");
            exit(1);
        } else {
            match create_dir(name.clone()) {
                Ok(_) => println!("Created directory '{}'", name),
                Err(e) => {
                    println!("Could not create direcotry '{}'. {:?}", name, e);
                    exit(1);
                }
            }
            if let Err(_) = env::set_current_dir(Path::new(&*name)) {
                println!("Could not change directory!");
                exit(1);
            }
        }
    }

    if !dir_is_empty(&".".to_string()) {
        println!("Direcotry is not empty!");
        exit(1);
    }

    if let Some(dirs) = config.mkdir {
        for dir in dirs {
            if !file_exists(&dir) {
                match create_dir(dir.clone()) {
                    Ok(_) => println!("Created directory '{}'", dir),
                    Err(e) => println!("Could not create directory '{}'. {:?}", dir, e),
                }
            } else {
                println!("Directory '{}' already exists", dir);
            }
        }
    }

    if let Some(gi) = config.gitignore {
        let ign = gitignore::get_gitignore(gi).unwrap();
        let file = OpenOptions::new()
            .create_new(true)
            .append(true)
            .open("gitignore");
        if let Ok(mut file) = file {
            writeln!(file, "{}", ign);
        } else {
            println!("Could not write .gitignore");
        }
    }

    if let Some(fnames) = config.touch {
        for file in fnames {
            if !create_file(&file) {
                println!("Could not create file '{}'", file);
            }
        }
    }

    if let Some(cmds) = config.exec {
        for cmd in cmds {
            println!("Executing '{}'", cmd);
            match exec(&cmd) {
                Ok((out, err)) => {
                    println!("stdout: {}", out);
                    println!("stderr: {}", err);
                }
                Err(e) => println!("Failed to execute: {:?}", e),
            }
        }
    }
}

fn dir_is_empty(dir: &String) -> bool {
    let paths = read_dir(dir).unwrap();

    for _ in paths {
        return false;
    }
    true
}

fn file_exists(name: &String) -> bool {
    Path::new(&name).exists()
}

fn exec(cmd: &String) -> Result<(String, String)> {
    #[cfg(unix)]
    let shell = "sh";
    #[cfg(unix)]
    let arg1 = "-c";
    #[cfg(windows)]
    let shell = "cmd.exe";
    #[cfg(windows)]
    let arg1 = "/C";

    let out = match Command::new(shell).arg(arg1).arg(cmd).output() {
        Ok(o) => o,
        Err(e) => return Err(e),
    };

    let stdout = match String::from_utf8(out.stdout) {
        Ok(so) => so,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    let stderr = match String::from_utf8(out.stderr) {
        Ok(se) => se,
        Err(e) => return Err(Error::new(ErrorKind::InvalidInput, e)),
    };

    Ok((stdout, stderr))
}

fn create_file(file_name: &String) -> bool {
    !file_exists(&file_name) &&
    match OpenOptions::new()
              .append(true)
              .create(true)
              .open(file_name) {
        Ok(_) => true,
        Err(_) => false,
    }
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}

#[test]
fn test_file_exists() {
    assert!(file_exists(&"test/foo.toml".to_string()));
    assert!(!file_exists(&"test/none".to_string()));
}

#[test]
fn test_parse_config() {
    let conf = parse_config("test/foo.toml".to_string()).unwrap();
    let gi = conf.gitignore.unwrap();
    assert_eq!(gi, vec!["rust".to_string(), "vim".to_string()]);

    let mkd = conf.mkdir.unwrap();
    assert_eq!(mkd, vec!["b".to_string()]);

    let touch = conf.touch.unwrap();
    assert_eq!(touch, vec!["a".to_string()]);

    let exec = conf.exec.unwrap();
    assert_eq!(exec, vec!["touch asdf".to_string()]);
}

#[test]
fn test_exec() {
    let (stdout, stderr) = exec(&"echo -n test".to_string()).unwrap();
    assert_eq!(stdout, "test".to_string());
    assert_eq!(stderr, "".to_string());
    let (stdout, stderr) = exec(&"echo -n test 1>&2".to_string()).unwrap();
    assert_eq!(stdout, "".to_string());
    assert_eq!(stderr, "test".to_string());
}

#[test]
fn test_dir_is_empty() {
    let is_empty = dir_is_empty(&"./test".to_string());
    assert!(!is_empty);
}
<|endoftext|>


<commit_before>use std::io;
use std::io::prelude::*;
use std::iter::repeat;
use std::str::FromStr;
use std::num::Int;
use std::num::ToPrimitive;
use std::collections::BitVec;

fn main() {
    let title = "RUSTYHAM: A HAMMING CODE GENERATOR IN RUST";
    let border: String = repeat('=').take(title.len()).collect::<>();
    println!("{}", border);
    println!("{}", title);
    println!("{}", border);

    loop {
        let mut linebuf = String::new();
        println!("(1) Encode ASCII");
        println!("(2) Encode binary");
        println!("(3) Decode to ASCII");
        println!("(4) Decode to binary");
        print!("Enter your choice: ");
        io::stdout().flush();
        io::stdin().read_line(&mut linebuf);

        let num = i32::from_str(linebuf.trim());
        match num.ok() {
            Some(n) => {
                let mut good = true;
                println!("{}", match n {
                    1 => hamming(),
                    2...4 => "Sorry, not implemented yet.".to_string(),
                    _ => { good = false; "Invalid input.".to_string() }
                });
                if good { break; }
            }
            None => {
                println!("Invalid input.");
            }
        };
    }
}

fn hamming() -> String {
    // prompt for ASCII input
    let mut message = String::new();
    print!("Enter string to encode: ");
    io::stdout().flush();
    io::stdin().read_line(&mut message);

    // compute block and message length
    message = message.trim().to_string();
    let mlen = message.len().to_u32().unwrap() * 7;
    let lenpow = (2..).find(|&r| 2.pow(r) - r - 1 >= mlen).unwrap();
    let len = 2.pow(lenpow) - 1;

    // the thing we're storing the hamming code in
    let mut bv = BitVec::from_elem(len, false);

    // convert ASCII string to binary
    // IMPORTANT NOTE: the following line takes ownership of the `message'
    // variable. We no longer have access to the original string from this
    // point onwards.
    let bytes = message.into_bytes();
    let bytes_str = bytes.iter()
        .map(|&c| format!("{:0>1$b}", c, 7))
        .collect::<Vec<String>>()
        .concat();
    let mut bytes_iter = bytes_str.chars();

    // set data bits
    for i in 1..len {
        if (i & (i - 1)) != 0 {
            bv.set(i-1, bytes_iter.next().unwrap_or('0') == '1');
        }
    }

    // set parity bits
    for i in 0..lenpow {
        let bi = 2.pow(i)-1;
        let mut parity = false;
        let mut ignore = false;
        let mut counter = 0;
        for j in bi..len {
            if !ignore {
                if bv.get(j).unwrap() {
                    parity = !parity;
                }
            }
            counter += 1;
            if counter >= 2.pow(i) {
                ignore = !ignore;
                counter = 0;
            }
        }
        bv.set(bi, parity);
    }

    format!("{:?}", bv)
}
<commit_msg>zap use of ToPrimitive<commit_after>use std::io;
use std::io::prelude::*;
use std::iter::repeat;
use std::str::FromStr;
use std::num::Int;
use std::collections::BitVec;

fn main() {
    let title = "RUSTYHAM: A HAMMING CODE GENERATOR IN RUST";
    let border: String = repeat('=').take(title.len()).collect::<>();
    println!("{}", border);
    println!("{}", title);
    println!("{}", border);

    loop {
        let mut linebuf = String::new();
        println!("(1) Encode ASCII");
        println!("(2) Encode binary");
        println!("(3) Decode to ASCII");
        println!("(4) Decode to binary");
        print!("Enter your choice: ");
        io::stdout().flush();
        io::stdin().read_line(&mut linebuf);

        let num = i32::from_str(linebuf.trim());
        match num.ok() {
            Some(n) => {
                let mut good = true;
                println!("{}", match n {
                    1 => hamming(),
                    2...4 => "Sorry, not implemented yet.".to_string(),
                    _ => { good = false; "Invalid input.".to_string() }
                });
                if good { break; }
            }
            None => {
                println!("Invalid input.");
            }
        };
    }
}

fn hamming() -> String {
    // prompt for ASCII input
    let mut message = String::new();
    print!("Enter string to encode: ");
    io::stdout().flush();
    io::stdin().read_line(&mut message);

    // compute block and message length
    message = message.trim().to_string();
    let mlen = message.len() as u32 * 7;
    let lenpow = (2..).find(|&r| 2.pow(r) - r - 1 >= mlen).unwrap();
    let len = 2.pow(lenpow) - 1;

    // the thing we're storing the hamming code in
    let mut bv = BitVec::from_elem(len, false);

    // convert ASCII string to binary
    // IMPORTANT NOTE: the following line takes ownership of the `message'
    // variable. We no longer have access to the original string from this
    // point onwards.
    let bytes = message.into_bytes();
    let bytes_str = bytes.iter()
        .map(|&c| format!("{:0>1$b}", c, 7))
        .collect::<Vec<String>>()
        .concat();
    let mut bytes_iter = bytes_str.chars();

    // set data bits
    for i in 1..len {
        if (i & (i - 1)) != 0 {
            bv.set(i-1, bytes_iter.next().unwrap_or('0') == '1');
        }
    }

    // set parity bits
    for i in 0..lenpow {
        let bi = 2.pow(i)-1;
        let mut parity = false;
        let mut ignore = false;
        let mut counter = 0;
        for j in bi..len {
            if !ignore {
                if bv.get(j).unwrap() {
                    parity = !parity;
                }
            }
            counter += 1;
            if counter >= 2.pow(i) {
                ignore = !ignore;
                counter = 0;
            }
        }
        bv.set(bi, parity);
    }

    format!("{:?}", bv)
}
<|endoftext|>


<commit_before>#[macro_use] extern crate clap;
#[macro_use] extern crate slog;
extern crate slog_term;
extern crate slog_async;
extern crate url;

extern crate rusqlite;

mod schema;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use slog::Drain;
use url::Url;
use rusqlite::Connection;

fn main() {
    // Slog
    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, o!());

    // Sqlite database
    let database_url = "/tmp/rusty.sqlite3";
        let connection = match Connection::open(database_url) {
            Ok(connection) => connection,
            Err(e) =>{
            println!("Error: {} {}", e, database_url);
            process::exit(1)
        },
        };
        init(&connection);

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection, &_log),
        ("list", Some(_)) => list(&connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }
}

    fn init(connection: &Connection) {

        connection.execute("create table if not exists podcast (
                    id integer primary key autoincrement,
                    url text not null,
                    label text not null,
                    created_at timestamp default current_timestamp)", &[]).unwrap();
    }

fn subscribe(args: &ArgMatches, connection: &Connection, logger: &slog::Logger) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let podcast = Podcast { id: 0, url: url.as_str().to_string(), label: args.value_of("label").unwrap_or("").to_string()};
            connection.execute("insert into podcast (url, label)
                    values (?1, ?2)",
                    &[&podcast.url, &podcast.label]).unwrap();
            info!(logger, "Subscribed to: {}", podcast.url;);
        }
        Err(e) => error!(logger, "Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn list(connection: &Connection) {
        let mut stmt = connection.prepare("select id, url, label from podcast").unwrap();

        let podcasts = stmt.query_map(&[], |row| {
            Podcast {
                id: row.get(0),
                url: row.get(1),
                label: row.get(2)
            }
        }).unwrap();

        for podcast in podcasts {
            match podcast {
                Ok(podcast) => println!("{}", podcast.url),
                Err(e) => println!("{}", e) ,
            }
        };
}

<commit_msg>Remove slog support<commit_after>#[macro_use] extern crate clap;
extern crate url;

extern crate rusqlite;

mod schema;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;

fn main() {
    // Sqlite database
    let database_url = "/tmp/rusty.sqlite3";
    let connection = match Connection::open(database_url) {
        Ok(connection) => connection,
        Err(e) =>{
            println!("Error: {} {}", e, database_url);
            process::exit(1)
        },
    };
    init(&connection);

    // Clap
    let yaml = load_yaml!("cli.yml");
    let matches = App::from_yaml(yaml).get_matches();

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }
}

fn init(connection: &Connection) {

    connection.execute("create table if not exists podcast (
                    id integer primary key autoincrement,
                    url text not null,
                    label text not null,
                    created_at timestamp default current_timestamp)", &[]).unwrap();
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let podcast = Podcast { id: 0, url: url.as_str().to_string(), label: args.value_of("label").unwrap_or("").to_string()};
            connection.execute("insert into podcast (url, label)
                    values (?1, ?2)",
                    &[&podcast.url, &podcast.label]).unwrap();
            println!("Subscribed to: {}", podcast.url);
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn list(connection: &Connection) {
    let mut stmt = connection.prepare("select id, url, label from podcast").unwrap();

    let podcasts = stmt.query_map(&[], |row| {
        Podcast {
            id: row.get(0),
            url: row.get(1),
            label: row.get(2)
        }
    }).unwrap();

    for podcast in podcasts {
        match podcast {
            Ok(podcast) => println!("{}", podcast.url),
            Err(e) => println!("{}", e) ,
        }
    };
}

<|endoftext|>


<commit_before>struct Philosopher {
    name: String
}

fn main() {

}
<commit_msg>Add associated function `new()` to `Philosopher`<commit_after>struct Philosopher {
    name: String
}

impl Philosopher {
    fn new(name: &str) -> Philosopher {
        Philosopher {
            name: name.to_string()
        }
    }
}

fn main() {

}
<|endoftext|>


<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 {
                            handle_bot_command(&server, &line.message, source, false, None)
                        } else if target.starts_with('#') {
                            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>Add some info!() with the lines.<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 });
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;

use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json;
use tempfile::NamedTempFile;

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

fn get_revisions(page: &str, limit: i32) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=timestamp|user|comment|ids&rvlimit={}&format=json",
            page, limit))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Vec<(u64, u64)> {
    let json = json::Json::from_str(&get_revisions(page, 60)).unwrap();

    let pages = json.as_object().unwrap().get("query").unwrap().as_object().unwrap().get("pages").unwrap().as_object().unwrap();
    let key = pages.keys().next().unwrap();
    pages.get(key).unwrap().as_object().unwrap()
        .get("revisions").unwrap().as_array().unwrap().into_iter()
        .map(|revision| revision.as_object().unwrap())
        .filter(|revision| { revision.get("comment").unwrap().as_string().unwrap().contains("vandal") })
        .map(|revision| (revision.get("revid").unwrap().as_u64().unwrap(),
                         revision.get("parentid").unwrap().as_u64().unwrap())).collect()
}

fn get_latest_revision_id(page: &str) -> u64 {
    let json = json::Json::from_str(&get_revisions(page, 1)).unwrap();

    let pages = json.as_object().unwrap().get("query").unwrap().as_object().unwrap().get("pages").unwrap().as_object().unwrap();
    let key = pages.keys().next().unwrap();
    pages.get(key).unwrap().as_object().unwrap()
        .get("revisions").unwrap().as_array().unwrap().into_iter()
        .next().unwrap().as_object().unwrap()
        .get("revid").unwrap().as_u64().unwrap()
}

fn get_revision(page: &str, id: u64) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=content&rvlimit=1&rvstartid={}&format=json",
            page, id))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

fn get_revision_content(page: &str, id: u64) -> String {
    let json = json::Json::from_str(&get_revision(page, id)).unwrap();

    // TODO: try!() instead of unwrap()? I genuinely don't know.
    let pages = json.as_object().unwrap().get("query").unwrap().as_object().unwrap().get("pages").unwrap().as_object().unwrap();
    let key = pages.keys().next().unwrap();
    pages.get(key).unwrap().as_object().unwrap()
        .get("revisions").unwrap().as_array().unwrap()
        .into_iter().next().unwrap().as_object().unwrap()
        .get("*").unwrap().as_string().unwrap().to_string()
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor");
    // TODO: this is disgusting.
    let processed_contents = get_revert_revision_ids("Zachary_Taylor").into_iter().fold(
        (get_revision_content("Zachary_Taylor", latest_revid), vec![latest_revid]),
        |accumulated_contents, revision_ids| {
            let revert_revid = revision_ids.0;
            let vandalism_revid = revision_ids.1;
            match merge(&get_revision_content("Zachary_Taylor", revert_revid),
                        &get_revision_content("Zachary_Taylor", vandalism_revid),
                        &accumulated_contents.0) {
                Some(merged_contents) => {
                    let mut merged_revision_ids = accumulated_contents.1;
                    merged_revision_ids.push(revert_revid);
                    (merged_contents, merged_revision_ids)
                }
                None => accumulated_contents
            }
        });

    println!("Restored vandalisms reverted in: {:?}", processed_contents.1);
    println!("{}", processed_contents.0);
}

#[cfg(test)]
mod tests {
    use super::merge;

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }
}
<commit_msg>Extract some code for JSON navigation, to make the JSON parsing more readable.<commit_after>extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;

use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;

use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use rustc_serialize::json::Json::{Array, Object};
use tempfile::NamedTempFile;

// TODO: there are some places where I've handrolled try!() equivalents. Fix those.
// TODO: make sure I'm returning Results everywhere, and propagating errors correctly.

// TODO: Can I get rid of the repeated "Zachary_Taylor"s everywhere? Surely the MediaWiki API doesn't actually need that - I can't imagine revision IDs aren't unique across all pages.

enum JsonPathElement {
    Key(&'static str), // Use the value associated with a specific key.
    Only,              // Use the only value in an array or object.
}

use JsonPathElement::Key;
use JsonPathElement::Only;

// TODO: return &str with a lifetime matching path_elements
fn pretty_print(path_elements: &[JsonPathElement]) -> String {
    // TODO: add (root) to the beginning always.
    if path_elements.is_empty() {
        "(root)".to_string()
    } else {
        path_elements.into_iter().map(|path_element| {
            match path_element {
                // TODO: why do I need the JsonPathElement:: here? I think I don't.
                &JsonPathElement::Key(ref key) => key.to_string(), // TODO: can I remove to_string()?
                &JsonPathElement::Only => "(only)".to_string(),
            }
        }).collect::<Vec<_>>().join(".")
    }
}

// TODO: document
fn get_json_value<'a>(json: &'a Json, path: &[JsonPathElement], index: usize) ->
    Result<&'a Json, String> {
    if index == path.len() {
        return Ok(json);
    }
    match path[index] {
        JsonPathElement::Key(key) => {
            match json {
                &Object(ref obj) => {
                    match obj.get(key) {
                        Some(value) => get_json_value(value, path, index + 1),
                        None => Err(format!("Key {} not found in {}", key, pretty_print(&path[0 .. index]))),
                    }
                }
                _ => Err(format!("Asked for key {} in {}, but value is not an object",
                         key, pretty_print(&path[0 .. index]))),
            }
        },
        JsonPathElement::Only => {
            match json {
                &Object(ref obj) =>
                    if obj.len() == 1 {
                        get_json_value(obj.values().next().unwrap(), path, index + 1)
                    } else {
                        Err(format!("Asked for only key in {}, but object has {} values",
                                    pretty_print(&path[0 .. index]), obj.len()))
                    },
                &Array(ref vec) =>
                    if vec.len() == 1 {
                        get_json_value(vec.first().unwrap(), path, index + 1)
                    } else {
                        Err(format!("Asked for only key in {}, but array has {} elements",
                                    pretty_print(&path[0 .. index]), vec.len()))
                    },
                _ => Err(format!("Asked for only key in {}, but value is not an object or array",
                                 pretty_print(&path[0 .. index]))),
            }
        },
    }
}

fn get_json_array<'a>(json: &'a Json, path: &[JsonPathElement]) -> Result<&'a Vec<Json>, String> {
    match get_json_value(json, path, 0) {
        Ok(&Json::Array(ref value)) => Ok(value),
        Ok(..) => Err(format!("Asked for array {}, but value is not an array",
                              pretty_print(&path[..]))),
        Err(message) => Err(message),
    }
}

fn get_json_number(json: &Json, path: &[JsonPathElement]) -> Result<u64, String> {
    match get_json_value(json, path, 0) {
        Ok(ref value) => {
            if value.is_number() {
                Ok(value.as_u64().unwrap())
            } else {
                Err(format!("Asked for number {}, but value is not a number",
                            pretty_print(&path[..])))
            }
        },
        Err(message) => Err(message),
    }
}

fn get_json_string<'a>(json: &'a Json, path: &[JsonPathElement]) -> Result<&'a str, String> {
    match get_json_value(json, path, 0) {
        Ok(&Json::String(ref value)) => Ok(value),
        Ok(..) => Err(format!("Asked for string {}, but value is not a string",
                              pretty_print(&path[..]))),
        Err(message) => Err(message),
    }
}

fn get_revisions(page: &str, limit: i32) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=timestamp|user|comment|ids&rvlimit={}&format=json",
            page, limit))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();

    body
}

// TODO: this name is terrible.
// Returns pairs of (revid, parentid)
fn get_revert_revision_ids(page: &str) -> Result<Vec<(u64, u64)>, String> {
    let json = Json::from_str(&get_revisions(page, 60)).unwrap();
    let revisions = try!(get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism
    let filtered_revisions =
        revisions.into_iter().filter(|revision| {
            match get_json_string(revision, &[Key("comment")]) {
                Ok(ref comment) => comment.contains("vandal"),
                // TODO: need to warn somehow when this happens, because it generally shouldn't.
                _ => false,
            }
        })
        // Filter out revisions with missing revid or parentid (which shouldn't happen).
        .filter(|revision| {
            match (get_json_number(revision, &[Key("revid")]),
                   get_json_number(revision, &[Key("parentid")])) {
                (Ok(_), Ok(_)) => true,
                // TODO: need to warn somehow when this happens, because it should never.
                _ => false,
            }});
    Ok(filtered_revisions.map(|revision| {
        (get_json_number(revision, &[Key("revid")]).unwrap(),
         get_json_number(revision, &[Key("parentid")]).unwrap())
    }).collect())
}

fn get_latest_revision_id(page: &str) -> u64 {
    let json = Json::from_str(&get_revisions(page, 1)).unwrap();
    let revision_id = get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")]);
    match revision_id {
        Ok(revision_id) => revision_id,
        // TODO: This function should return a Result so we don't have to panic here.
        Err(message) => panic!(format!("Failed to get latest revision ID: \"{}\"", message)),
    }
}

fn get_revision(page: &str, id: u64) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop=content&rvlimit=1&rvstartid={}&format=json",
            page, id))
        .header(Connection::close())
        .send().unwrap();

    let mut body = String::new();
    res.read_to_string(&mut body).unwrap();
    body
}

fn get_revision_content(page: &str, id: u64) -> String {
    // TODO: this function should return a Result so we don't have to unwrap() here.
    get_json_string(
        &Json::from_str(&get_revision(page, id)).unwrap(),
        &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]).unwrap().to_string()
}

fn write_to_temp_file(contents: &str) -> NamedTempFile {
    let tempfile = NamedTempFile::new().unwrap();
    let mut file = OpenOptions::new().write(true).open(tempfile.path()).unwrap();
    file.write_all(contents.as_bytes()).unwrap();
    file.flush().unwrap();
    tempfile
}

// TODO: I'm not so sure these parameter names aren't terrible.
fn merge(old: &str, new1: &str, new2: &str) -> Option<String> {
    let old_tempfile = write_to_temp_file(old);
    let new1_tempfile = write_to_temp_file(new1);
    let new2_tempfile = write_to_temp_file(new2);

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = process.output().unwrap();
    if output.status.success() {
        Some(String::from_utf8(output.stdout).unwrap())
    } else {
        None
    }
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor");
    // TODO: this is disgusting.
    let revision_ids = get_revert_revision_ids("Zachary_Taylor").ok().unwrap();
    let processed_contents = revision_ids.into_iter().fold(
        (get_revision_content("Zachary_Taylor", latest_revid), vec![]),
        |accumulated_contents, revision_ids| {
            let revert_revid = revision_ids.0;
            let vandalism_revid = revision_ids.1;
            match merge(&get_revision_content("Zachary_Taylor", revert_revid),
                        &get_revision_content("Zachary_Taylor", vandalism_revid),
                        &accumulated_contents.0) {
                Some(merged_contents) => {
                    let mut merged_revision_ids = accumulated_contents.1;
                    merged_revision_ids.push(revert_revid);
                    (merged_contents, merged_revision_ids)
                }
                None => accumulated_contents
            }
        });

    println!("Restored vandalisms reverted in: {:?}", processed_contents.1);
    println!("{}", processed_contents.0);
}

#[cfg(test)]
mod tests {
    use super::{get_json_array, get_json_string, get_json_number, merge};
    use super::JsonPathElement::*;
    use rustc_serialize::json::Json;

    #[test]
    fn test_get_json_value_key() {
        assert_eq!(Ok("val"),
                   get_json_string(&Json::from_str("{\"key\": \"val\"}").unwrap(),
                                  &[Key("key")]))
    }

    #[test]
    fn test_get_json_value_only_with_object() {
        let json = Json::from_str("{\"key\": \"val\"}").unwrap();
        let value = get_json_string(&json, &[Only]);
        assert_eq!(Ok("val"), value);
    }

    #[test]
    fn test_get_json_value_only_with_array() {
        let json = Json::from_str("[\"val1\"]").unwrap();
        let value = get_json_string(&json, &[Only]);
        assert_eq!(Ok("val1"), value);
    }

    #[test]
    fn test_get_json_value_multiple_elements() {
        assert_eq!(
            Ok("val3"),
            get_json_string(
                &Json::from_str(
                    "{\"key1\": \"val1\", \"key2\": [{\"key3\": \"val3\"}]}").unwrap(),
                &[Key("key2"), Only, Only]))
    }

    fn assert_error_message<T>(result: &Result<T, String>, expected_message: &str) {
        match result {
            &Ok(..) => panic!(format!("Expected error message: \"{}\"", expected_message)),
            &Err(ref message) if message == expected_message => return,
            &Err(ref message) => panic!(format!("Wrong error message: \"{}\"", message)),
        }
    }

    #[test]
    fn test_get_json_value_key_not_found() {
        assert_error_message(
            &get_json_string(
                &Json::from_str("{\"key\": \"val\"}").unwrap(), &[Key("wrong_key")]),
            "Key wrong_key not found in (root)");
    }

    #[test]
    fn test_get_json_value_key_not_object() {
        for json in &["{\"key1\": 4}",
                      "{\"key1\": false}",
                      "{\"key1\": \"val1\"}",
                      "{\"key1\": [1, 2, 3]}",
                      "{\"key1\": null}"] {
            assert_error_message(
                &get_json_string(&Json::from_str(json).unwrap(), &[Key("key1"), Key("key2")]),
                "Asked for key key2 in key1, but value is not an object");
        }
    }

    #[test]
    fn test_get_json_value_only_object_empty() {
        assert_error_message(
            &get_json_string(
                &Json::from_str("{\"key\": {}}").unwrap(), &[Key("key"), Only]),
            "Asked for only key in key, but object has 0 values");
    }

    #[test]
    fn test_get_json_value_only_object_multiple_values() {
        assert_error_message(
            &get_json_string(
                &Json::from_str("{\"key\": {\"key1\": \"val1\", \"key2\": \"val2\"}}").unwrap(),
                &[Key("key"), Only]),
            "Asked for only key in key, but object has 2 values");
    }

    #[test]
    fn test_get_json_value_only_array_empty() {
        assert_error_message(
            &get_json_string(
                &Json::from_str("{\"key\": []}").unwrap(), &[Key("key"), Only]),
            "Asked for only key in key, but array has 0 elements");
    }

    #[test]
    fn test_get_json_value_only_array_multiple_values() {
        assert_error_message(
            &get_json_string(
                &Json::from_str("{\"key\": [\"val1\", \"val2\"]}").unwrap(),
                &[Key("key"), Only]),
            "Asked for only key in key, but array has 2 elements");
    }

    #[test]
    fn test_get_json_value_only_not_object_or_array() {
        for json in &["{\"key1\": 4}",
                      "{\"key1\": false}",
                      "{\"key1\": \"val1\"}",
                      "{\"key1\": null}"] {
            assert_error_message(
                &get_json_string(&Json::from_str(json).unwrap(), &[Key("key1"), Only]),
                "Asked for only key in key1, but value is not an object or array");
        }
    }

    #[test]
    fn test_get_json_array_wrong_type() {
        for json in &["{\"key1\": 4}",
                      "{\"key1\": \"val1\"}",
                      "{\"key1\": false}",
                      "{\"key1\": {\"key2\": \"val1\"}}",
                      "{\"key1\": null}"] {
            assert_error_message(
                &get_json_array(&Json::from_str(json).unwrap(), &[Key("key1")]),
                "Asked for array key1, but value is not an array");
        }
    }

    #[test]
    fn test_get_json_number_wrong_type() {
        for json in &["{\"key1\": \"val1\"}",
                      "{\"key1\": false}",
                      "{\"key1\": [\"val1\"]}",
                      "{\"key1\": {\"key2\": \"val1\"}}",
                      "{\"key1\": null}"] {
            assert_error_message(
                &get_json_number(&Json::from_str(json).unwrap(), &[Key("key1")]),
                "Asked for number key1, but value is not a number");
        }
    }

    #[test]
    fn test_get_json_string_wrong_type() {
        for json in &["{\"key1\": 4}",
                      "{\"key1\": false}",
                      "{\"key1\": [\"val1\"]}",
                      "{\"key1\": {\"key2\": \"val1\"}}",
                      "{\"key1\": null}"] {
            assert_error_message(
                &get_json_string(&Json::from_str(json).unwrap(), &[Key("key1")]),
                "Asked for string key1, but value is not a string");
        }
    }

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!("First line changed.\n\nSecond line changed.\n", merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2));
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!("First line さようなら.\n\nSecond line 𐅃.\n", merge(old, new1, new2).unwrap());
    }
}
<|endoftext|>


<commit_before>#![allow(non_upper_case_globals, dead_code)]
#![feature(box_syntax)]
#![feature(slice_patterns)]

mod gamestate;
use gamestate::*;

extern crate rand;
//use rand::{Rng, SeedableRng, XorShiftRng};

extern crate piston;
extern crate image;
extern crate graphics;
extern crate sdl2_window;
extern crate opengl_graphics;

use std::rc::Rc;
use std::cell::RefCell;

use opengl_graphics::{ GlGraphics, OpenGL, Texture };
use sdl2_window::Sdl2Window;
use image::GenericImage;
use piston::input::{ MouseButton };
use piston::window::{WindowSettings, AdvancedWindow};
use piston::event::*;

fn main() {
    let mut game = GameState::new();

    let mut paused = true;
    let mut raining = true;
    let mut redraw_needed = true;

    let mut mouse_buttons_pressed = (false, false); //left, right

    let opengl = OpenGL::_3_2;
    let (width, height) = (WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32);
    let window = Sdl2Window::new(
        opengl,
        WindowSettings::new("Falling Sand", (width, height))
        .exit_on_esc(true)
        //what those do?
        .vsync(true)
        .samples(4)
        .fullscreen(true)
    );
    let window = Rc::new(RefCell::new(window));
    let mut image = image::ImageBuffer::from_pixel(width, height, image::Rgba([0, 0, 0, 255]));
    let blank = image.clone().into_raw();
    let mut texture = Texture::from_image(&image);
    let ref mut gl = GlGraphics::new(opengl);
    for e in window.clone().events().max_fps(120).ups(60) {
        use piston::event::*;
        use piston::input::{Button, Key};

        if let Some(Button::Keyboard(key)) = e.press_args() {
            match key {
                Key::Space => {
                    paused = !paused;
                    window.borrow_mut().set_capture_cursor(!paused); //remove cursor when game is running
                },
                Key::R => {raining = !raining},
                _ => {}
            }
        }

        if let Some(_) = e.update_args() {
            //Update game and image on every loop
            if !paused {
                if raining {game.rain()}
                game.update();
                redraw_needed = true;
            }//if !paused {
        }

        if paused {
            if let Some(button) = e.press_args() {
                match button {
                    Button::Mouse(MouseButton::Left) => {mouse_buttons_pressed.0 = true},
                    Button::Mouse(MouseButton::Right) => {mouse_buttons_pressed.1 = true},
                    _ => {}
                }
            }
            if let Some(button) = e.release_args() {
                match button {
                    Button::Mouse(MouseButton::Left) => {mouse_buttons_pressed.0 = false},
                    Button::Mouse(MouseButton::Right) => {mouse_buttons_pressed.1 = false},
                    _ => {}
                }
            }
            if let Some(pos) = e.mouse_cursor_args() {
                redraw_needed = true;
                //get mouse coordinates and draw something if there is place
                let (mouse_x, mouse_y) = (pos[0] as i16, pos[1] as i16);

                match mouse_buttons_pressed {
                   (true, false) => {
                       game.paint_square_obstacles(mouse_x, mouse_y, 30, 30);
                   }
                   (false, true) => {
                       //remove everything from map and both lists
                       game.remove_square(mouse_x, mouse_y, 30, 30);
                   }
                   _ => {}
               }
            }
        } //if paused

        if redraw_needed {
            if let Some(args) = e.render_args() {
                image = image::ImageBuffer::from_vec(width, height, blank.clone()).unwrap();
                for particle in game.particles.iter() {
                    image.put_pixel(particle.x as u32, particle.y as u32,
                                    image::Rgba([238,232,170,255]));
                }
                for obstacle in game.obstacles.iter() {
                    image.put_pixel(obstacle.x as u32, obstacle.y as u32,
                                    image::Rgba([128,0,0,255]));
                }
                texture.update(&image);
                gl.draw(args.viewport(), |c, gl| {
                    graphics::clear([1.0; 4], gl);
                    graphics::image(&texture, c.transform, gl);
                });

                redraw_needed = false;
            }
        };
    }
}
<commit_msg>Remove oversampling to reduce flickering to zero<commit_after>#![allow(non_upper_case_globals, dead_code)]
#![feature(box_syntax)]
#![feature(slice_patterns)]

mod gamestate;
use gamestate::*;

extern crate rand;
//use rand::{Rng, SeedableRng, XorShiftRng};

extern crate piston;
extern crate image;
extern crate graphics;
extern crate sdl2_window;
extern crate opengl_graphics;

use std::rc::Rc;
use std::cell::RefCell;

use opengl_graphics::{ GlGraphics, OpenGL, Texture };
use sdl2_window::Sdl2Window;
use image::GenericImage;
use piston::input::{ MouseButton };
use piston::window::{WindowSettings, AdvancedWindow};
use piston::event::*;

fn main() {
    let mut game = GameState::new();

    let mut paused = true;
    let mut raining = true;
    let mut redraw_needed = true;

    let mut mouse_buttons_pressed = (false, false); //left, right

    let opengl = OpenGL::_3_2;
    let (width, height) = (WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32);
    let window = Sdl2Window::new(
        opengl,
        WindowSettings::new("Falling Sand", (width, height))
        .exit_on_esc(true)
        //what those do?
        .vsync(true)
        .samples(1)
        .fullscreen(true)
    );
    let window = Rc::new(RefCell::new(window));
    let mut image = image::ImageBuffer::from_pixel(width, height, image::Rgba([0, 0, 0, 255]));
    let blank = image.clone().into_raw();
    let mut texture = Texture::from_image(&image);
    let ref mut gl = GlGraphics::new(opengl);
    for e in window.clone().events().max_fps(120).ups(60) {
        use piston::event::*;
        use piston::input::{Button, Key};

        if let Some(Button::Keyboard(key)) = e.press_args() {
            match key {
                Key::Space => {
                    paused = !paused;
                    window.borrow_mut().set_capture_cursor(!paused); //remove cursor when game is running
                },
                Key::R => {raining = !raining},
                _ => {}
            }
        }

        if let Some(_) = e.update_args() {
            //Update game and image on every loop
            if !paused {
                if raining {game.rain()}
                game.update();
                redraw_needed = true;
            }//if !paused {
        }

        if paused {
            if let Some(button) = e.press_args() {
                match button {
                    Button::Mouse(MouseButton::Left) => {mouse_buttons_pressed.0 = true},
                    Button::Mouse(MouseButton::Right) => {mouse_buttons_pressed.1 = true},
                    _ => {}
                }
            }
            if let Some(button) = e.release_args() {
                match button {
                    Button::Mouse(MouseButton::Left) => {mouse_buttons_pressed.0 = false},
                    Button::Mouse(MouseButton::Right) => {mouse_buttons_pressed.1 = false},
                    _ => {}
                }
            }
            if let Some(pos) = e.mouse_cursor_args() {
                redraw_needed = true;
                //get mouse coordinates and draw something if there is place
                let (mouse_x, mouse_y) = (pos[0] as i16, pos[1] as i16);

                match mouse_buttons_pressed {
                   (true, false) => {
                       game.paint_square_obstacles(mouse_x, mouse_y, 30, 30);
                   }
                   (false, true) => {
                       //remove everything from map and both lists
                       game.remove_square(mouse_x, mouse_y, 30, 30);
                   }
                   _ => {}
               }
            }
        } //if paused

        if redraw_needed {
            if let Some(args) = e.render_args() {
                image = image::ImageBuffer::from_vec(width, height, blank.clone()).unwrap();
                for particle in game.particles.iter() {
                    image.put_pixel(particle.x as u32, particle.y as u32,
                                    image::Rgba([238,232,170,255]));
                }
                for obstacle in game.obstacles.iter() {
                    image.put_pixel(obstacle.x as u32, obstacle.y as u32,
                                    image::Rgba([128,0,0,255]));
                }
                texture.update(&image);
                gl.draw(args.viewport(), |c, gl| {
                    graphics::clear([1.0; 4], gl);
                    graphics::image(&texture, c.transform, gl);
                });

                redraw_needed = false;
            }
        };
    }
}
<|endoftext|>


<commit_before>extern crate ansi_term;
extern crate getopts;
extern crate isatty;
extern crate regex;
extern crate walkdir;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::Write;
use std::path::Path;
use std::process;

use ansi_term::Colour;
use getopts::Options;
use isatty::stdout_isatty;
use regex::{Regex, RegexBuilder};
use walkdir::{WalkDir, DirEntry, WalkDirIterator};

struct FdOptions {
    case_sensitive: bool,
    search_full_path: bool,
    search_hidden: bool,
    follow_links: bool,
    colored: bool,
    max_depth: usize
}

/// Print a search result to the console.
fn print_entry(entry: &DirEntry, path_str: &str, config: &FdOptions) {
    if config.colored {
        let style = match entry {
            e if e.path_is_symbolic_link() => Colour::Purple,
            e if e.path().is_dir()         => Colour::Cyan,
            _                              => Colour::White
        };
        println!("{}", style.paint(path_str));
    } else {
        println!("{}", path_str);
    }
}

/// Check if filename of entry starts with a dot.
fn is_hidden(entry: &DirEntry) -> bool {
    entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false)
}

/// Recursively scan the given root path and search for files / pathnames
/// matching the pattern.
fn scan(root: &Path, pattern: &Regex, config: &FdOptions) {
    let walker = WalkDir::new(root)
                     .follow_links(config.follow_links)
                     .max_depth(config.max_depth)
                     .into_iter()
                     .filter_entry(|e| config.search_hidden || !is_hidden(e))
                     .filter_map(|e| e.ok())
                     .filter(|e| e.path() != root);

    for entry in walker {
        let path_rel = match entry.path().strip_prefix(root) {
            Ok(p) => p,
            Err(_) => continue
        };

        if let Some(path_str) = path_rel.to_str() {
            let res =
                if config.search_full_path {
                    pattern.find(path_str)
                } else {
                    if !path_rel.is_file() { continue }

                    path_rel.file_name()
                            .and_then(OsStr::to_str)
                            .and_then(|s| pattern.find(s))
                };

            res.map(|_| print_entry(&entry, path_str, &config));
        }
    }
}

/// Print error message to stderr and exit with status `1`.
fn error(message: &str) -> ! {
    writeln!(&mut std::io::stderr(), "{}", message)
        .expect("Failed writing to stderr");
    process::exit(1);
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help message");
    opts.optflag("s", "sensitive",
                      "case-sensitive search (default: smart case)");
    opts.optflag("f", "filename",
                      "search filenames only (default: full path)");
    opts.optflag("", "hidden",
                      "search hidden files/directories (default: off)");
    opts.optflag("F", "follow", "follow symlinks (default: off)");
    opts.optflag("n", "no-color", "do not colorize output");
    opts.optopt("d", "max-depth", "maximum search depth", "DEPTH");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => m,
        Err(e) => error(e.description())
    };

    if matches.opt_present("h") {
        let brief = "Usage: fd [PATTERN]";
        print!("{}", opts.usage(&brief));
        process::exit(1);
    }

    let empty = String::new();
    let pattern = matches.free.get(0).unwrap_or(&empty);

    let current_dir_buf = match env::current_dir() {
        Ok(cd) => cd,
        Err(_) => error("Could not get current directory!")
    };
    let current_dir = current_dir_buf.as_path();

    let config = FdOptions {
        // The search will be case-sensitive if the command line flag is set or
        // if the pattern has an uppercase character (smart case).
        case_sensitive:    matches.opt_present("sensitive") ||
                           pattern.chars().any(char::is_uppercase),
        search_full_path: !matches.opt_present("filename"),
        search_hidden:     matches.opt_present("hidden"),
        colored:          !matches.opt_present("no-color") &&
                           stdout_isatty(),
        follow_links:      matches.opt_present("follow"),
        max_depth:
            matches.opt_str("max-depth")
                   .and_then(|ds| usize::from_str_radix(&ds, 10).ok())
                   .unwrap_or(25)
    };

    match RegexBuilder::new(pattern)
              .case_insensitive(!config.case_sensitive)
              .build() {
        Ok(re)   => scan(&current_dir, &re, &config),
        Err(err) => error(err.description())
    }
}
<commit_msg>Re-structure<commit_after>extern crate ansi_term;
extern crate getopts;
extern crate isatty;
extern crate regex;
extern crate walkdir;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::Write;
use std::path::Path;
use std::process;

use ansi_term::Colour;
use getopts::Options;
use isatty::stdout_isatty;
use regex::{Regex, RegexBuilder};
use walkdir::{WalkDir, DirEntry, WalkDirIterator};

struct FdOptions {
    case_sensitive: bool,
    search_full_path: bool,
    search_hidden: bool,
    follow_links: bool,
    colored: bool,
    max_depth: usize
}

/// Print a search result to the console.
fn print_entry(entry: &DirEntry, path_rel: &Path, config: &FdOptions) {
    let path_str = match path_rel.to_str() {
        Some(p) => p,
        None    => return
    };

    if config.colored {
        let style = match entry {
            e if e.path_is_symbolic_link() => Colour::Purple,
            e if e.path().is_dir()         => Colour::Cyan,
            _                              => Colour::White
        };
        println!("{}", style.paint(path_str));
    } else {
        println!("{}", path_str);
    }
}

/// Check if filename of entry starts with a dot.
fn is_hidden(entry: &DirEntry) -> bool {
    entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false)
}

/// Recursively scan the given root path and search for files / pathnames
/// matching the pattern.
fn scan(root: &Path, pattern: &Regex, config: &FdOptions) {
    let walker = WalkDir::new(root)
                     .follow_links(config.follow_links)
                     .max_depth(config.max_depth)
                     .into_iter()
                     .filter_entry(|e| config.search_hidden || !is_hidden(e))
                     .filter_map(|e| e.ok())
                     .filter(|e| e.path() != root);

    for entry in walker {
        let path_rel = match entry.path().strip_prefix(root) {
            Ok(p) => p,
            Err(_) => continue
        };

        let search_str =
            if config.search_full_path {
                path_rel.to_str()
            } else {
                if !path_rel.is_file() { continue }

                path_rel.file_name()
                        .and_then(OsStr::to_str)
            };

        search_str.and_then(|s| pattern.find(s))
                  .map(|_| print_entry(&entry, path_rel, &config));
    }
}

/// Print error message to stderr and exit with status `1`.
fn error(message: &str) -> ! {
    writeln!(&mut std::io::stderr(), "{}", message)
        .expect("Failed writing to stderr");
    process::exit(1);
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help message");
    opts.optflag("s", "sensitive",
                      "case-sensitive search (default: smart case)");
    opts.optflag("f", "filename",
                      "search filenames only (default: full path)");
    opts.optflag("", "hidden",
                      "search hidden files/directories (default: off)");
    opts.optflag("F", "follow", "follow symlinks (default: off)");
    opts.optflag("n", "no-color", "do not colorize output");
    opts.optopt("d", "max-depth", "maximum search depth", "DEPTH");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => m,
        Err(e) => error(e.description())
    };

    if matches.opt_present("h") {
        let brief = "Usage: fd [PATTERN]";
        print!("{}", opts.usage(&brief));
        process::exit(1);
    }

    let empty = String::new();
    let pattern = matches.free.get(0).unwrap_or(&empty);

    let current_dir_buf = match env::current_dir() {
        Ok(cd) => cd,
        Err(_) => error("Could not get current directory!")
    };
    let current_dir = current_dir_buf.as_path();

    let config = FdOptions {
        // The search will be case-sensitive if the command line flag is set or
        // if the pattern has an uppercase character (smart case).
        case_sensitive:    matches.opt_present("sensitive") ||
                           pattern.chars().any(char::is_uppercase),
        search_full_path: !matches.opt_present("filename"),
        search_hidden:     matches.opt_present("hidden"),
        colored:          !matches.opt_present("no-color") &&
                           stdout_isatty(),
        follow_links:      matches.opt_present("follow"),
        max_depth:
            matches.opt_str("max-depth")
                   .and_then(|ds| usize::from_str_radix(&ds, 10).ok())
                   .unwrap_or(25)
    };

    match RegexBuilder::new(pattern)
              .case_insensitive(!config.case_sensitive)
              .build() {
        Ok(re)   => scan(&current_dir, &re, &config),
        Err(err) => error(err.description())
    }
}
<|endoftext|>