<commit_before>//! An implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust.
//
// Copyright (c) 2015-2018 tealdeer developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be
// copied, modified, or distributed except according to those terms.

#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::similar_names)]
#![allow(clippy::stutter)]

#[cfg(feature = "logging")]
extern crate env_logger;

use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process;
use std::time::Duration;

use ansi_term::Color;
use app_dirs::AppInfo;
use docopt::Docopt;
use pager::Pager;
use serde_derive::Deserialize;

mod cache;
mod config;
mod error;
mod formatter;
mod tokenizer;
mod types;

use crate::cache::Cache;
use crate::config::{get_config_path, make_default_config, Config};
use crate::error::TealdeerError::{CacheError, ConfigError, UpdateError};
use crate::formatter::print_lines;
use crate::tokenizer::Tokenizer;
use crate::types::OsType;

const NAME: &str = "tealdeer";
const APP_INFO: AppInfo = AppInfo {
    name: NAME,
    author: NAME,
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "
Usage:

    tldr [options] <command>...
    tldr [options]

Options:

    -h --help           Show this screen
    -v --version        Show version information
    -l --list           List all commands in the cache
    -f --render <file>  Render a specific markdown file
    -o --os <type>      Override the operating system [linux, osx, sunos, windows]
    -u --update         Update the local cache
    -c --clear-cache    Clear the local cache
    -p --pager          Use a pager to page output
    -q --quiet          Suppress informational messages
    --config-path       Show config file path
    --seed-config       Create a basic config

Examples:

    $ tldr tar
    $ tldr --list

To control the cache:

    $ tldr --update
    $ tldr --clear-cache

To render a local file (for testing):

    $ tldr --render /path/to/file.md
";
const ARCHIVE_URL: &str = "https://github.com/tldr-pages/tldr/archive/master.tar.gz";
const PAGER_COMMAND: &str = "less -R";
const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days

#[derive(Debug, Deserialize)]
struct Args {
    arg_command: Option<Vec<String>>,
    flag_help: bool,
    flag_version: bool,
    flag_list: bool,
    flag_render: Option<String>,
    flag_os: Option<OsType>,
    flag_update: bool,
    flag_clear_cache: bool,
    flag_pager: bool,
    flag_quiet: bool,
    flag_config_path: bool,
    flag_seed_config: bool,
}

/// Print page by path
fn print_page(path: &Path, enable_styles: bool) -> Result<(), String> {
    // Open file
    let file = File::open(path).map_err(|msg| format!("Could not open file: {}", msg))?;
    let reader = BufReader::new(file);

    // Look up config file, if none is found fall back to default config.
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    // Create tokenizer and print output
    let mut tokenizer = Tokenizer::new(reader);
    print_lines(&mut tokenizer, &config);

    Ok(())
}

/// Set up display pager
fn configure_pager(args: &Args, enable_styles: bool) {
    // Flags have precedence
    if args.flag_pager {
        Pager::with_default_pager(PAGER_COMMAND).setup();
        return;
    }

    // Then check config
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    if config.display.use_pager {
        Pager::with_default_pager(PAGER_COMMAND).setup();
    }
}

/// Check the cache for freshness
fn check_cache(args: &Args, cache: &Cache) {
    if !args.flag_update {
        match cache.last_update() {
            Some(ago) if ago > MAX_CACHE_AGE => {
                if args.flag_quiet {
                    return;
                }
                println!(
                    "{}",
                    Color::Red.paint(format!(
                        "Cache wasn't updated for more than {} days.\n\
                         You should probably run `tldr --update` soon.",
                        MAX_CACHE_AGE.as_secs() / 24 / 3600
                    ))
                );
            }
            None => {
                eprintln!("Cache not found. Please run `tldr --update`.");
                process::exit(1);
            }
            _ => {}
        }
    };
}

#[cfg(feature = "logging")]
fn init_log() {
    env_logger::init();
}

#[cfg(not(feature = "logging"))]
fn init_log() {}

#[cfg(target_os = "linux")]
fn get_os() -> OsType {
    OsType::Linux
}

#[cfg(any(target_os = "macos",
          target_os = "freebsd",
          target_os = "netbsd",
          target_os = "openbsd",
          target_os = "dragonfly"))]
fn get_os() -> OsType {
    OsType::OsX
}

#[cfg(target_os = "windows")]
fn get_os() -> OsType {
    OsType::Windows
}

#[cfg(not(any(target_os = "linux",
              target_os = "macos",
              target_os = "freebsd",
              target_os = "netbsd",
              target_os = "openbsd",
              target_os = "dragonfly",
              target_os = "windows")))]
fn get_os() -> OsType {
    OsType::Other
}

fn main() {
    // Initialize logger
    init_log();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    // Show version and exit
    if args.flag_version {
        let os = get_os();
        println!("{} v{} ({})", NAME, VERSION, os);
        process::exit(0);
    }

	// Determine the usage of styles
    #[cfg(target_os = "windows")]
    let enable_styles = ansi_term::enable_ansi_support().is_ok();
    #[cfg(not(target_os = "windows"))]
    let enable_styles = true;

    // Configure pager
    configure_pager(&args, enable_styles);

    // Specify target OS
    let os: OsType = match args.flag_os {
        Some(os) => os,
        None => get_os(),
    };

    // Initialize cache
    let cache = Cache::new(ARCHIVE_URL, os);

    // Clear cache, pass through
    if args.flag_clear_cache {
        cache.clear().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not delete cache: {}", msg)
                }
            };
            process::exit(1);
        });
        if !args.flag_quiet {
            println!("Successfully deleted cache.");
        }
    }

    // Update cache, pass through
    if args.flag_update {
        cache.update().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not update cache: {}", msg)
                }
            };
            process::exit(1);
        });
        if !args.flag_quiet {
            println!("Successfully updated cache.");
        }
    }

    // Show config file and path, pass through
    if args.flag_config_path {
        match get_config_path() {
            Ok(config_file_path) => {
                println!("Config path is: {}", config_file_path.to_str().unwrap());
            }
            Err(ConfigError(msg)) => {
                eprintln!("Could not look up config_path: {}", msg);
                process::exit(1);
            }
            Err(_) => {
                eprintln!("Unknown error");
                process::exit(1);
            }
        }
    }

    // Create a basic config and exit
    if args.flag_seed_config {
        match make_default_config() {
            Ok(config_file_path) => {
                println!(
                    "Successfully created seed config file here: {}",
                    config_file_path.to_str().unwrap()
                );
                process::exit(0);
            }
            Err(ConfigError(msg)) => {
                eprintln!("Could not create seed config: {}", msg);
                process::exit(1);
            }
            Err(_) => {
                eprintln!("Unkown error");
                process::exit(1);
            }
        }
    }

    // Render local file and exit
    if let Some(ref file) = args.flag_render {
        let path = PathBuf::from(file);
        if let Err(msg) = print_page(&path, enable_styles) {
            eprintln!("{}", msg);
            process::exit(1);
        } else {
            process::exit(0);
        };
    }

    // List cached commands and exit
    if args.flag_list {
        // Check cache for freshness
        check_cache(&args, &cache);

        // Get list of pages
        let pages = cache.list_pages().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not get list of pages: {}", msg)
                }
            }
            process::exit(1);
        });

        // Print pages
        println!("{}", pages.join(", "));
        process::exit(0);
    }

    // Show command from cache
    if let Some(ref command) = args.arg_command {
        let command = command.join("-");
        // Check cache for freshness
        check_cache(&args, &cache);

        // Search for command in cache
        if let Some(path) = cache.find_page(&command) {
            if let Err(msg) = print_page(&path, enable_styles) {
                eprintln!("{}", msg);
                process::exit(1);
            } else {
                process::exit(0);
            }
        } else {
            if !args.flag_quiet {
                println!("Page {} not found in cache", &command);
                println!("Try updating with `tldr --update`, or submit a pull request to:");
                println!("https://github.com/tldr-pages/tldr");
            }
            process::exit(1);
        }
    }

    // Some flags can be run without a command.
    if !(args.flag_update || args.flag_clear_cache || args.flag_config_path) {
        eprintln!("{}", USAGE);
        process::exit(1);
    }
}

#[cfg(test)]
mod test {
    use docopt::{Docopt, Error};
    use crate::{Args, OsType, USAGE};

    fn test_helper(argv: &[&str]) -> Result<Args, Error> {
        Docopt::new(USAGE).and_then(|d| d.argv(argv.iter()).deserialize())
    }

    #[test]
    fn test_docopt_os_case_insensitive() {
        let argv = vec!["cp", "--os", "LiNuX"];
        let os = test_helper(&argv).unwrap().flag_os.unwrap();
        assert_eq!(OsType::Linux, os);
    }

    #[test]
    fn test_docopt_expect_error() {
        let argv = vec!["cp", "--os", "lindows"];
        assert!(!test_helper(&argv).is_ok());
    }
}
<commit_msg>Fix typo in error message for flag_seed_config (#91)<commit_after>//! An implementation of [tldr](https://github.com/tldr-pages/tldr) in Rust.
//
// Copyright (c) 2015-2018 tealdeer developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be
// copied, modified, or distributed except according to those terms.

#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::similar_names)]
#![allow(clippy::stutter)]

#[cfg(feature = "logging")]
extern crate env_logger;

use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process;
use std::time::Duration;

use ansi_term::Color;
use app_dirs::AppInfo;
use docopt::Docopt;
use pager::Pager;
use serde_derive::Deserialize;

mod cache;
mod config;
mod error;
mod formatter;
mod tokenizer;
mod types;

use crate::cache::Cache;
use crate::config::{get_config_path, make_default_config, Config};
use crate::error::TealdeerError::{CacheError, ConfigError, UpdateError};
use crate::formatter::print_lines;
use crate::tokenizer::Tokenizer;
use crate::types::OsType;

const NAME: &str = "tealdeer";
const APP_INFO: AppInfo = AppInfo {
    name: NAME,
    author: NAME,
};
const VERSION: &str = env!("CARGO_PKG_VERSION");
const USAGE: &str = "
Usage:

    tldr [options] <command>...
    tldr [options]

Options:

    -h --help           Show this screen
    -v --version        Show version information
    -l --list           List all commands in the cache
    -f --render <file>  Render a specific markdown file
    -o --os <type>      Override the operating system [linux, osx, sunos, windows]
    -u --update         Update the local cache
    -c --clear-cache    Clear the local cache
    -p --pager          Use a pager to page output
    -q --quiet          Suppress informational messages
    --config-path       Show config file path
    --seed-config       Create a basic config

Examples:

    $ tldr tar
    $ tldr --list

To control the cache:

    $ tldr --update
    $ tldr --clear-cache

To render a local file (for testing):

    $ tldr --render /path/to/file.md
";
const ARCHIVE_URL: &str = "https://github.com/tldr-pages/tldr/archive/master.tar.gz";
const PAGER_COMMAND: &str = "less -R";
const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days

#[derive(Debug, Deserialize)]
struct Args {
    arg_command: Option<Vec<String>>,
    flag_help: bool,
    flag_version: bool,
    flag_list: bool,
    flag_render: Option<String>,
    flag_os: Option<OsType>,
    flag_update: bool,
    flag_clear_cache: bool,
    flag_pager: bool,
    flag_quiet: bool,
    flag_config_path: bool,
    flag_seed_config: bool,
}

/// Print page by path
fn print_page(path: &Path, enable_styles: bool) -> Result<(), String> {
    // Open file
    let file = File::open(path).map_err(|msg| format!("Could not open file: {}", msg))?;
    let reader = BufReader::new(file);

    // Look up config file, if none is found fall back to default config.
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    // Create tokenizer and print output
    let mut tokenizer = Tokenizer::new(reader);
    print_lines(&mut tokenizer, &config);

    Ok(())
}

/// Set up display pager
fn configure_pager(args: &Args, enable_styles: bool) {
    // Flags have precedence
    if args.flag_pager {
        Pager::with_default_pager(PAGER_COMMAND).setup();
        return;
    }

    // Then check config
    let config = match Config::load(enable_styles) {
        Ok(config) => config,
        Err(ConfigError(msg)) => {
            eprintln!("Could not load config: {}", msg);
            process::exit(1);
        }
        Err(e) => {
            eprintln!("Could not load config: {}", e);
            process::exit(1);
        }
    };

    if config.display.use_pager {
        Pager::with_default_pager(PAGER_COMMAND).setup();
    }
}

/// Check the cache for freshness
fn check_cache(args: &Args, cache: &Cache) {
    if !args.flag_update {
        match cache.last_update() {
            Some(ago) if ago > MAX_CACHE_AGE => {
                if args.flag_quiet {
                    return;
                }
                println!(
                    "{}",
                    Color::Red.paint(format!(
                        "Cache wasn't updated for more than {} days.\n\
                         You should probably run `tldr --update` soon.",
                        MAX_CACHE_AGE.as_secs() / 24 / 3600
                    ))
                );
            }
            None => {
                eprintln!("Cache not found. Please run `tldr --update`.");
                process::exit(1);
            }
            _ => {}
        }
    };
}

#[cfg(feature = "logging")]
fn init_log() {
    env_logger::init();
}

#[cfg(not(feature = "logging"))]
fn init_log() {}

#[cfg(target_os = "linux")]
fn get_os() -> OsType {
    OsType::Linux
}

#[cfg(any(target_os = "macos",
          target_os = "freebsd",
          target_os = "netbsd",
          target_os = "openbsd",
          target_os = "dragonfly"))]
fn get_os() -> OsType {
    OsType::OsX
}

#[cfg(target_os = "windows")]
fn get_os() -> OsType {
    OsType::Windows
}

#[cfg(not(any(target_os = "linux",
              target_os = "macos",
              target_os = "freebsd",
              target_os = "netbsd",
              target_os = "openbsd",
              target_os = "dragonfly",
              target_os = "windows")))]
fn get_os() -> OsType {
    OsType::Other
}

fn main() {
    // Initialize logger
    init_log();

    // Parse arguments
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    // Show version and exit
    if args.flag_version {
        let os = get_os();
        println!("{} v{} ({})", NAME, VERSION, os);
        process::exit(0);
    }

    // Determine the usage of styles
    #[cfg(target_os = "windows")]
    let enable_styles = ansi_term::enable_ansi_support().is_ok();
    #[cfg(not(target_os = "windows"))]
    let enable_styles = true;

    // Configure pager
    configure_pager(&args, enable_styles);

    // Specify target OS
    let os: OsType = match args.flag_os {
        Some(os) => os,
        None => get_os(),
    };

    // Initialize cache
    let cache = Cache::new(ARCHIVE_URL, os);

    // Clear cache, pass through
    if args.flag_clear_cache {
        cache.clear().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not delete cache: {}", msg)
                }
            };
            process::exit(1);
        });
        if !args.flag_quiet {
            println!("Successfully deleted cache.");
        }
    }

    // Update cache, pass through
    if args.flag_update {
        cache.update().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not update cache: {}", msg)
                }
            };
            process::exit(1);
        });
        if !args.flag_quiet {
            println!("Successfully updated cache.");
        }
    }

    // Show config file and path, pass through
    if args.flag_config_path {
        match get_config_path() {
            Ok(config_file_path) => {
                println!("Config path is: {}", config_file_path.to_str().unwrap());
            }
            Err(ConfigError(msg)) => {
                eprintln!("Could not look up config_path: {}", msg);
                process::exit(1);
            }
            Err(_) => {
                eprintln!("Unknown error");
                process::exit(1);
            }
        }
    }

    // Create a basic config and exit
    if args.flag_seed_config {
        match make_default_config() {
            Ok(config_file_path) => {
                println!(
                    "Successfully created seed config file here: {}",
                    config_file_path.to_str().unwrap()
                );
                process::exit(0);
            }
            Err(ConfigError(msg)) => {
                eprintln!("Could not create seed config: {}", msg);
                process::exit(1);
            }
            Err(_) => {
                eprintln!("Unknown error");
                process::exit(1);
            }
        }
    }

    // Render local file and exit
    if let Some(ref file) = args.flag_render {
        let path = PathBuf::from(file);
        if let Err(msg) = print_page(&path, enable_styles) {
            eprintln!("{}", msg);
            process::exit(1);
        } else {
            process::exit(0);
        };
    }

    // List cached commands and exit
    if args.flag_list {
        // Check cache for freshness
        check_cache(&args, &cache);

        // Get list of pages
        let pages = cache.list_pages().unwrap_or_else(|e| {
            match e {
                CacheError(msg) | ConfigError(msg) | UpdateError(msg) => {
                    eprintln!("Could not get list of pages: {}", msg)
                }
            }
            process::exit(1);
        });

        // Print pages
        println!("{}", pages.join(", "));
        process::exit(0);
    }

    // Show command from cache
    if let Some(ref command) = args.arg_command {
        let command = command.join("-");
        // Check cache for freshness
        check_cache(&args, &cache);

        // Search for command in cache
        if let Some(path) = cache.find_page(&command) {
            if let Err(msg) = print_page(&path, enable_styles) {
                eprintln!("{}", msg);
                process::exit(1);
            } else {
                process::exit(0);
            }
        } else {
            if !args.flag_quiet {
                println!("Page {} not found in cache", &command);
                println!("Try updating with `tldr --update`, or submit a pull request to:");
                println!("https://github.com/tldr-pages/tldr");
            }
            process::exit(1);
        }
    }

    // Some flags can be run without a command.
    if !(args.flag_update || args.flag_clear_cache || args.flag_config_path) {
        eprintln!("{}", USAGE);
        process::exit(1);
    }
}

#[cfg(test)]
mod test {
    use docopt::{Docopt, Error};
    use crate::{Args, OsType, USAGE};

    fn test_helper(argv: &[&str]) -> Result<Args, Error> {
        Docopt::new(USAGE).and_then(|d| d.argv(argv.iter()).deserialize())
    }

    #[test]
    fn test_docopt_os_case_insensitive() {
        let argv = vec!["cp", "--os", "LiNuX"];
        let os = test_helper(&argv).unwrap().flag_os.unwrap();
        assert_eq!(OsType::Linux, os);
    }

    #[test]
    fn test_docopt_expect_error() {
        let argv = vec!["cp", "--os", "lindows"];
        assert!(!test_helper(&argv).is_ok());
    }
}
<|endoftext|>


<commit_before>#[macro_use] extern crate clap;

extern crate libsniff;
extern crate colored;
extern crate regex;

use libsniff::prelude::*;
use clap::App;
use colored::*;

#[allow(unknown_lints)]
#[allow(cyclomatic_complexity)]
fn main() {

    // command-line parser
    #[cfg(feature = "english")]
    let yaml = load_yaml!("options-en.yml");
    #[cfg(feature = "français")]
    let yaml = load_yaml!("options-fr.yml");
    #[cfg(feature = "deutsch")]
    let yaml = load_yaml!("options-de.yml");
    let matches = App::from_yaml(yaml).version(crate_version!()).get_matches();

    // find large files
    if let Some(command) = matches.subcommand_matches("fat") {

        // set threshhold
        let min_bytes = match threshhold(command.value_of("threshhold")) {
            Some(t) => t,
            _ => 1048576,
        };

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // don't print warnings
        let silent = command.is_present("silent");

        // set regex for exclusions
        let regex = command.value_of("excludes");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // get relevant filenames &c.
        let v = match regex {
                Some(r) => read_all(&init_dir, 0, Some(min_bytes), None, Some(&check_regex(r)), silent, false),
                _ => read_all(&init_dir, 0, Some(min_bytes), None, None, silent, false),
        };

        // filter by depth
        let mut v_filtered = v.filtered(depth);

        // display results
        v_filtered.display_tree(init_dir);
    }

    else if let Some(command) = matches.subcommand_matches("artifacts") {

        // set threshhold
        let min_bytes = match command.value_of("threshhold") {
            Some(t) => match t {
                        "M" => Some(1048576),
                        "G" => Some(1073741824),
                        _ => { println!("{}: invalid threshhold; defaulting to M", "Warning".yellow()) ; Some(1048576) }
                        },
            _ => None,
        };

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // set number of things to fetch for sort
        let num_int = get_num(command.value_of("count"));

        // decide whether to sort
        let should_sort = command.is_present("sort");

        // don't print warnings
        let silent = command.is_present("silent");

        // set regex for artifacts
        let artifacts = command.value_of("regex");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // get relevant filenames &c.
        let v = if let Some(r) = artifacts {
                let re = check_regex(r);
                match command.value_of("excludes") {
                    Some(ex) => read_all(&init_dir, 0, min_bytes, Some(&re), Some(&check_regex(ex)), silent, true),
                    _ => read_all(&init_dir, 0, min_bytes, Some(&re), None, silent, true),
                }
            }
            else {
                match command.value_of("excludes") {
                    Some(ex) => read_all(&init_dir, 0, min_bytes, None, Some(&check_regex(ex)), silent, true),
                    _ => read_all(&init_dir, 0, min_bytes, None, None, silent, true),
                }
            };

        let mut v_processed = if should_sort {
                v.sort(Some(num_int), depth)
            }
            else {
                v.filtered(depth)
            };

        v_processed.display_tree(init_dir);
    }

    // sort entities by size
    else if let Some(command) = matches.subcommand_matches("sort") {

        // set threshhold
        let min_bytes = threshhold(command.value_of("threshhold"));

        // set number of things to fetch for sort
        let num_int = get_num(command.value_of("count"));

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // decide whether to warnings
        let silent = command.is_present("silent");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // set regex for exclusions
        let regex =
            if let Some(n) = command.value_of("excludes") {
                Some(n)
            }
            else {
                None
            };

        // get relevant filenames &c.
        let v = match regex {
            Some(r) => read_all(&init_dir, 0, min_bytes, None, Some(&check_regex(r)), silent, false),
            _ => read_all(&init_dir, 0, min_bytes, None, None, silent, false),
        };

        // sort them
        let mut v_sorted = v.sort(Some(num_int), depth);

        // display sorted filenames
        v_sorted.display_tree(init_dir);
    }

    else {
        println!("{}: Command not recognized. Try 'sniff --help' if you're stuck.", "Error".red());
    }
}
<commit_msg>all subcommands<commit_after>#[macro_use] extern crate clap;

extern crate libsniff;
extern crate colored;
extern crate regex;

use libsniff::prelude::*;
use clap::App;
use colored::*;

#[allow(unknown_lints)]
#[allow(cyclomatic_complexity)]
fn main() {

    // command-line parser
    #[cfg(feature = "english")]
    let yaml = load_yaml!("options-en.yml");
    #[cfg(feature = "français")]
    let yaml = load_yaml!("options-fr.yml");
    #[cfg(feature = "deutsch")]
    let yaml = load_yaml!("options-de.yml");
    let matches = App::from_yaml(yaml).version(crate_version!()).get_matches();

    // find large files
    if let Some(command) = matches.subcommand_matches("fat") {

        // set threshhold
        let min_bytes = match threshhold(command.value_of("threshhold")) {
            Some(t) => t,
            _ => 1048576,
        };

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // don't print warnings
        let silent = command.is_present("silent");

        // set regex for exclusions
        let regex = command.value_of("excludes");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // get relevant filenames &c.
        let v = match regex {
                Some(r) => read_all(&init_dir, 0, Some(min_bytes), None, Some(&check_regex(r)), silent, false),
                _ => read_all(&init_dir, 0, Some(min_bytes), None, None, silent, false),
        };

        // filter by depth
        let mut v_filtered = v.filtered(depth);

        // display results
        v_filtered.display_tree(init_dir);
    }

    else if let Some(command) = matches.subcommand_matches("artifacts") {

        // set threshhold
        let min_bytes = threshhold(command.value_of("threshhold"));

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // set number of things to fetch for sort
        let num_int = get_num(command.value_of("count"));

        // decide whether to sort
        let should_sort = command.is_present("sort");

        // don't print warnings
        let silent = command.is_present("silent");

        // set regex for artifacts
        let artifacts = command.value_of("regex");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // get relevant filenames &c.
        let v = if let Some(r) = artifacts {
                let re = check_regex(r);
                match command.value_of("excludes") {
                    Some(ex) => read_all(&init_dir, 0, min_bytes, Some(&re), Some(&check_regex(ex)), silent, true),
                    _ => read_all(&init_dir, 0, min_bytes, Some(&re), None, silent, true),
                }
            }
            else {
                match command.value_of("excludes") {
                    Some(ex) => read_all(&init_dir, 0, min_bytes, None, Some(&check_regex(ex)), silent, true),
                    _ => read_all(&init_dir, 0, min_bytes, None, None, silent, true),
                }
            };

        let mut v_processed = if should_sort {
                v.sort(Some(num_int), depth)
            }
            else {
                v.filtered(depth)
            };

        v_processed.display_tree(init_dir);
    }

    // sort entities by size
    else if let Some(command) = matches.subcommand_matches("sort") {

        // set threshhold
        let min_bytes = threshhold(command.value_of("threshhold"));

        // set number of things to fetch for sort
        let num_int = get_num(command.value_of("count"));

        // set depth
        let depth = get_depth(command.value_of("depth"));

        // decide whether to warnings
        let silent = command.is_present("silent");

        // set path to dir
        let init_dir = get_dir(command.value_of("dir"));

        // set regex for exclusions
        let regex =
            if let Some(n) = command.value_of("excludes") {
                Some(n)
            }
            else {
                None
            };

        // get relevant filenames &c.
        let v = match regex {
            Some(r) => read_all(&init_dir, 0, min_bytes, None, Some(&check_regex(r)), silent, false),
            _ => read_all(&init_dir, 0, min_bytes, None, None, silent, false),
        };

        // sort them
        let mut v_sorted = v.sort(Some(num_int), depth);

        // display sorted filenames
        v_sorted.display_tree(init_dir);
    }

    else {
        println!("{}: Command not recognized. Try 'sniff --help' if you're stuck.", "Error".red());
    }
}
<|endoftext|>


<commit_before>extern crate rand;
extern crate walkdir;

use walkdir::WalkDir;
use rand::{Rng, thread_rng};
use std::env;
use std::fs::File;
use std::fs;
use std::io::prelude::*;
use std::io::Error;
use std::cmp;

fn main() {
    let mut recursive: bool = false;
    let mut file: String = String::from("");
    let mut i: i32 = -1;

    for argument in env::args() {
        i += 1;
        if i == 0 {
            continue;
        }

        if argument.to_lowercase() == "--help" || argument.to_lowercase() == "-help" {
            help();
            return;
        } else if argument.to_lowercase() == "--v" || argument.to_lowercase() == "-v" ||
           argument.to_lowercase() == "-version" ||
           argument.to_lowercase() == "--version" {
            version();
            return;
        } else if argument.to_lowercase() == "-r" {
            recursive = true;
        } else {
            file = argument;
            break;
        }
    }
    if file == "" {
        help();
        return;
    }
    file = get_path(&file);

    if recursive {
        for entry in WalkDir::new(file) {
            let entry = entry.unwrap();
            trumpify(entry.path().display().to_string());
        }
    } else {
        trumpify(file)
    }
}

fn get_path2(file: &str) -> Result<String, Error> {
    let path = try!(fs::canonicalize(file));
    Ok(path.display().to_string())
}

fn get_path(file: &str) -> String {
    get_path2(file).unwrap_or(file.to_owned())
}

fn read_file(file: &str) -> Result<String, Error> {
    let mut file = try!(File::open(file));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    Ok(contents)
}

fn write_file(file: &str, content: String) -> Result<(), Error> {
    let mut f = try!(File::create(file));
    try!(f.write_all(content.as_bytes()));

    try!(f.sync_data());
    Ok(())
}

fn version() {
    println!("\x1b[32mVersion:");
    println!("\x1b[0m    trump-0.1.0");
    println!("\x1b[32mWebsite:");
    println!("\x1b[0m    https://github.com/x13machine/trump/");
    println!("\x1b[32mCreator:");
    println!("\x1b[0m    <Googolplexking/x13machine> https://googolplex.ninja/");
}
fn help() {
    println!("\x1b[32mHelp:");
    println!("\x1b[0m    trump --help");
    println!("\x1b[32mVersion:");
    println!("\x1b[0m    trump -v");
    println!("\x1b[32mFiles:");
    println!("\x1b[0m    trump <File>");
    println!("\x1b[32mDirectories:");
    println!("\x1b[0m    trump -r <Directory>");
}

fn trumpify(file: String) {
    let dirs: Vec<&str> = file.split('/').collect::<Vec<&str>>();
    let mut name: String = String::from(dirs[dirs.len() - 1]);
    name = format!("{}{}",
                   &name[0..1].to_uppercase(),
                   &name[1..name.len()].to_lowercase());
    // Ansi Color Codes!!!
    println!("\x1b[32mMaking Great Again:\x1b[0m {}", file);

    let text = format!("Make {} Great Again!", name);

    let mut content = match read_file(&file) {
        Ok(content) => content,
        Err(_) => return,
    };

    let loops = cmp::max(content.len() / text.len() / 2, 1);
    let mut rng = thread_rng();
    for _ in 0..loops {
        let place: usize = rng.gen_range(0, content.len() as u32) as usize;
        let part1: String = String::from(&content[0..place]);
        let part2: String =
            String::from(&content[cmp::min(place + text.len(), content.len())..content.len()]);
        content = format!("{}{}{}", part1, text, part2);
    }
    let _ = write_file(&file, content);
}
<commit_msg>Remove that pesky `i`<commit_after>extern crate rand;
extern crate walkdir;

use walkdir::WalkDir;
use rand::{Rng, thread_rng};
use std::env;
use std::fs::File;
use std::fs;
use std::io::prelude::*;
use std::io::Error;
use std::cmp;

fn main() {
    let mut recursive: bool = false;
    let mut file: String = String::from("");

    for argument in env::args().skip(1) {
        if argument.to_lowercase() == "--help" || argument.to_lowercase() == "-help" {
            help();
            return;
        } else if argument.to_lowercase() == "--v" || argument.to_lowercase() == "-v" ||
           argument.to_lowercase() == "-version" ||
           argument.to_lowercase() == "--version" {
            version();
            return;
        } else if argument.to_lowercase() == "-r" {
            recursive = true;
        } else {
            file = argument;
            break;
        }
    }
    if file == "" {
        help();
        return;
    }
    file = get_path(&file);

    if recursive {
        for entry in WalkDir::new(file) {
            let entry = entry.unwrap();
            trumpify(entry.path().display().to_string());
        }
    } else {
        trumpify(file)
    }
}

fn get_path2(file: &str) -> Result<String, Error> {
    let path = try!(fs::canonicalize(file));
    Ok(path.display().to_string())
}

fn get_path(file: &str) -> String {
    get_path2(file).unwrap_or(file.to_owned())
}

fn read_file(file: &str) -> Result<String, Error> {
    let mut file = try!(File::open(file));
    let mut contents = String::new();
    try!(file.read_to_string(&mut contents));
    Ok(contents)
}

fn write_file(file: &str, content: String) -> Result<(), Error> {
    let mut f = try!(File::create(file));
    try!(f.write_all(content.as_bytes()));

    try!(f.sync_data());
    Ok(())
}

fn version() {
    println!("\x1b[32mVersion:");
    println!("\x1b[0m    trump-0.1.0");
    println!("\x1b[32mWebsite:");
    println!("\x1b[0m    https://github.com/x13machine/trump/");
    println!("\x1b[32mCreator:");
    println!("\x1b[0m    <Googolplexking/x13machine> https://googolplex.ninja/");
}
fn help() {
    println!("\x1b[32mHelp:");
    println!("\x1b[0m    trump --help");
    println!("\x1b[32mVersion:");
    println!("\x1b[0m    trump -v");
    println!("\x1b[32mFiles:");
    println!("\x1b[0m    trump <File>");
    println!("\x1b[32mDirectories:");
    println!("\x1b[0m    trump -r <Directory>");
}

fn trumpify(file: String) {
    let dirs: Vec<&str> = file.split('/').collect::<Vec<&str>>();
    let mut name: String = String::from(dirs[dirs.len() - 1]);
    name = format!("{}{}",
                   &name[0..1].to_uppercase(),
                   &name[1..name.len()].to_lowercase());
    // Ansi Color Codes!!!
    println!("\x1b[32mMaking Great Again:\x1b[0m {}", file);

    let text = format!("Make {} Great Again!", name);

    let mut content = match read_file(&file) {
        Ok(content) => content,
        Err(_) => return,
    };

    let loops = cmp::max(content.len() / text.len() / 2, 1);
    let mut rng = thread_rng();
    for _ in 0..loops {
        let place: usize = rng.gen_range(0, content.len() as u32) as usize;
        let part1: String = String::from(&content[0..place]);
        let part2: String =
            String::from(&content[cmp::min(place + text.len(), content.len())..content.len()]);
        content = format!("{}{}{}", part1, text, part2);
    }
    let _ = write_file(&file, content);
}
<|endoftext|>


<commit_before>extern crate rustc_serialize;
use rustc_serialize::json;

#[derive(RustcDecodable, RustcEncodable)]
struct Command {
	cid: u64,
	uid: u32,
	target: String,
}

fn get_string(cmd: &Command) -> String {
	return json::encode(&cmd).unwrap();
}

fn main(){
	let bs = Command{
		cid: 0,
		uid: 1,
		target: "something".to_string()
	};
	println!("So guess what?: {}", get_string(&bs));
}

#[test]
fn some_test() {
	assert_eq!(
		get_string(
			&Command{cid: 0, uid: 1, target: "cat".to_string()}
		), "{\"cid\":0,\"uid\":1,\"target\":\"cat\"}");
}

#[test]
fn some_fail() {
	assert!(false);
}<commit_msg>Fix the test<commit_after>extern crate rustc_serialize;
use rustc_serialize::json;

#[derive(RustcDecodable, RustcEncodable)]
struct Command {
	cid: u64,
	uid: u32,
	target: String,
}

fn get_string(cmd: &Command) -> String {
	return json::encode(&cmd).unwrap();
}

fn main(){
	let bs = Command{
		cid: 0,
		uid: 1,
		target: "something".to_string()
	};
	println!("So guess what?: {}", get_string(&bs));
}

#[test]
fn some_test() {
	assert_eq!(
		get_string(
			&Command{cid: 0, uid: 1, target: "cat".to_string()}
		), "{\"cid\":0,\"uid\":1,\"target\":\"cat\"}");
}

#[test]
#[should_panic]
fn some_fail() {
	assert!(false);
}
<|endoftext|>


<commit_before>extern crate bencode;
extern crate rand;
extern crate bittorrent;
extern crate hyper;

use std::env;
use std::io::Read;
use bencode::{deserialize, deserialize_file, Bencode, TypedMethods};
use rand::{Rng, thread_rng};
use bittorrent::querystring::QueryString;
use bittorrent::metadata::{MetadataDict, Metadata};
use hyper::Client;
use hyper::header::Connection;

const PEER_ID_LENGTH:usize = 20;
const PEER_ID_PREFIX:&'static str = "ABT:";

fn gen_rand_peer_id (prefix: &str) -> String {
    let rand_length = PEER_ID_LENGTH - prefix.len();
    let rand = thread_rng().gen_ascii_chars()
                           .take(rand_length)
                           .collect::<String>();

    prefix.to_string() + &rand
}

fn init (metadata: Metadata, listen_port: u32, bytes_dled: u32) {
    let peer_id = gen_rand_peer_id(PEER_ID_PREFIX);
    let bytes_left = metadata.get_total_length() - bytes_dled;
    let req_addr = metadata.announce + "?" + &QueryString::from(vec![
                                                                ("info_hash", metadata.info_hash),
                                                                ("peer_id", peer_id),
                                                                ("port", listen_port.to_string()),
                                                                ("uploaded", 0.to_string()),
                                                                ("downloaded", bytes_dled.to_string()),
                                                                ("left", bytes_left.to_string()),
                                                                ("compact", 1.to_string()),
                                                                ("event", "started".to_string())
                                                                ]).query_string();

    //println!("{}", req_addr);

    let client = Client::new();
    let mut res = client.get(&req_addr)
                        .header(Connection::close())
                        .send().unwrap();

    let mut body = Vec::new();
    res.read_to_end(&mut body).unwrap();

    println!("dict: {:?}", body.iter().map(|a| *a as char).collect::<Vec<char>>());

    println!("{}", body.iter().map(|a| *a as char).collect::<String>());
    let tracker_response = deserialize(body).unwrap();
    let tracker_resp = match tracker_response.first() {
        Some(&Bencode::Dict(ref a)) => a,
        _ => panic!("unable to parse dictionary from tracker response!")
    };
    let peers = tracker_resp.get_owned_string("peers").unwrap();
    let peers6 = tracker_resp.get_owned_string("peers6").unwrap();

    let peers_bytes = peers.chars().map(|x| x as u8).collect::<Vec<u8>>();

    let addresses = (0..peers_bytes.len()/6).map(|x| {
        let ip_start = x * 6;
        let ip_end = ip_start + 4;
        (&peers_bytes[ip_start..ip_end], peers_bytes[ip_end] as u32 + peers_bytes[ip_end+1] as u32)
    }).collect::<Vec<(&[u8], u32)>>();

    println!("y: {:?}", addresses);
    println!("peers: {:?}, len: {}", peers_bytes, peers_bytes.len());

    /*for item in peers.chars() {
        println!("{} - {}", item);
    }*/

    let peers6_bytes = peers6.chars().map(|x| x as u8).collect::<Vec<u8>>();
    println!("peers6: {:?}, len: {}", peers6_bytes, peers6_bytes.len());

    println!("Response: {:?}", tracker_response);
}

fn main () {
    let path = env::args().nth(1)
                          .unwrap_or_else(||panic!("no path to torrent provided"));

    let content = deserialize_file(path).unwrap_or_else(||panic!("unable to parse bencoded metadata"));

    let metadata = match content.first() {
        Some(&Bencode::Dict(ref dict)) => dict.to_metadata(),
        _ => panic!("no valid information in torrent file")
    }.unwrap();

    //println!("{:?}", metadata);
    init(metadata, 6888, 0);
}
<commit_msg>returning a human readable ip address<commit_after>extern crate bencode;
extern crate rand;
extern crate bittorrent;
extern crate hyper;

use std::env;
use std::io::Read;
use bencode::{deserialize, deserialize_file, Bencode, TypedMethods};
use rand::{Rng, thread_rng};
use bittorrent::querystring::QueryString;
use bittorrent::metadata::{MetadataDict, Metadata};
use hyper::Client;
use hyper::header::Connection;

const PEER_ID_LENGTH:usize = 20;
const PEER_ID_PREFIX:&'static str = "ABT:";

fn gen_rand_peer_id (prefix: &str) -> String {
    let rand_length = PEER_ID_LENGTH - prefix.len();
    let rand = thread_rng().gen_ascii_chars()
                           .take(rand_length)
                           .collect::<String>();

    prefix.to_string() + &rand
}

fn init (metadata: Metadata, listen_port: u32, bytes_dled: u32) {
    let peer_id = gen_rand_peer_id(PEER_ID_PREFIX);
    let bytes_left = metadata.get_total_length() - bytes_dled;
    let req_addr = metadata.announce + "?" + &QueryString::from(vec![
                                                                ("info_hash", metadata.info_hash),
                                                                ("peer_id", peer_id),
                                                                ("port", listen_port.to_string()),
                                                                ("uploaded", 0.to_string()),
                                                                ("downloaded", bytes_dled.to_string()),
                                                                ("left", bytes_left.to_string()),
                                                                ("compact", 1.to_string()),
                                                                ("event", "started".to_string())
                                                                ]).query_string();

    //println!("{}", req_addr);

    let client = Client::new();
    let mut res = client.get(&req_addr)
                        .header(Connection::close())
                        .send().unwrap();

    let mut body = Vec::new();
    res.read_to_end(&mut body).unwrap();

    println!("dict: {:?}", body.iter().map(|a| *a as char).collect::<Vec<char>>());

    println!("{}", body.iter().map(|a| *a as char).collect::<String>());
    let tracker_response = deserialize(body).unwrap();
    let tracker_resp = match tracker_response.first() {
        Some(&Bencode::Dict(ref a)) => a,
        _ => panic!("unable to parse dictionary from tracker response!")
    };
    let peers = tracker_resp.get_owned_string("peers").unwrap();
    let peers6 = tracker_resp.get_owned_string("peers6").unwrap();

    let peers_bytes = peers.chars().map(|x| x as u8).collect::<Vec<u8>>();

    let addresses = (0..peers_bytes.len()/6).map(|x| {
        let ip_start = x * 6;
        let ip_end = ip_start + 4;
        //returns a 2-ple of addresses (as string) and port (as u32)
        ((&peers_bytes[ip_start..ip_end]).iter()
                                            .map(|y| y.to_string())
                                            .collect::<Vec<String>>()
                                            .join("."),
            peers_bytes[ip_end] as u32 + peers_bytes[ip_end+1] as u32)
    }).collect::<Vec<(String, u32)>>();

    println!("{:?}", addresses);
    println!("Response: {:?}", tracker_response);
}

fn main () {
    let path = env::args().nth(1)
                          .unwrap_or_else(||panic!("no path to torrent provided"));

    let content = deserialize_file(path).unwrap_or_else(||panic!("unable to parse bencoded metadata"));

    let metadata = match content.first() {
        Some(&Bencode::Dict(ref dict)) => dict.to_metadata(),
        _ => panic!("no valid information in torrent file")
    }.unwrap();

    //println!("{:?}", metadata);
    init(metadata, 6888, 0);
}
<|endoftext|>


<commit_before>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

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

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// 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: randomize placeholder per-request

// 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.

// TODO: return a Result
//fn call_wikimedia_api(action: &str, prop: &str, Vec<(&str, &str)> parameters) -> String {
//    let client = Client::new();
//    let mut res = client.get(
//        &format!(
//            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop={}&rvlimit={}{}&format=json",
//            page, properties.join("|"), limit,
//            match start_id { Some(id) => format!("&rvstartid={}", id), None => "".to_string()}))
//        .header(Connection::close())
//        .send().unwrap();
//
//    let mut body = String::new();
//    res.read_to_string(&mut body).unwrap();
//
//    body
//}

fn get_revisions(page: &str, limit: i32, properties: Vec<&str>, start_id: Option<u64>) -> String {
    let client = Client::new();
    let mut res = client.post(
        &format!(
            "https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles={}&rvprop={}&rvlimit={}{}&format=json",
            page, properties.join("|"), limit,
            match start_id { Some(id) => format!("&rvstartid={}", id), None => "".to_string()}))
        .header(Connection::close())
        .send().unwrap();

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

    body
}

fn parse_wikitext(wikitext: &str) -> String {
    let post_body = format!(
        "text={}", percent_encoding::percent_encode(
            wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET));

    let client = Client::new();
    let mut res = client.post(
        "https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text&disablepp=&contentmodel=wikitext")
        .body(&post_body)
        .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, vec!["comment", "ids"], None)).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism, and that have revid and parentid (which they all
    // should, but merits checking).
    Ok(revisions.into_iter().filter(|revision| {
        match (json::get_json_string(revision, &[Key("comment")]),
               json::get_json_number(revision, &[Key("revid")]),
               json::get_json_number(revision, &[Key("parentid")])) {
            (Ok(ref comment), Ok(_), Ok(_)) => comment.contains("vandal"),
            _ => false,
        }})
       .map(|revision| {
           (json::get_json_number(revision, &[Key("revid")]).unwrap(),
            json::get_json_number(revision, &[Key("parentid")]).unwrap())
       }).collect())
}

fn get_latest_revision_id(page: &str) -> Result<u64, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["ids"], None)).unwrap();
    json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")])
}

fn get_revision_content(page: &str, id: u64) -> Result<String, String> {
    let json = Json::from_str(&get_revisions(page, 1, vec!["content"], Some(id))).unwrap();
    match json::get_json_string(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]) {
        Ok(content) => Ok(content.to_string()),
        Err(msg) => Err(msg),
    }
}

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 render(wikitext: &str) -> Result<String, String> {
    let html = parse_wikitext(wikitext);
    // TODO: check return value
    let json = Json::from_str(&html).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn get_current_page(page: &str) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!("https://en.wikipedia.org/wiki/{}", page))
        .header(Connection::close())
        .send().unwrap();

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

    body
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor").unwrap();
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid).unwrap();
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid).unwrap();
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid).unwrap();
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    let rendered_body = render(&accumulated_contents).unwrap();
    let current_page_contents = get_current_page("Zachary_Taylor");
    let page_contents_with_placeholder =
        replace_node_with_placeholder(
            &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT").unwrap();
    let final_page_contents = page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &rendered_body);

    println!("Restored vandalisms reverted in: {:?}", revisions);
    println!("{}", final_page_contents);
}

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

    #[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());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Unify get_revisions() and parse_wikitext() into call_wikimedia_api().<commit_after>extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

mod json;

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

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use rustc_serialize::json::Json;
use tempfile::NamedTempFile;
use url::percent_encoding;

use json::JsonPathElement::{Key, Only};

// 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: randomize placeholder per-request

// 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.

// TODO: return a Result
fn call_wikimedia_api(parameters: Vec<(&str, &str)>) -> String {
    let post_body =
        parameters.into_iter().map(|p| format!("{}={}", p.0, p.1))
        .collect::<Vec<_>>().join("&") + "&format=json";

    let client = Client::new();
    let mut res = client.post("https://en.wikipedia.org/w/api.php")
        .body(&post_body)
        .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_str = call_wikimedia_api(
        vec![("action", "query"), ("prop", "revisions"), ("titles", page),
             ("rvprop", "comment|ids"), ("rvlimit", "60")]);

    let json = Json::from_str(&json_str).unwrap();
    let revisions = try!(
        json::get_json_array(&json, &[Key("query"), Key("pages"), Only, Key("revisions")]));

    // Filter for revisions that mention vandalism, and that have revid and parentid (which they all
    // should, but merits checking).
    Ok(revisions.into_iter().filter(|revision| {
        match (json::get_json_string(revision, &[Key("comment")]),
               json::get_json_number(revision, &[Key("revid")]),
               json::get_json_number(revision, &[Key("parentid")])) {
            (Ok(ref comment), Ok(_), Ok(_)) => comment.contains("vandal"),
            _ => false,
        }})
       .map(|revision| {
           (json::get_json_number(revision, &[Key("revid")]).unwrap(),
            json::get_json_number(revision, &[Key("parentid")]).unwrap())
       }).collect())
}

fn get_latest_revision_id(page: &str) -> Result<u64, String> {
    let json_str = call_wikimedia_api(
        vec![("action", "query"), ("prop", "revisions"), ("titles", page), ("rvprop", "ids"),
             ("rvlimit", "1")]);
    let json = Json::from_str(&json_str).unwrap();
    json::get_json_number(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("revid")])
}

fn get_revision_content(page: &str, id: u64) -> Result<String, String> {
    let json_str = call_wikimedia_api(
        vec![("action", "query"), ("prop", "revisions"), ("titles", page), ("rvprop", "content"),
             ("rvlimit", "1"), ("rvstartid", &id.to_string())]);
    let json = Json::from_str(&json_str).unwrap();
    match json::get_json_string(
        &json, &[Key("query"), Key("pages"), Only, Key("revisions"), Only, Key("*")]) {
        Ok(content) => Ok(content.to_string()),
        Err(msg) => Err(msg),
    }
}

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 render(wikitext: &str) -> Result<String, String> {
    let encoded_wikitext =
        percent_encoding::percent_encode(wikitext.as_bytes(), percent_encoding::QUERY_ENCODE_SET);
    let html = call_wikimedia_api(
        vec![("action", "parse"), ("prop", "text"), ("disablepp", ""), ("contentmodel", "wikitext"),
             ("text", &encoded_wikitext)]);
    // TODO: check return value
    let json = Json::from_str(&html).unwrap();
    match json::get_json_string(&json, &[Key("parse"), Key("text"), Key("*")]) {
        Ok(contents) => Ok(contents.to_string()),
        Err(message) => Err(message),
    }
}

fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
    // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
    for attribute in attributes {
        // TODO: do I seriously have to construct a StrTendril here?
        // There has to be a better way.
        if attribute.name.local.as_slice() == "id" &&
            attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                return true;
            }
    }
    return false;
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str) -> Result<String, String> {
    // TODO: check errors
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    // TODO: error handling
    Ok(String::from_utf8(serialized).unwrap())
}

fn get_current_page(page: &str) -> String {
    let client = Client::new();
    let mut res = client.get(
        &format!("https://en.wikipedia.org/wiki/{}", page))
        .header(Connection::close())
        .send().unwrap();

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

    body
}

fn main() {
    let latest_revid = get_latest_revision_id("Zachary_Taylor").unwrap();
    let mut accumulated_contents = get_revision_content("Zachary_Taylor", latest_revid).unwrap();
    let mut revisions = vec![];
    for (revert_revid, vandalism_revid) in get_revert_revision_ids("Zachary_Taylor").ok().unwrap() {
        let reverted_contents = get_revision_content("Zachary_Taylor", revert_revid).unwrap();
        let vandalized_contents = get_revision_content("Zachary_Taylor", vandalism_revid).unwrap();
        match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
            Some(merged_contents) => {
                accumulated_contents = merged_contents;
                revisions.push(revert_revid);
            }
            None => (),
        }
    }

    let rendered_body = render(&accumulated_contents).unwrap();
    let current_page_contents = get_current_page("Zachary_Taylor");
    let page_contents_with_placeholder =
        replace_node_with_placeholder(
            &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT").unwrap();
    let final_page_contents = page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &rendered_body);

    println!("Restored vandalisms reverted in: {:?}", revisions);
    println!("{}", final_page_contents);
}

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

    #[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());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>


<commit_before>extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;

extern crate catchit;

use catchit::{Engine, Object, CollisionShape, ObstacleKind, State, Extent};
use catchit::Scalar as CatchitScalar;

use piston::window::WindowSettings;
use piston::event::{ RenderArgs, UpdateArgs, Events, RenderEvent, UpdateEvent, MouseCursorEvent,
                     EventLoop, PressEvent, ReleaseEvent };
use piston::input::{Button, Key, MouseButton};
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{ GlGraphics, OpenGL };
use opengl_graphics::glyph_cache::GlyphCache;
use graphics::character::CharacterCache;
use graphics::math::Scalar;

pub struct App {
    gl: GlGraphics,
    engine: Engine,
    last_state: Option<State>,
    field_border_y: Scalar,
    max_score: u32,
    text_height: f64,
    tries: u32,
    font_fira_bold: GlyphCache<'static>
}

const WIDTH: u16 = 800;
const HEIGHT: u16 = 600;
const UPDATES_PER_SECOND: u64 = 60;
const FONT_SIZE: u32 = 20;
const HUD_SPACE: Scalar = 1.0 / 8.0;
const NEW_GAME_TEXT: &'static str = "Press SPACE for new game";

impl App {
    fn render(&mut self, args: &RenderArgs) {
        use graphics::{ rectangle, ellipse, clear , Transformed, Text, Line };
        use graphics::math::Matrix2d;
        use graphics::types::Color;

        const BG:    Color = [1.0, 204.0 / 255.0, 0.0, 1.0];
        const BLACK: Color = [0.2, 0.2, 0.2, 1.0];
        const WHITE: Color = [0.8, 0.8, 0.8, 1.0];
        const BLUE:  Color = [0.0, 0.0, 0.8, 1.0];
        const RED:   Color = [204.0 / 255.0, 0.0, 0.0, 1.0];

        let square = rectangle::square(0.0, 0.0, 1.0);
        let s = self.engine.state();
        let game_over = self.game_over();
        let font_fira_bold = &mut self.font_fira_bold;
        let text_height = self.text_height;
        let max_score = self.max_score;
        let tries = self.tries;
        let field_border_y = self.field_border_y;
        let last_state = &self.last_state;

        self.gl.draw(args.viewport(), |c, gl| {
            let draw_object = |obj: &Object, gl: &mut GlGraphics, color: Color| {
                let transform = c.transform.trans(obj.pos[0] - obj.half_size,
                                                  obj.pos[1] - obj.half_size)
                                           .scale(obj.half_size * 2.0, obj.half_size * 2.0);
                match obj.shape {
                    CollisionShape::Square => rectangle(color, square, transform, gl),
                    CollisionShape::Circle => ellipse(color, square, transform, gl),
                }
            };

            fn blend_color(c1: Color, c2: Color, blend: CatchitScalar) -> Color {
                let mut c = c1;
                for i in (0..3) {
                    c[i] = blend as f32 * c1[i] + (1.0 - blend as f32) * c2[i];
                }
                c
            }

            clear(BG, gl);

            let text = Text::colored(BLACK, FONT_SIZE);
            let text_matrix = |x: Scalar| -> Matrix2d {
                c.transform.trans(x, HEIGHT as Scalar - text_height / 2.0)
            };

            if let Some(ref s) = s.as_ref().or(last_state.as_ref()) {
                let deadly_color = blend_color(BLACK, BG, s.obstacle_opacity.current);
                let hunter_color = blend_color(RED, BLUE, 1.0 - s.attracting_force.current
                                                                / s.attracting_force.v2);

                for obstacle in &s.obstacles {
                    let color = match obstacle.kind {
                        ObstacleKind::Deadly => deadly_color,
                        ObstacleKind::AttractiveForceSwitch => BLUE,
                        ObstacleKind::InvisibiltySwitch => WHITE,
                    };
                    draw_object(&obstacle.object, gl, color);
                }

                draw_object(&s.prey, gl, RED);
                draw_object(&s.hunter.object, gl, hunter_color);

                text.draw(&format!("Score: {}", s.score), font_fira_bold,
                                                          &c.draw_state,
                                                          text_matrix(WIDTH as Scalar
                                                                      * HUD_SPACE * 4.5),
                                                          gl);

                text.draw(&format!("Multiplier: {:.2}", s.score_coeff), font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 6.0),
                                                         gl);
            }

            if game_over {
                let w = text_width(font_fira_bold, NEW_GAME_TEXT) / 2.0;
                text.draw(NEW_GAME_TEXT, font_fira_bold, &c.draw_state,
                                          c.transform.trans(WIDTH as Scalar / 2.0 - w,
                                                           HEIGHT as Scalar / 2.0),
                                          gl);
                text.draw("Use LMB for repelling force", font_fira_bold, &c.draw_state,
                                          c.transform.trans(WIDTH as Scalar / 2.0 - w,
                                                           HEIGHT as Scalar / 2.0
                                                           + text_height
                                                           + text_height * 0.4),
                                          gl);
            }

            // Draw HUD
            ////////////
            let line = Line::new(BLACK, 1.0);

            line.draw([0.0, 0.0, WIDTH as Scalar, 0.0], &c.draw_state,
                                                         c.transform.trans(0.0, field_border_y),
                                                         gl);

            text.draw(&format!("Best Score: {}", max_score), font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 1.0),
                                                         gl);

            text.draw(&format!("Tries: {}", tries),      font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 3.0),
                                                         gl);

        });
    }

    fn game_over(&self) -> bool {
        self.last_state.is_some()
    }

    fn update(&mut self, args: &UpdateArgs) {
        if let Err(state) = self.engine.update(args.dt) {
            if state.score > self.max_score {
                self.max_score += state.score;
            }
            self.tries += 1;
            self.last_state = Some(state);
        }
    }
}

fn compute_field(width: u16, height: u16, text_height: Scalar) -> Extent {
    [width as CatchitScalar, height as CatchitScalar
                                    - (text_height * 2.0)]
}

fn text_width(cache: &mut GlyphCache<'static>, text: &str) -> Scalar {
    let mut w = 0.0;
    for c in text.chars() {
        w += cache.character(FONT_SIZE, c).width();
    }
    w
}

fn main() {
    // Create an Glutin window.
    let window = Window::new(
        WindowSettings::new(
            "catchit",
            [WIDTH as u32, HEIGHT as u32]
        )
        .exit_on_esc(true)
        .vsync(true)
        .samples(16)
    );

    let mut app = {
        let gl = GlGraphics::new(OpenGL::_3_2);
        let mut glyphs = GlyphCache::from_bytes(include_bytes!("../res/FiraMono-Bold.ttf"))
                                                              .unwrap();
        let text_height = glyphs.character(FONT_SIZE, 'S').top();
        let field = compute_field(WIDTH, HEIGHT, text_height);

        // Create a new game and run it.
        App {
            gl: gl,
            engine: Engine::from_field(field),
            last_state: None,
            field_border_y: field[1],
            text_height: text_height,
            tries: 0,
            max_score: 0,
            font_fira_bold: glyphs,
        }
    };

    for e in window.events()
                   .max_fps(UPDATES_PER_SECOND)
                   .ups(UPDATES_PER_SECOND) {
        if let Some(pos) = e.mouse_cursor_args() {
            app.engine.set_hunter_pos(pos);
        }

        match e.press_args() {
            Some(Button::Keyboard(Key::Space)) if app.game_over() => {
                app.last_state = None;
                app.engine.reset(compute_field(WIDTH, HEIGHT, app.text_height))
            },
            Some(Button::Mouse(MouseButton::Left)) => {
                app.engine.set_hunter_force(true);
            }
            _ => {},
        }

        if let Some(Button::Mouse(MouseButton::Left)) = e.release_args() {
            app.engine.set_hunter_force(false);
        }

        if let Some(r) = e.render_args() {
            app.render(&r);
        }

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}
<commit_msg>Remove multisampling<commit_after>extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;

extern crate catchit;

use catchit::{Engine, Object, CollisionShape, ObstacleKind, State, Extent};
use catchit::Scalar as CatchitScalar;

use piston::window::WindowSettings;
use piston::event::{ RenderArgs, UpdateArgs, Events, RenderEvent, UpdateEvent, MouseCursorEvent,
                     EventLoop, PressEvent, ReleaseEvent };
use piston::input::{Button, Key, MouseButton};
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{ GlGraphics, OpenGL };
use opengl_graphics::glyph_cache::GlyphCache;
use graphics::character::CharacterCache;
use graphics::math::Scalar;

pub struct App {
    gl: GlGraphics,
    engine: Engine,
    last_state: Option<State>,
    field_border_y: Scalar,
    max_score: u32,
    text_height: f64,
    tries: u32,
    font_fira_bold: GlyphCache<'static>
}

const WIDTH: u16 = 800;
const HEIGHT: u16 = 600;
const UPDATES_PER_SECOND: u64 = 60;
const FONT_SIZE: u32 = 20;
const HUD_SPACE: Scalar = 1.0 / 8.0;
const NEW_GAME_TEXT: &'static str = "Press SPACE for new game";

impl App {
    fn render(&mut self, args: &RenderArgs) {
        use graphics::{ rectangle, ellipse, clear , Transformed, Text, Line };
        use graphics::math::Matrix2d;
        use graphics::types::Color;

        const BG:    Color = [1.0, 204.0 / 255.0, 0.0, 1.0];
        const BLACK: Color = [0.2, 0.2, 0.2, 1.0];
        const WHITE: Color = [0.8, 0.8, 0.8, 1.0];
        const BLUE:  Color = [0.0, 0.0, 0.8, 1.0];
        const RED:   Color = [204.0 / 255.0, 0.0, 0.0, 1.0];

        let square = rectangle::square(0.0, 0.0, 1.0);
        let s = self.engine.state();
        let game_over = self.game_over();
        let font_fira_bold = &mut self.font_fira_bold;
        let text_height = self.text_height;
        let max_score = self.max_score;
        let tries = self.tries;
        let field_border_y = self.field_border_y;
        let last_state = &self.last_state;

        self.gl.draw(args.viewport(), |c, gl| {
            let draw_object = |obj: &Object, gl: &mut GlGraphics, color: Color| {
                let transform = c.transform.trans(obj.pos[0] - obj.half_size,
                                                  obj.pos[1] - obj.half_size)
                                           .scale(obj.half_size * 2.0, obj.half_size * 2.0);
                match obj.shape {
                    CollisionShape::Square => rectangle(color, square, transform, gl),
                    CollisionShape::Circle => ellipse(color, square, transform, gl),
                }
            };

            fn blend_color(c1: Color, c2: Color, blend: CatchitScalar) -> Color {
                let mut c = c1;
                for i in (0..3) {
                    c[i] = blend as f32 * c1[i] + (1.0 - blend as f32) * c2[i];
                }
                c
            }

            clear(BG, gl);

            let text = Text::colored(BLACK, FONT_SIZE);
            let text_matrix = |x: Scalar| -> Matrix2d {
                c.transform.trans(x, HEIGHT as Scalar - text_height / 2.0)
            };

            if let Some(ref s) = s.as_ref().or(last_state.as_ref()) {
                let deadly_color = blend_color(BLACK, BG, s.obstacle_opacity.current);
                let hunter_color = blend_color(RED, BLUE, 1.0 - s.attracting_force.current
                                                                / s.attracting_force.v2);

                for obstacle in &s.obstacles {
                    let color = match obstacle.kind {
                        ObstacleKind::Deadly => deadly_color,
                        ObstacleKind::AttractiveForceSwitch => BLUE,
                        ObstacleKind::InvisibiltySwitch => WHITE,
                    };
                    draw_object(&obstacle.object, gl, color);
                }

                draw_object(&s.prey, gl, RED);
                draw_object(&s.hunter.object, gl, hunter_color);

                text.draw(&format!("Score: {}", s.score), font_fira_bold,
                                                          &c.draw_state,
                                                          text_matrix(WIDTH as Scalar
                                                                      * HUD_SPACE * 4.5),
                                                          gl);

                text.draw(&format!("Multiplier: {:.2}", s.score_coeff), font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 6.0),
                                                         gl);
            }

            if game_over {
                let w = text_width(font_fira_bold, NEW_GAME_TEXT) / 2.0;
                text.draw(NEW_GAME_TEXT, font_fira_bold, &c.draw_state,
                                          c.transform.trans(WIDTH as Scalar / 2.0 - w,
                                                           HEIGHT as Scalar / 2.0),
                                          gl);
                text.draw("Use LMB for repelling force", font_fira_bold, &c.draw_state,
                                          c.transform.trans(WIDTH as Scalar / 2.0 - w,
                                                           HEIGHT as Scalar / 2.0
                                                           + text_height
                                                           + text_height * 0.4),
                                          gl);
            }

            // Draw HUD
            ////////////
            let line = Line::new(BLACK, 1.0);

            line.draw([0.0, 0.0, WIDTH as Scalar, 0.0], &c.draw_state,
                                                         c.transform.trans(0.0, field_border_y),
                                                         gl);

            text.draw(&format!("Best Score: {}", max_score), font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 1.0),
                                                         gl);

            text.draw(&format!("Tries: {}", tries),      font_fira_bold,
                                                         &c.draw_state,
                                                         text_matrix(WIDTH as Scalar
                                                                     * HUD_SPACE * 3.0),
                                                         gl);

        });
    }

    fn game_over(&self) -> bool {
        self.last_state.is_some()
    }

    fn update(&mut self, args: &UpdateArgs) {
        if let Err(state) = self.engine.update(args.dt) {
            if state.score > self.max_score {
                self.max_score += state.score;
            }
            self.tries += 1;
            self.last_state = Some(state);
        }
    }
}

fn compute_field(width: u16, height: u16, text_height: Scalar) -> Extent {
    [width as CatchitScalar, height as CatchitScalar
                                    - (text_height * 2.0)]
}

fn text_width(cache: &mut GlyphCache<'static>, text: &str) -> Scalar {
    let mut w = 0.0;
    for c in text.chars() {
        w += cache.character(FONT_SIZE, c).width();
    }
    w
}

fn main() {
    // Create an Glutin window.
    let window = Window::new(
        WindowSettings::new(
            "catchit",
            [WIDTH as u32, HEIGHT as u32]
        )
        .exit_on_esc(true)
        .vsync(true)
    );

    let mut app = {
        let gl = GlGraphics::new(OpenGL::_3_2);
        let mut glyphs = GlyphCache::from_bytes(include_bytes!("../res/FiraMono-Bold.ttf"))
                                                              .unwrap();
        let text_height = glyphs.character(FONT_SIZE, 'S').top();
        let field = compute_field(WIDTH, HEIGHT, text_height);

        // Create a new game and run it.
        App {
            gl: gl,
            engine: Engine::from_field(field),
            last_state: None,
            field_border_y: field[1],
            text_height: text_height,
            tries: 0,
            max_score: 0,
            font_fira_bold: glyphs,
        }
    };

    for e in window.events()
                   .max_fps(UPDATES_PER_SECOND)
                   .ups(UPDATES_PER_SECOND) {
        if let Some(pos) = e.mouse_cursor_args() {
            app.engine.set_hunter_pos(pos);
        }

        match e.press_args() {
            Some(Button::Keyboard(Key::Space)) if app.game_over() => {
                app.last_state = None;
                app.engine.reset(compute_field(WIDTH, HEIGHT, app.text_height))
            },
            Some(Button::Mouse(MouseButton::Left)) => {
                app.engine.set_hunter_force(true);
            }
            _ => {},
        }

        if let Some(Button::Mouse(MouseButton::Left)) = e.release_args() {
            app.engine.set_hunter_force(false);
        }

        if let Some(r) = e.render_args() {
            app.render(&r);
        }

        if let Some(u) = e.update_args() {
            app.update(&u);
        }
    }
}
<|endoftext|>