<commit_before>#![recursion_limit = "128"]

extern crate ape;
extern crate app_dirs;
extern crate core;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_codegen;
#[macro_use]
extern crate error_chain;
extern crate getopts;
extern crate hyper;
extern crate id3;
extern crate image;
extern crate iron;
extern crate lewton;
extern crate metaflac;
extern crate mount;
extern crate params;
extern crate rand;
extern crate reqwest;
extern crate regex;
extern crate ring;
extern crate router;
extern crate secure_session;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate staticfile;
extern crate toml;
extern crate typemap;
extern crate url;

#[cfg(windows)]
extern crate uuid;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate shell32;
#[cfg(windows)]
extern crate user32;

#[cfg(unix)]
extern crate unix_daemonize;

#[cfg(unix)]
use unix_daemonize::{daemonize_redirect, ChdirMode};

use core::ops::Deref;
use errors::*;
#[cfg(unix)]
use getopts::Matches;
use getopts::Options;
use iron::prelude::*;
use mount::Mount;
use staticfile::Static;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;

mod api;
mod config;
mod db;
mod ddns;
mod errors;
mod index;
mod metadata;
mod playlist;
mod ui;
mod user;
mod utils;
mod serve;
mod thumbnails;
mod vfs;

fn main() {
	if let Err(ref e) = run() {
		println!("Error: {}", e);

		for e in e.iter().skip(1) {
			println!("caused by: {}", e);
		}
		if let Some(backtrace) = e.backtrace() {
			println!("backtrace: {:?}", backtrace);
		}
		::std::process::exit(1);
	}
}

#[cfg(unix)]
fn daemonize(options: &getopts::Matches) -> Result<()> {
	if options.opt_present("f") {
		return Ok(());
	}
	let mut log_file = utils::get_data_root()?;
	log_file.push("polaris.log");
	match daemonize_redirect(Some(&log_file), Some(&log_file), ChdirMode::NoChdir) {
		Ok(_) => Ok(()),
		Err(_) => bail!(ErrorKind::DaemonError),
	}
}

fn run() -> Result<()> {

	// Parse CLI options
	let args: Vec<String> = std::env::args().collect();
	let mut options = Options::new();
	options.optopt("c", "config", "set the configuration file", "FILE");
	options.optopt("p", "port", "set polaris to run on a custom port", "PORT");
	options.optopt("d", "database", "set the path to index database", "FILE");
	options.optopt("w", "web", "set the path to web client files", "DIRECTORY");

	#[cfg(unix)]
	options.optflag("f",
	                "foreground",
	                "run polaris in the foreground instead of daemonizing");

	options.optflag("h", "help", "print this help menu");

	let matches = options.parse(&args[1..])?;

	if matches.opt_present("h") {
		let program = args[0].clone();
		let brief = format!("Usage: {} [options]", program);
		print!("{}", options.usage(&brief));
		return Ok(());
	}

	#[cfg(unix)]
	daemonize(&matches)?;

	// Init DB
	println!("Starting up database");
	let db_path = matches.opt_str("d");
	let mut default_db_path = utils::get_data_root()?;
	default_db_path.push("db.sqlite");
	let db_path = db_path
		.map(|n| Path::new(n.as_str()).to_path_buf())
		.unwrap_or(default_db_path);
	let db = Arc::new(db::DB::new(&db_path)?);

	// Parse config
	let config_file_name = matches.opt_str("c");
	let config_file_path = config_file_name.map(|p| Path::new(p.as_str()).to_path_buf());
	if let Some(path) = config_file_path {
		let config = config::parse_toml_file(&path)?;
		config::overwrite(db.deref(), &config)?;
	}

	// Init index
	let (index_sender, index_receiver) = channel();
	let index_sender = Arc::new(Mutex::new(index_sender));
	let db_ref = db.clone();
	std::thread::spawn(move || {
		                   let db = db_ref.deref();
		                   index::update_loop(db, index_receiver);
		                  });

	// Trigger auto-indexing
	let db_ref = db.clone();
	let sender_ref = index_sender.clone();
	std::thread::spawn(move || { index::self_trigger(db_ref.deref(), sender_ref); });

	// Mount API
	println!("Mounting API");
	let mut mount = Mount::new();
	let handler = api::get_handler(db.clone(), index_sender)?;
	mount.mount("/api/", handler);

	// Mount static files
	println!("Mounting static files");
	let web_dir_name = matches.opt_str("w");
	let mut default_web_dir = utils::get_data_root()?;
	default_web_dir.push("web");
	let web_dir_path = web_dir_name
		.map(|n| Path::new(n.as_str()).to_path_buf())
		.unwrap_or(default_web_dir);

	mount.mount("/", Static::new(web_dir_path));

	println!("Starting up server");
	let port: u16 = matches
		.opt_str("p")
		.unwrap_or("5050".to_owned())
		.parse()
		.or(Err("invalid port number"))?;
	let mut server = match Iron::new(mount).http(("0.0.0.0", port)) {
		Ok(s) => s,
		Err(e) => bail!("Error starting up server: {}", e),
	};

	// Start DDNS updates
	let db_ref = db.clone();
	std::thread::spawn(move || { ddns::run(db_ref.deref()); });

	// Run UI
	ui::run();

	println!("Shutting down server");
	if let Err(e) = server.close() {
		bail!("Error shutting down server: {}", e);
	}

	Ok(())
}
<commit_msg>Removed unused import<commit_after>#![recursion_limit = "128"]

extern crate ape;
extern crate app_dirs;
extern crate core;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_codegen;
#[macro_use]
extern crate error_chain;
extern crate getopts;
extern crate hyper;
extern crate id3;
extern crate image;
extern crate iron;
extern crate lewton;
extern crate metaflac;
extern crate mount;
extern crate params;
extern crate rand;
extern crate reqwest;
extern crate regex;
extern crate ring;
extern crate router;
extern crate secure_session;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate staticfile;
extern crate toml;
extern crate typemap;
extern crate url;

#[cfg(windows)]
extern crate uuid;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate shell32;
#[cfg(windows)]
extern crate user32;

#[cfg(unix)]
extern crate unix_daemonize;

#[cfg(unix)]
use unix_daemonize::{daemonize_redirect, ChdirMode};

use core::ops::Deref;
use errors::*;
use getopts::Options;
use iron::prelude::*;
use mount::Mount;
use staticfile::Static;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::channel;

mod api;
mod config;
mod db;
mod ddns;
mod errors;
mod index;
mod metadata;
mod playlist;
mod ui;
mod user;
mod utils;
mod serve;
mod thumbnails;
mod vfs;

fn main() {
	if let Err(ref e) = run() {
		println!("Error: {}", e);

		for e in e.iter().skip(1) {
			println!("caused by: {}", e);
		}
		if let Some(backtrace) = e.backtrace() {
			println!("backtrace: {:?}", backtrace);
		}
		::std::process::exit(1);
	}
}

#[cfg(unix)]
fn daemonize(options: &getopts::Matches) -> Result<()> {
	if options.opt_present("f") {
		return Ok(());
	}
	let mut log_file = utils::get_data_root()?;
	log_file.push("polaris.log");
	match daemonize_redirect(Some(&log_file), Some(&log_file), ChdirMode::NoChdir) {
		Ok(_) => Ok(()),
		Err(_) => bail!(ErrorKind::DaemonError),
	}
}

fn run() -> Result<()> {

	// Parse CLI options
	let args: Vec<String> = std::env::args().collect();
	let mut options = Options::new();
	options.optopt("c", "config", "set the configuration file", "FILE");
	options.optopt("p", "port", "set polaris to run on a custom port", "PORT");
	options.optopt("d", "database", "set the path to index database", "FILE");
	options.optopt("w", "web", "set the path to web client files", "DIRECTORY");

	#[cfg(unix)]
	options.optflag("f",
	                "foreground",
	                "run polaris in the foreground instead of daemonizing");

	options.optflag("h", "help", "print this help menu");

	let matches = options.parse(&args[1..])?;

	if matches.opt_present("h") {
		let program = args[0].clone();
		let brief = format!("Usage: {} [options]", program);
		print!("{}", options.usage(&brief));
		return Ok(());
	}

	#[cfg(unix)]
	daemonize(&matches)?;

	// Init DB
	println!("Starting up database");
	let db_path = matches.opt_str("d");
	let mut default_db_path = utils::get_data_root()?;
	default_db_path.push("db.sqlite");
	let db_path = db_path
		.map(|n| Path::new(n.as_str()).to_path_buf())
		.unwrap_or(default_db_path);
	let db = Arc::new(db::DB::new(&db_path)?);

	// Parse config
	let config_file_name = matches.opt_str("c");
	let config_file_path = config_file_name.map(|p| Path::new(p.as_str()).to_path_buf());
	if let Some(path) = config_file_path {
		let config = config::parse_toml_file(&path)?;
		config::overwrite(db.deref(), &config)?;
	}

	// Init index
	let (index_sender, index_receiver) = channel();
	let index_sender = Arc::new(Mutex::new(index_sender));
	let db_ref = db.clone();
	std::thread::spawn(move || {
		                   let db = db_ref.deref();
		                   index::update_loop(db, index_receiver);
		                  });

	// Trigger auto-indexing
	let db_ref = db.clone();
	let sender_ref = index_sender.clone();
	std::thread::spawn(move || { index::self_trigger(db_ref.deref(), sender_ref); });

	// Mount API
	println!("Mounting API");
	let mut mount = Mount::new();
	let handler = api::get_handler(db.clone(), index_sender)?;
	mount.mount("/api/", handler);

	// Mount static files
	println!("Mounting static files");
	let web_dir_name = matches.opt_str("w");
	let mut default_web_dir = utils::get_data_root()?;
	default_web_dir.push("web");
	let web_dir_path = web_dir_name
		.map(|n| Path::new(n.as_str()).to_path_buf())
		.unwrap_or(default_web_dir);

	mount.mount("/", Static::new(web_dir_path));

	println!("Starting up server");
	let port: u16 = matches
		.opt_str("p")
		.unwrap_or("5050".to_owned())
		.parse()
		.or(Err("invalid port number"))?;
	let mut server = match Iron::new(mount).http(("0.0.0.0", port)) {
		Ok(s) => s,
		Err(e) => bail!("Error starting up server: {}", e),
	};

	// Start DDNS updates
	let db_ref = db.clone();
	std::thread::spawn(move || { ddns::run(db_ref.deref()); });

	// Run UI
	ui::run();

	println!("Shutting down server");
	if let Err(e) = server.close() {
		bail!("Error shutting down server: {}", e);
	}

	Ok(())
}
<|endoftext|>


<commit_before>extern crate argparse;
extern crate glob;
extern crate regex;

use argparse::{ArgumentParser, StoreTrue, Store, StoreOption, Print};
use std::io::BufRead;
use glob::glob;
use regex::Regex;

fn identity<T>(x: T) -> T {x}

struct Options {
    debug: bool,
    path: String,
    file: Option<String>
}

fn get_possible_files_from_glob() -> Result<Vec<std::path::PathBuf>, glob::PatternError> {
    glob("**/*").map(|paths| paths.flat_map(identity).filter(|path| path.is_file()).collect())
}

fn get_filename_minus_extension(path_str: &String) -> String {
    std::path::Path::new(path_str).file_stem().unwrap().to_str().unwrap().to_string()
}

fn is_test_file(path: &String) -> bool {
    let re = Regex::new(r"^(features/|test/|spec/|tests/)").unwrap();
    re.is_match(path.as_str())
}

fn strip_test_words(filename: &String) -> String {
    let re = Regex::new(r"(test_)?(?P<p>\w+?)(_rake_spec|_spec|_test|_steps)?(\.rb|\.exs|\.ex|\.js|\.py)?$").unwrap();
    re.replace_all(filename.as_str(), "$p")
}

fn cleanse_path(path: &str) -> String {
    let s = path.to_string();
    if s[0..2].to_string() == "./" {
        s[2..].to_string()
    } else {
        s
    }
}

fn find_longest_common_substring_length(s1: &String, s2: &String) -> i32 {
    // Currently this is implemented using a dynamic programming solution similar
    // to http://www.geeksforgeeks.org/longest-common-substring/. This is O(N*M)
    // where N is the length of one string and M is the length of the other
    // string.
    //
    // Another option would of course be to explore using something like a
    // suffix tree to solve this problem, something like, the following.
    // http://www.geeksforgeeks.org/suffix-tree-application-5-longest-common-substring-2/
    // This is O(M+N) to build a Generalized Suffix Tree and O(M+N) to find the
    // the longest common substring via depth first search.
    //
    // Beyond that we would have to explore not caring about longest substring
    // and moving to a similarity ranking algorithm that maybe cares about
    // subsequences rather that substrings, etc.
    if s1.is_empty() || s2.is_empty() {
        return 0
    }

    let mut m: Vec<Vec<i32>> = Vec::with_capacity(s1.len());
    for _ in 0..s1.len() {
        let v: Vec<i32> = vec![0; s2.len()];
        m.push(v);
    }

    let mut longest_length = 0;

    let s1_bytes = s1.as_bytes();
    let s2_bytes = s2.as_bytes();

    for i in 0..s1.len() {
        for j in 0..s2.len() {
            if s1_bytes[i] == s2_bytes[j] {
                m[i][j] = 1;
                if i > 0 && j > 0 {
                    m[i][j] += m[i-1][j-1];
                }
                if m[i][j] > longest_length {
                    longest_length = m[i][j];
                }
            }
        }
    }

    longest_length
}

fn score(s1: &String, s2: &String) -> f32 {
    // TODO: Improve performance. At the moment the find_longest_common_substring
    // method is the most costly operation in this app. This leaves us with a few
    // levers to move in terms of improving performance. Specifically, we could
    // find a more performant algorithm, or find a way to reduce the number of
    // times it has to be called, or use threading to do a scatter and gather
    // approach.
    let longest_match_length: f32 = find_longest_common_substring_length(s1, s2) as f32;
    (longest_match_length/s2.len() as f32) * (longest_match_length/s1.len() as f32)
}

fn find_alt(filename: &String, cleansed_path: &String, paths: Vec<String>, test_file: bool) -> String {
    let (_, alternate) = paths.iter()
        .map(|path| cleanse_path(&path))
        .filter(|path| path.contains(filename.as_str()))  // filter to paths that contain the filename
        .filter(|path| is_test_file(&path) != test_file)
        .fold((0 as f32, "".to_string()), |result, path| {
            let (highest_score, best_match) = result;
            let s = score(&path, &cleansed_path);
            if s > highest_score {
                (s, path)
            } else {
                (highest_score, best_match)
            }
        });
    alternate
}

fn main() {
    let mut options = Options {
        debug: false,
        path: "".to_string(),
        file: None
    };

    { // block limits of borrows by refer() method calls
        let mut ap = ArgumentParser::new();
        ap.add_option(&["-v", "--version"], Print(env!("CARGO_PKG_VERSION").to_string()), "show version");
        ap.refer(&mut options.debug).add_option(&["-d", "--debug"], StoreTrue, "enable debug output");
        ap.refer(&mut options.file).add_option(&["-f", "--file"], StoreOption, "possible alternates file, - for stdin");
        ap.refer(&mut options.path).add_argument("PATH", Store, "path to find alternate for").required();
        ap.parse_args_or_exit();
    }

    //println!("debug: {}", options.debug);
    //println!("required path: {}", options.path);
    let cleansed_path = cleanse_path(&options.path);
    let mut filename = get_filename_minus_extension(&options.path);
    if is_test_file(&cleansed_path) {
        filename = strip_test_words(&filename);
    }

    let best_match = if let Some(unwrapped_file) = options.file {
        if unwrapped_file == "-" {
            let stdin = std::io::stdin();
            let paths: Vec<String> = stdin.lock().lines().map(|path| path.unwrap()).collect();
            find_alt(&filename, &cleansed_path, paths, is_test_file(&cleansed_path))
        } else {
            println!("DON'T CURRENTLY SUPPORT SPECIFIED FILES");
            "".to_string()
            // figure out how to open specified file and read in as content
        }
    } else {
        match get_possible_files_from_glob() {
            Ok(paths) => {
                let unwrapped_paths:Vec<String> = paths.iter().map(|path| { path.to_str().unwrap().to_string() }).collect();
                find_alt(&filename, &cleansed_path, unwrapped_paths, is_test_file(&cleansed_path))
            },
            Err(e) => {
                println!("{:?}", e);
                "".to_string()
            }
        }
    };
    print!("{}", best_match);
}
<commit_msg>Added error handling and general code cleanup for release<commit_after>extern crate argparse;
extern crate glob;
extern crate regex;

use argparse::{ArgumentParser, StoreTrue, Store, StoreOption, Print};
use std::io::BufReader;
use std::io::BufRead;
use std::fs::File;
use std::io::Write;
use glob::glob;
use regex::Regex;

fn identity<T>(x: T) -> T {x}

macro_rules! printerr(
    ($($arg:tt)*) => { {
        let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
        r.expect("failed printing to stderr");
    } }
);

struct Options {
    path: String,
    file: Option<String>
}

fn get_possible_files_from_glob() -> Result<Vec<std::path::PathBuf>, glob::PatternError> {
    glob("**/*").map(|paths| paths.flat_map(identity).filter(|path| path.is_file()).collect())
}

fn get_filename_minus_extension(path_str: &String) -> String {
    std::path::Path::new(path_str).file_stem().unwrap().to_str().unwrap().to_string()
}

fn is_test_file(path: &String) -> bool {
    let re = Regex::new(r"^(features/|test/|spec/|tests/)").unwrap();
    re.is_match(path.as_str())
}

fn strip_test_words(filename: &String) -> String {
    let re = Regex::new(r"(test_)?(?P<p>\w+?)(_rake_spec|_spec|_test|_steps)?(\.rb|\.exs|\.ex|\.js|\.py)?$").unwrap();
    re.replace_all(filename.as_str(), "$p")
}

fn cleanse_path(path: &str) -> String {
    let s = path.to_string();
    if s[0..2].to_string() == "./" {
        s[2..].to_string()
    } else {
        s
    }
}

fn find_longest_common_substring_length(s1: &String, s2: &String) -> i32 {
    // Currently this is implemented using a dynamic programming solution similar
    // to http://www.geeksforgeeks.org/longest-common-substring/. This is O(N*M)
    // where N is the length of one string and M is the length of the other
    // string.
    //
    // Another option would of course be to explore using something like a
    // suffix tree to solve this problem, something like, the following.
    // http://www.geeksforgeeks.org/suffix-tree-application-5-longest-common-substring-2/
    // This is O(M+N) to build a Generalized Suffix Tree and O(M+N) to find the
    // the longest common substring via depth first search.
    //
    // Beyond that we would have to explore not caring about longest substring
    // and moving to a similarity ranking algorithm that maybe cares about
    // subsequences rather that substrings, etc.
    if s1.is_empty() || s2.is_empty() {
        return 0
    }

    let mut m: Vec<Vec<i32>> = Vec::with_capacity(s1.len());
    for _ in 0..s1.len() {
        let v: Vec<i32> = vec![0; s2.len()];
        m.push(v);
    }

    let mut longest_length = 0;

    let s1_bytes = s1.as_bytes();
    let s2_bytes = s2.as_bytes();

    for i in 0..s1.len() {
        for j in 0..s2.len() {
            if s1_bytes[i] == s2_bytes[j] {
                m[i][j] = 1;
                if i > 0 && j > 0 {
                    m[i][j] += m[i-1][j-1];
                }
                if m[i][j] > longest_length {
                    longest_length = m[i][j];
                }
            }
        }
    }

    longest_length
}

fn score(s1: &String, s2: &String) -> f32 {
    let longest_match_length: f32 = find_longest_common_substring_length(s1, s2) as f32;
    (longest_match_length/s2.len() as f32) * (longest_match_length/s1.len() as f32)
}

fn find_alt(filename: &String, cleansed_path: &String, paths: Vec<String>, test_file: bool) -> String {
    let (_, alternate) = paths.iter()
        .map(|path| cleanse_path(&path))
        .filter(|path| path.contains(filename.as_str()))  // filter to paths that contain the filename
        .filter(|path| is_test_file(&path) != test_file)
        .fold((0 as f32, "".to_string()), |result, path| {
            let (highest_score, best_match) = result;
            let s = score(&path, &cleansed_path);
            if s > highest_score {
                (s, path)
            } else {
                (highest_score, best_match)
            }
        });
    alternate
}

fn main() {
    let mut options = Options {
        path: "".to_string(),
        file: None
    };

    { // block limits of borrows by refer() method calls
        let mut ap = ArgumentParser::new();
        ap.add_option(&["-v", "--version"], Print(env!("CARGO_PKG_VERSION").to_string()), "show version");
        ap.refer(&mut options.file).add_option(&["-f", "--file"], StoreOption, "possible alternates file, - for stdin");
        ap.refer(&mut options.path).add_argument("PATH", Store, "path to find alternate for").required();
        ap.parse_args_or_exit();
    }

    let cleansed_path = cleanse_path(&options.path);
    let mut filename = get_filename_minus_extension(&options.path);
    if is_test_file(&cleansed_path) {
        filename = strip_test_words(&filename);
    }

    let best_match = if let Some(unwrapped_file) = options.file {
        if unwrapped_file == "-" {
            let stdin = std::io::stdin();
            let paths: Vec<String> = stdin.lock().lines().map(|path| path.unwrap()).collect();
            find_alt(&filename, &cleansed_path, paths, is_test_file(&cleansed_path))
        } else {
            let f = match File::open(&unwrapped_file) {
                Ok(file) => file,
                Err(e) => {
                    printerr!("Failure occurred opening file {}, {}", &unwrapped_file, e);
                    std::process::exit(1)
                }
            };
            let file = BufReader::new(&f);
            let paths: Vec<String> = file.lines().map(|path| path.unwrap()).collect();
            find_alt(&filename, &cleansed_path, paths, is_test_file(&cleansed_path))
        }
    } else {
        match get_possible_files_from_glob() {
            Ok(paths) => {
                let unwrapped_paths:Vec<String> = paths.iter().map(|path| { path.to_str().unwrap().to_string() }).collect();
                find_alt(&filename, &cleansed_path, unwrapped_paths, is_test_file(&cleansed_path))
            },
            Err(e) => {
                printerr!("Error reading paths {}", e);
                std::process::exit(1)
            }
        }
    };
    print!("{}", best_match);
}
<|endoftext|>


<commit_before>//! Loads the configuration and runs the game workflow.
extern crate crossterm;
use crossterm::{terminal, ClearType};
extern crate rand;
mod game;
use game::{Game, State};
mod user_interface;
use user_interface::UserInterface;
mod dictionary;
use dictionary::Dict;
use dictionary::RewardingScheme;
mod image;

use std::env;
use std::error::Error;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process;

#[macro_use]
extern crate custom_error;

const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
const AUTHOR: &str = "(c) Jens Getreu, 2016-2019.";

/// Text to show as command-line help --help
const COMMANDLINE_HELP: &str = r#"
Hangman is a paper and pencil guessing game for two or more players.  One player
thinks of a word, phrase or sentence and the other tries to guess it by
suggesting letters or numbers, within a certain number of guesses. In this
version for children the computer selects a word, phrase or sentence randomly
out of a word-list defined in a configuration file. In the course of the game
Ascii-Art images - designed for children - are progressively disclosed.

===================================
-----------------------------------
ASCII-ART HANGMAN FOR KIDS

         ,.
        (_|,.
       ,' /, )_______
    __j o``-'        `
   (")
    `-j                |
      `-._(           /
         |_\  |--^.  /
        /_]'|_| /_)_/
           /_]'  /_]'

Lives:  1       Last guess: 3

 g o o _ _ l _ _ k

Type a letter then type [Enter]:
-----------------------------------
===================================

 Usage: hangman
        hangman [FILE]...
        hangman -h|--help
        hangman -V|--version

`[FILE]` are configuration files containing word-lists and optionally Ascii-Art
images.

When no `[FILE]` argument is given, `[FILE]` defaults to `hangman-words.txt`. In
case no `[FILE]` is found, a template configuration file `hangman-words.txt` is
written into the current working directory. Multiple `[FILE]`s are concatted.

`[FILE]` is an UTF-8 file containing 4 different line-types:

- lines starting with a letter, a digit or '-' are secret strings. At the
  beginning of the game one line is randomly chosen and all characters are
  hidden. If you want to give an additional hint, enclose some characters
  with `_`.  The enclosed is then displayed in clear when the game starts.
  For example the configuration line:

      Guess _me_

  is shown in the game as:

      _ _ _ _ _ _ m e

- lines starting with `#` are ignored. This can be used for comments.

- lines starting with `|` are part of an optional custom Ascii-Art image shown
  progressively in the course of the game. If not defined here, built in
  Ascii-Art images are used instead.

- lines starting with `:` are game modifier. They change the logic how the image
  is progressively disclosed:

   `:success-rewarding`       Every guessed character shows a bit more of
                              the image. This mode is default.
   `:traditional-rewarding`   Every lost live discloses a bit more of the
                              image. Choose this mode together with a
                              traditional gallows image (not built in).

The following shows an example of a custom image (copy it left-aligned
into the config-file):

        |  ,~~--~~-.
        | +      | |\
        | || |~ |`,/-\
        | *\_) \_) `-'

If you prefer a traditional gallows image, add the following:

        :traditional-rewarding
        |  ______
        |  |    |
        |  |    O
        |  |   /|\
        |  |    |
        |  |   / \
        |__|_____
        ||      |___
        ||_________|

"#;

/// Number of wrong guess allowed.
const LIVES: u8 = 7;
/// Default configuration filename when no filename is given at the command-line.
const PATHSTR: &str = "hangman-words.txt";
/// Fallback sample configuration when no configuration file can be found.
const CONF_TEMPLATE: &str =
    r#"# Type `hangman -h` to learn how to insert custom ASCII-art images here.`

guess me
hang_man_
_good l_uck
"#;

/// Fallback secret when no configuration file can be found.
const CONF_DEMO: &str = "- _Demo: add own words to config file and start a_gain_!";

// ------------------ MAIN ---------------------------------------------

trait Render {
    /// Renders a graphical text representation of Self. It would be more consistent to implement
    /// Display instead, but crossterm does not support `print!(f, ...)`. Therefor, it is not on
    /// option here.
    fn render(&self);
}

/// Reads the configuration file.
pub fn read_config(pathstr: &PathBuf) -> Result<String, io::Error> {
    let mut f = File::open(pathstr)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

/// Writes a sample configuration file on disk. Called when no configuration file can be found.
pub fn write_config_template(pathstr: &PathBuf) -> Result<(), io::Error> {
    let mut file = File::create(&pathstr)?;
    file.write_all(CONF_TEMPLATE.as_bytes())?;
    Ok(())
}

/// Starts the game.
fn main() {
    // SHOW HELP TEXT
    match env::args().nth(1) {
        Some(ref a) if a == "-h" || a == "--help" => {
            eprintln!("{}", COMMANDLINE_HELP);
            return;
        }
        Some(ref a) if a == "-V" || a == "--version" => {
            eprintln!("{}", VERSION.unwrap());
            return;
        }
        Some(_) | None => {}
    };

    // READ CONFIG

    // Read all config files given on command line
    let mut conf_file_paths = env::args()
        .skip(1)
        .map(|s| PathBuf::from(s))
        .collect::<Vec<PathBuf>>();

    // if no conf_file_paths are given then use default config path
    if conf_file_paths.is_empty() {
        conf_file_paths.push(PathBuf::from(PATHSTR))
    };

    // read and concat all config files given on command line
    let cwd = env::current_dir().unwrap();

    let mut config: String = String::new();
    for conf_file_path in &conf_file_paths {
        let path = conf_file_path;
        let c = match read_config(&path) {
            Ok(s) => s,
            Err(_) => {
                match write_config_template(&path) {
                    Ok(_) => {
                        eprintln!(
                            "As no config-file :\n\
                             \t{:?}\n\
                             was found a template file is written in the \
                             current working directory.\n\
                             \t{:?}\n\n\nPress [Enter] to enter demo mode.",
                            path, cwd
                        );
                        // wait for [Enter] key
                        let s = &mut String::new();
                        io::stdin().read_line(s).unwrap();
                        CONF_DEMO.to_string()
                    }
                    Err(why) => {
                        eprintln!(
                            "Couldn't write hangman template \
                             config-file:\n\t{:?}\n({})\n\n\
                             Current working directory is:\n\t{:?}\n\n\
                             Press [Enter] to enter demo mode.",
                            path,
                            Error::description(&why),
                            cwd
                        );
                        // wait for [Enter] key
                        let s = &mut String::new();
                        io::stdin().read_line(s).unwrap();
                        CONF_DEMO.to_string()
                    }
                }
            }
        };
        config.push_str(&c);
    }

    // INITIALISE GAME

    let terminal = terminal();
    let mut ui = UserInterface::new(&config);

    let dict = match Dict::new(&config) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("ERROR IN CONFIGURATION FILE\n{}", e);

            // wait for [Enter] key
            let s = &mut String::new();
            io::stdin().read_line(s).unwrap();
            process::exit(1);
        }
    };

    // PLAY

    'playing: loop {
        let mut game = Game::new(&(dict.get_random_word()), LIVES);
        let chars_to_guess = game.visible_chars();

        // The game loop

        // Clear all lines in terminal;
        terminal
            .clear(ClearType::All)
            .expect("Can not clear terminal.");

        'running_game: loop {
            match dict.rewarding_scheme {
                RewardingScheme::UnhideWhenGuessedChar => {
                    ui.image.disclose((game.visible_chars(), chars_to_guess));
                }
                RewardingScheme::UnhideWhenLostLife => {
                    ui.image.disclose((game.lives as usize, LIVES as usize));
                }
            };

            ui.message = format!("{}\n", game);
            ui.render();

            match game.get_state() {
                State::Victory => {
                    println!("Congratulations! You won!");
                    break 'running_game;
                }
                State::Defeat => {
                    println!("Sorry, you lost! Better luck next time!");
                    break 'running_game;
                }
                _ => {}
            }

            print!("Type a letter, then press [Enter]: ");
            io::stdout().flush().unwrap();

            // Read next char and send it
            let guess = &mut String::new();
            io::stdin().read_line(guess).unwrap();
            game.guess(guess.chars().next().unwrap_or(' '));
        }

        println!("New game? Type [Y]es or [n]o: ");
        let s = &mut String::new();
        io::stdin().read_line(s).unwrap();
        let answer = s.chars().next().unwrap_or('Y');

        if answer == 'N' || answer == 'n' {
            break 'playing;
        };
    }

    println!("\n{}", AUTHOR);
}
<commit_msg>write config template with U+0A + additional U+0D<commit_after>//! Loads the configuration and runs the game workflow.
extern crate crossterm;
use crossterm::{terminal, ClearType};
extern crate rand;
mod game;
use game::{Game, State};
mod user_interface;
use user_interface::UserInterface;
mod dictionary;
use dictionary::Dict;
use dictionary::RewardingScheme;
mod image;

use std::env;
use std::error::Error;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process;

#[macro_use]
extern crate custom_error;

const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
const AUTHOR: &str = "(c) Jens Getreu, 2016-2019.";

/// Text to show as command-line help --help
const COMMANDLINE_HELP: &str = r#"
Hangman is a paper and pencil guessing game for two or more players.  One player
thinks of a word, phrase or sentence and the other tries to guess it by
suggesting letters or numbers, within a certain number of guesses. In this
version for children the computer selects a word, phrase or sentence randomly
out of a word-list defined in a configuration file. In the course of the game
Ascii-Art images - designed for children - are progressively disclosed.

===================================
-----------------------------------
ASCII-ART HANGMAN FOR KIDS

         ,.
        (_|,.
       ,' /, )_______
    __j o``-'        `
   (")
    `-j                |
      `-._(           /
         |_\  |--^.  /
        /_]'|_| /_)_/
           /_]'  /_]'

Lives:  1       Last guess: 3

 g o o _ _ l _ _ k

Type a letter then type [Enter]:
-----------------------------------
===================================

 Usage: hangman
        hangman [FILE]...
        hangman -h|--help
        hangman -V|--version

`[FILE]` are configuration files containing word-lists and optionally Ascii-Art
images.

When no `[FILE]` argument is given, `[FILE]` defaults to `hangman-words.txt`. In
case no `[FILE]` is found, a template configuration file `hangman-words.txt` is
written into the current working directory. Multiple `[FILE]`s are concatted.

`[FILE]` is an UTF-8 file containing 4 different line-types:

- lines starting with a letter, a digit or '-' are secret strings. At the
  beginning of the game one line is randomly chosen and all characters are
  hidden. If you want to give an additional hint, enclose some characters
  with `_`.  The enclosed is then displayed in clear when the game starts.
  For example the configuration line:

      Guess _me_

  is shown in the game as:

      _ _ _ _ _ _ m e

- lines starting with `#` are ignored. This can be used for comments.

- lines starting with `|` are part of an optional custom Ascii-Art image shown
  progressively in the course of the game. If not defined here, built in
  Ascii-Art images are used instead.

- lines starting with `:` are game modifier. They change the logic how the image
  is progressively disclosed:

   `:success-rewarding`       Every guessed character shows a bit more of
                              the image. This mode is default.
   `:traditional-rewarding`   Every lost live discloses a bit more of the
                              image. Choose this mode together with a
                              traditional gallows image (not built in).

The following shows an example of a custom image (copy it left-aligned
into the config-file):

        |  ,~~--~~-.
        | +      | |\
        | || |~ |`,/-\
        | *\_) \_) `-'

If you prefer a traditional gallows image, add the following:

        :traditional-rewarding
        |  ______
        |  |    |
        |  |    O
        |  |   /|\
        |  |    |
        |  |   / \
        |__|_____
        ||      |___
        ||_________|

"#;

/// Number of wrong guess allowed.
const LIVES: u8 = 7;
/// Default configuration filename when no filename is given at the command-line.
const PATHSTR: &str = "hangman-words.txt";
/// Fallback sample configuration when no configuration file can be found.
const CONF_TEMPLATE: &str =
    "# Type `hangman -h` to learn how to insert custom ASCII-art images here. \r
\r
guess me\r
hang_man_\r
_good l_uck\r
";

/// Fallback secret when no configuration file can be found.
const CONF_DEMO: &str = "- _Demo: add own words to config file and start a_gain_!";

// ------------------ MAIN ---------------------------------------------

trait Render {
    /// Renders a graphical text representation of Self. It would be more consistent to implement
    /// Display instead, but crossterm does not support `print!(f, ...)`. Therefor, it is not on
    /// option here.
    fn render(&self);
}

/// Reads the configuration file.
pub fn read_config(pathstr: &PathBuf) -> Result<String, io::Error> {
    let mut f = File::open(pathstr)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

/// Writes a sample configuration file on disk. Called when no configuration file can be found.
pub fn write_config_template(pathstr: &PathBuf) -> Result<(), io::Error> {
    let mut file = File::create(&pathstr)?;
    file.write_all(CONF_TEMPLATE.as_bytes())?;
    Ok(())
}

/// Starts the game.
fn main() {
    // SHOW HELP TEXT
    match env::args().nth(1) {
        Some(ref a) if a == "-h" || a == "--help" => {
            eprintln!("{}", COMMANDLINE_HELP);
            return;
        }
        Some(ref a) if a == "-V" || a == "--version" => {
            eprintln!("{}", VERSION.unwrap());
            return;
        }
        Some(_) | None => {}
    };

    // READ CONFIG

    // Read all config files given on command line
    let mut conf_file_paths = env::args()
        .skip(1)
        .map(|s| PathBuf::from(s))
        .collect::<Vec<PathBuf>>();

    // if no conf_file_paths are given then use default config path
    if conf_file_paths.is_empty() {
        conf_file_paths.push(PathBuf::from(PATHSTR))
    };

    // read and concat all config files given on command line
    let cwd = env::current_dir().unwrap();

    let mut config: String = String::new();
    for conf_file_path in &conf_file_paths {
        let path = conf_file_path;
        let c = match read_config(&path) {
            Ok(s) => s,
            Err(_) => {
                match write_config_template(&path) {
                    Ok(_) => {
                        eprintln!(
                            "As no config-file :\n\
                             \t{:?}\n\
                             was found a template file is written in the \
                             current working directory.\n\
                             \t{:?}\n\n\nPress [Enter] to enter demo mode.",
                            path, cwd
                        );
                        // wait for [Enter] key
                        let s = &mut String::new();
                        io::stdin().read_line(s).unwrap();
                        CONF_DEMO.to_string()
                    }
                    Err(why) => {
                        eprintln!(
                            "Couldn't write hangman template \
                             config-file:\n\t{:?}\n({})\n\n\
                             Current working directory is:\n\t{:?}\n\n\
                             Press [Enter] to enter demo mode.",
                            path,
                            Error::description(&why),
                            cwd
                        );
                        // wait for [Enter] key
                        let s = &mut String::new();
                        io::stdin().read_line(s).unwrap();
                        CONF_DEMO.to_string()
                    }
                }
            }
        };
        config.push_str(&c);
    }

    // INITIALISE GAME

    let terminal = terminal();
    let mut ui = UserInterface::new(&config);

    let dict = match Dict::new(&config) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("ERROR IN CONFIGURATION FILE\n{}", e);

            // wait for [Enter] key
            let s = &mut String::new();
            io::stdin().read_line(s).unwrap();
            process::exit(1);
        }
    };

    // PLAY

    'playing: loop {
        let mut game = Game::new(&(dict.get_random_word()), LIVES);
        let chars_to_guess = game.visible_chars();

        // The game loop

        // Clear all lines in terminal;
        terminal
            .clear(ClearType::All)
            .expect("Can not clear terminal.");

        'running_game: loop {
            match dict.rewarding_scheme {
                RewardingScheme::UnhideWhenGuessedChar => {
                    ui.image.disclose((game.visible_chars(), chars_to_guess));
                }
                RewardingScheme::UnhideWhenLostLife => {
                    ui.image.disclose((game.lives as usize, LIVES as usize));
                }
            };

            ui.message = format!("{}\n", game);
            ui.render();

            match game.get_state() {
                State::Victory => {
                    println!("Congratulations! You won!");
                    break 'running_game;
                }
                State::Defeat => {
                    println!("Sorry, you lost! Better luck next time!");
                    break 'running_game;
                }
                _ => {}
            }

            print!("Type a letter, then press [Enter]: ");
            io::stdout().flush().unwrap();

            // Read next char and send it
            let guess = &mut String::new();
            io::stdin().read_line(guess).unwrap();
            game.guess(guess.chars().next().unwrap_or(' '));
        }

        println!("New game? Type [Y]es or [n]o: ");
        let s = &mut String::new();
        io::stdin().read_line(s).unwrap();
        let answer = s.chars().next().unwrap_or('Y');

        if answer == 'N' || answer == 'n' {
            break 'playing;
        };
    }

    println!("\n{}", AUTHOR);
}
<|endoftext|>


<commit_before>extern crate hyper;

use hyper::Server;
use hyper::server::Request;
use hyper::server::Response;

fn hello(_: Request, res: Response) {
    res.send(b"Hello World!").unwrap();
}

fn main() {
    Server::http("127.0.0.1:3000").unwrap()
        .handle_threads(hello, 10).unwrap();
}
<commit_msg>Add some path parsing<commit_after>extern crate hyper;

use hyper::Server;
use hyper::server::Request;
use hyper::server::Response;
use hyper::uri::RequestUri;

fn hello(req: Request, res: Response) {
    let retval;
    match req.uri {
        RequestUri::AbsolutePath(ref path) => {
            if path.as_str().starts_with("/announce") {
                retval = "do some comparisons";
            }
            else if path.as_str().starts_with("/somethingelse") {
                retval = "do some comparisons";
            }
            else if path.as_str().starts_with("/helloworld") {
                retval = "Hi";
            }
            else {
                retval = "Error";
            }
        },
        _ => {
            retval = "Error";
        },
    };

    res.send(retval.as_bytes()).unwrap();
}

fn main() {
    Server::http("127.0.0.1:3000").unwrap()
        .handle_threads(hello, 10).unwrap();
}
<|endoftext|>


<commit_before>#[macro_use] extern crate clap;
extern crate url;
extern crate rss;
extern crate hyper;
extern crate hyper_native_tls;
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite;

mod schema;
mod common;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

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

    // Init Sqlite database
    let mut database_url = String::new();

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label) values (?1, '')", &[&subscription.url]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    use std::io;
    use std::io::prelude::*;
    let mut stdin = io::stdin();
    let id = args.value_of("id").unwrap();
    let mut buffer = [0;1];

    let mut stmt = connection.prepare("select id, url, label from subscription where id = ?1").unwrap();
    let mut rows = stmt.query_map(&[&id], Subscription::map).unwrap();
    match rows.next() {
        Some(row) => {
            let subscription = row.unwrap();
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                connection.execute("delete from subscription where id = ?1", &[&id]).unwrap();
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

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

    println!("Subscriptions list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    No subscription");
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();
        println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {
    use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
    use hyper::net::HttpsConnector;
    use hyper_native_tls::NativeTlsClient;
    use std::io::Read; // needed for read_to_string trait
    use std::str::FromStr; // needed for FromStr trait on Channel
    use rss::Channel;

    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();
     let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();

        if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
            continue;
        }

        println!("Updating {}:", subscription.label);

        let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
        let mut body = String::new();
        let mut previous_insert_rowid: i64 = 0;
        res.read_to_string(&mut body).unwrap(); // extract body from query result

        let channel = Channel::from_str(&body).unwrap(); // parse rss into channel
        connection.execute("update subscription set label = ?1 where id = ?2", &[&channel.title(), &subscription.id]).unwrap(); // update podcast feed name
        for item in channel.items() {
            let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
            let path_segments = url.path_segments().unwrap();

            // create filename
            let mut filename = String::new();
            for segment in path_segments {
                if segment == "enclosure.mp3" || segment == "listen.mp3" {
                    filename = filename + ".mp3";
                    break;
                }
                filename = segment.to_string();
            }

            let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
            connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
            if previous_insert_rowid != connection.last_insert_rowid() {
                println!("    New podcast added: {:?}", podcast.filename);
            }
            previous_insert_rowid = connection.last_insert_rowid();
        }
        println!("Updated");
    }
}

fn pending(connection: &Connection) {
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Pending list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        println!("    {} ({})", podcast.filename, podcast.url);
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    let path = args.value_of("path").unwrap();
    connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
    println!("Download dir set to: {}", path);
}

fn download(connection: &Connection) {
    use std::io::Write;
    use std::ops::Mul;
    let mut curl = Easy::new();

    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( |a, b, _, _| {
        print!("    Downloading: {}%      \r", (b.mul(100_f64)/a).floor());
        true
    }).unwrap();
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Download pending podcast:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        let temp = "/tmp/".to_string() + podcast.filename.as_str();
        let path = Path::new(&temp);

        let mut file = match File::create(&path) {
            Err(why) => panic!("couldn't create {}",
                               why),
            Ok(file) => file,
        };
        curl.url(&podcast.url).unwrap();
        curl.write_function( move |data| {
            Ok(file.write(data).unwrap())
        }).unwrap();
        println!("    {}", podcast.filename);
        curl.perform().unwrap();
        connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
    }
}
<commit_msg>Download use downloaddir param (fix #20)<commit_after>#[macro_use] extern crate clap;
extern crate url;
extern crate rss;
extern crate hyper;
extern crate hyper_native_tls;
extern crate curl; // https://docs.rs/curl/0.4.6/curl/easy/
extern crate rusqlite;

mod schema;
mod common;

use std::process;
use clap::{App, ArgMatches};
use schema::*;
use url::Url;
use rusqlite::Connection;
use curl::easy::Easy;
use std::path::{Path};
use std::fs::{File, create_dir};
use std::env::home_dir;

fn main() {
    let lock_socket = common::create_app_lock(12345); // https://rosettacode.org/wiki/Category:Rust

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

    // Init Sqlite database
    let mut database_url = String::new();

    if matches.is_present("database") { // get path from command line
        database_url = matches.value_of("database").unwrap().to_string();
    } else { // or put database file into ~/.config/rusty/rusty.sqlite3
        let db_path = home_dir().expect("/tmp/").into_os_string().into_string().unwrap() + "/.config/rusty";
        if !Path::new(&db_path).exists() { // If path doesn't exist we create it
            create_dir(&db_path).unwrap();
        }
        database_url = db_path + "/rusty.sqlite3";
    }
    let connection = match Connection::open(&database_url) {
        Ok(connection) => connection,
        Err(e) => {
            println!("Error database connection: {} {}", e, database_url);
            process::exit(1)
        }
    };
    init(&connection); // Initialize database

    match matches.subcommand() {
        ("subscribe", Some(sub_matches)) => subscribe(sub_matches, &connection),
        ("unsubscribe", Some(sub_matches)) => unsubscribe(sub_matches, &connection),
        ("list", Some(_)) => list(&connection),
        ("update", Some(sub_matches)) => {
            let feed_id = sub_matches.value_of("id").unwrap_or("0").parse::<i64>().unwrap();
            update(sub_matches, &connection, feed_id);
        },
        ("pending", Some(_)) => pending(&connection),
        ("download", Some(_)) => download(&connection),
        ("download-dir", Some(sub_matches)) => downloaddir(sub_matches, &connection),
        ("", None) => println!("No subcommand was used"),
        _ => println!("No!"),
    }

    common::remove_app_lock(lock_socket);
}

fn init(connection: &Connection) {
    // execute only the first query if there are several queries. So we need to used split()
    let split = include_str!("sql/init.sql").split(";");
    for s in split {
        if s.trim() == "" {
            continue;
        }
        connection.execute(s, &[]).unwrap();
    }
}

fn subscribe(args: &ArgMatches, connection: &Connection) {
    match Url::parse(args.value_of("url").unwrap()) {
        Ok(url) => {
            let subscription = Subscription { id: 0, url: url.as_str().to_string(), label: String::new()};
            println!("Subscribing to: {}", subscription.url);

            let mut stmt = connection.prepare("select * from subscription where url = ?1").unwrap();
            if !stmt.exists(&[&subscription.url]).unwrap() {
                connection.execute("insert into subscription (url, label) values (?1, '')", &[&subscription.url]).unwrap();
            } else {
                println!("    Feed already subscribed to");
                return
            }
            update(args, connection, connection.last_insert_rowid());
            println!("Subscribed");
        }
        Err(e) => println!("Could not parse url '{}' {}", args.value_of("url").unwrap(), e),
    }
}

fn unsubscribe(args: &ArgMatches, connection: &Connection) {
    use std::io;
    use std::io::prelude::*;
    let mut stdin = io::stdin();
    let id = args.value_of("id").unwrap();
    let mut buffer = [0;1];

    let mut stmt = connection.prepare("select id, url, label from subscription where id = ?1").unwrap();
    let mut rows = stmt.query_map(&[&id], Subscription::map).unwrap();
    match rows.next() {
        Some(row) => {
            let subscription = row.unwrap();
            println!("Unsubscribing from: {}", subscription.url);
            println!("Sure? [y/N]");
            stdin.read_exact(&mut buffer).unwrap();
            if buffer[0] == 121u8 { // 121 is ascii code for 'y'
                connection.execute("delete from subscription where id = ?1", &[&id]).unwrap();
                println!("Unsubscribed from: {}", subscription.url);
            }
        },
        None => println!("Subscription doesn't exist"),
    }
}

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

    println!("Subscriptions list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    No subscription");
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();
        println!("    {}: {} ({})", subscription.id, subscription.label, subscription.url);
    }
}

fn update(args: &ArgMatches, connection: &Connection, feed_id: i64) {
    use hyper::Client; // https://hyper.rs/hyper/v0.10.9/hyper/index.html
    use hyper::net::HttpsConnector;
    use hyper_native_tls::NativeTlsClient;
    use std::io::Read; // needed for read_to_string trait
    use std::str::FromStr; // needed for FromStr trait on Channel
    use rss::Channel;

    let mut stmt = connection.prepare("select id, url, label from subscription").unwrap();
     let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    let client = Client::with_connector(connector); // create http client with tls support

    let mut as_downloaded = 0;
    if args.is_present("as-downloaded") {
        as_downloaded = 1;
    }

    for row in stmt.query_map(&[], Subscription::map).unwrap() {
        let subscription = row.unwrap();

        if feed_id != subscription.id && feed_id > 0 { // if an id is set, update/populate only this id
            continue;
        }

        println!("Updating {}:", subscription.label);

        let mut res = client.get(subscription.clone()).send().unwrap(); // get query result thanks to IntoUrl trait implement for Subscription
        let mut body = String::new();
        let mut previous_insert_rowid: i64 = 0;
        res.read_to_string(&mut body).unwrap(); // extract body from query result

        let channel = Channel::from_str(&body).unwrap(); // parse rss into channel
        connection.execute("update subscription set label = ?1 where id = ?2", &[&channel.title(), &subscription.id]).unwrap(); // update podcast feed name
        for item in channel.items() {
            let url = Url::parse(item.enclosure().unwrap().url()).unwrap();
            let path_segments = url.path_segments().unwrap();

            // create filename
            let mut filename = String::new();
            for segment in path_segments {
                if segment == "enclosure.mp3" || segment == "listen.mp3" {
                    filename = filename + ".mp3";
                    break;
                }
                filename = segment.to_string();
            }

            let podcast = Podcast { id: 0, subscription_id: subscription.id, url: url.as_str().to_string(), filename: filename};
            connection.execute("insert or ignore into podcast (subscription_id, url, filename, downloaded) values (?1, ?2, ?3, ?4)", &[&podcast.subscription_id, &podcast.url, &podcast.filename, &as_downloaded]).unwrap();
            if previous_insert_rowid != connection.last_insert_rowid() {
                println!("    New podcast added: {:?}", podcast.filename);
            }
            previous_insert_rowid = connection.last_insert_rowid();
        }
        println!("Updated");
    }
}

fn pending(connection: &Connection) {
    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();

    println!("Pending list:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        println!("    {} ({})", podcast.filename, podcast.url);
    }
}

fn downloaddir(args: &ArgMatches, connection: &Connection) {
    let path = args.value_of("path").unwrap();
    connection.execute("insert or replace into config (key, value) values ('downloaddir', ?1)", &[&path]).unwrap();
    println!("Download dir set to: {}", path);
}

fn getdownloaddir(connection: &Connection) -> String {
    let mut stmt = connection.prepare("select value from config where key = 'downloaddir'").unwrap();
    let mut rows = stmt.query(&[]).unwrap();
    while let Some(row) = rows.next() {
        return row.unwrap().get(0);
    }
    return "/tmp/".to_string();
}

fn download(connection: &Connection) {
    use std::io::Write;
    use std::ops::Mul;
    let mut curl = Easy::new();

    curl.progress(true).unwrap();
    curl.follow_location(true).unwrap();
    curl.progress_function( |a, b, _, _| {
        print!("    Downloading: {}%      \r", (b.mul(100_f64)/a).floor());
        true
    }).unwrap();

    let mut stmt = connection.prepare("select id, subscription_id, url, filename from podcast where downloaded = 0").unwrap();
    println!("Download pending podcast:");
    if !stmt.exists(&[]).unwrap() {
        println!("{}", "    Nothing to download");
    }
    for row in stmt.query_map(&[], Podcast::map).unwrap(){
        let podcast = row.unwrap();
        let temp = getdownloaddir(connection) + podcast.filename.as_str();
        let path = Path::new(&temp);

        let mut file = match File::create(&path) {
            Err(why) => panic!("couldn't create {}",
                               why),
            Ok(file) => file,
        };
        curl.url(&podcast.url).unwrap();
        curl.write_function( move |data| {
            Ok(file.write(data).unwrap())
        }).unwrap();
        println!("    {}", podcast.filename);
        curl.perform().unwrap();
        connection.execute("update podcast set downloaded = 1, downloaded_at = current_timestamp where id = ?1", &[&podcast.id]).unwrap();
    }
}
<|endoftext|>


<commit_before>mod operation;
mod tokenize;
mod log_entry;

extern crate chrono;
extern crate clap;

use log_entry::log_entry::LogEntry;
use clap::{Arg, App};
use std::io::prelude::*;
use std::path::Path;
use std::fs::File;
use std::io::BufReader;

fn main() {
    let matches = App::new("Podlog")
        .version("0.1")
        .arg(Arg::with_name("FILENAME")
                .required(true)
                .index(1))
    .get_matches();

    let path = Path::new(matches.value_of("FILENAME").unwrap());
    let file = BufReader::new(File::open(&path).unwrap());
    let lines = file.lines().filter_map(|result| result.ok()); // Filter out bad rows
    let entries = lines.map(|x| LogEntry::from_str(&x)); // Turn them into log entries

    // Create a hashmap based on path name
}
<commit_msg>Entries are being split properly<commit_after>mod operation;
mod tokenize;
mod log_entry;

extern crate chrono;
extern crate clap;

use log_entry::log_entry::LogEntry;
use operation::operation::OperationSource;
use clap::{Arg, App};
use std::io::prelude::*;
use std::path::Path;
use std::fs::File;
use std::io::BufReader;
use std::collections::HashMap;

fn main() {
    let matches = App::new("Podlog")
        .version("0.1")
        .arg(Arg::with_name("FILENAME")
                .required(true)
                .index(1))
    .get_matches();

    let path = Path::new(matches.value_of("FILENAME").unwrap());
    let file = BufReader::new(File::open(&path).unwrap());
    let lines = file.lines().filter_map(|result| result.ok()); // Filter out bad rows
    let entries = lines.map(|x| LogEntry::from_str(&x))
                       .filter(|x| x.operation.source == OperationSource::WEBSITE)
                       .filter(|ref x| x.is_audio_file());

    let mut results : HashMap<String, Vec<&str> > = HashMap::new();
    for entry in entries {
        let path = entry.path.clone().unwrap();
        if !results.contains_key(&path) {
            results.insert(path, Vec::new());
        } else {
            match results.get_mut(&path) {
                Some(x) => x.push("foo"),
                None => ()
            }
        }
    }
    println!("{:?}", results);
}

// Output Format:
// UniqueID, DateTime, Episode, Was Complete Download
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rand;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
//use collections::borrow::Borrow;
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 iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(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 = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    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![];
    try_display!(
        html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default()),
        "Failed to serialize modified HTML");
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        return attributes.into_iter().any(
            |attribute| attribute.name.local.as_slice() == "id" &&
                format!("{}", attribute.value) == id);
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => (&node.children).into_iter()
            .map(|child| find_node_by_id(child, id))
            .filter(|result| result.is_ok())
            .map(|result| result.unwrap())
            .next().ok_or(format!("No node with ID {} found", id)),
    }
}

struct WikipediaMinusWikipediansHandler {
    wiki: Wiki,
    client: Client
}

impl WikipediaMinusWikipediansHandler {
    fn new(wiki: Wiki, client: Client) -> WikipediaMinusWikipediansHandler {
        WikipediaMinusWikipediansHandler { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut merged_revisions = vec![];
        let vandalism_reversions = try!(self.get_vandalism_reversions(&canonical_title));
        for revision in &vandalism_reversions {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    accumulated_contents = merged_contents;
                    merged_revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        info!("For \"{}\", successfully merged {} of {} possible vandalisms: {:?}", title,
              merged_revisions.len(), vandalism_reversions.len(), merged_revisions);

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        let placeholder = format!("WMW_PLACEHOLDER_TEXT_{}", rand::random::<u64>());
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", &placeholder));
        Ok(page_contents_with_placeholder.replace(&placeholder, &body))
    }
}

impl Handler for WikipediaMinusWikipediansHandler {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: Maybe should be moved to wiki module.
            let mut url = request.url.clone();
            url.host = url::Host::Domain(self.wiki.hostname.clone());
            match self.client.get(&url.into_generic_url().serialize())
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        match wikipedia_response.read_to_end(&mut wikipedia_body) {
                            Ok(..) => {
                                let mut response = Response::with(wikipedia_body);
                                response.status = Some(wikipedia_response.status);
                                response.headers = wikipedia_response.headers.clone();
                                info!("Forwarded request for {} to {}", request.url.path.join("/"),
                                      self.wiki.hostname);
                                Ok(response)
                            },
                            Err(error) => {
                                warn!("Error reading Wikipedia response: {}", error);
                                let mut response = Response::with(
                                    (iron::status::InternalServerError,
                                     "ERROR"));
                                response.headers.set(
                                    ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                                Ok(response)
                            }
                        }
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             "ERROR: {}"));
                        response.headers.set(
                            ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let handler =
        WikipediaMinusWikipediansHandler::new(Wiki::new(wiki.to_string(), Client::new()),
                                              Client::new());
    Iron::new(handler).http(("localhost", port)).unwrap();
}

#[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!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   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).unwrap());
    }

    #[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!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   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>Log a pair of error messages, getting rid of two unused-variable compiler warnings.<commit_after>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rand;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
//use collections::borrow::Borrow;
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 iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(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 = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    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![];
    try_display!(
        html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default()),
        "Failed to serialize modified HTML");
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        return attributes.into_iter().any(
            |attribute| attribute.name.local.as_slice() == "id" &&
                format!("{}", attribute.value) == id);
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => (&node.children).into_iter()
            .map(|child| find_node_by_id(child, id))
            .filter(|result| result.is_ok())
            .map(|result| result.unwrap())
            .next().ok_or(format!("No node with ID {} found", id)),
    }
}

struct WikipediaMinusWikipediansHandler {
    wiki: Wiki,
    client: Client
}

impl WikipediaMinusWikipediansHandler {
    fn new(wiki: Wiki, client: Client) -> WikipediaMinusWikipediansHandler {
        WikipediaMinusWikipediansHandler { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut merged_revisions = vec![];
        let vandalism_reversions = try!(self.get_vandalism_reversions(&canonical_title));
        for revision in &vandalism_reversions {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    accumulated_contents = merged_contents;
                    merged_revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        info!("For \"{}\", successfully merged {} of {} possible vandalisms: {:?}", title,
              merged_revisions.len(), vandalism_reversions.len(), merged_revisions);

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        let placeholder = format!("WMW_PLACEHOLDER_TEXT_{}", rand::random::<u64>());
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", &placeholder));
        Ok(page_contents_with_placeholder.replace(&placeholder, &body))
    }
}

impl Handler for WikipediaMinusWikipediansHandler {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => {
                        warn!("Failed to get page with vandalism restored: {}", msg);
                        Response::with(
                            (iron::status::InternalServerError, "ERROR"))
                    },
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: Maybe should be moved to wiki module.
            let mut url = request.url.clone();
            url.host = url::Host::Domain(self.wiki.hostname.clone());
            match self.client.get(&url.into_generic_url().serialize())
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        match wikipedia_response.read_to_end(&mut wikipedia_body) {
                            Ok(..) => {
                                let mut response = Response::with(wikipedia_body);
                                response.status = Some(wikipedia_response.status);
                                response.headers = wikipedia_response.headers.clone();
                                info!("Forwarded request for {} to {}", request.url.path.join("/"),
                                      self.wiki.hostname);
                                Ok(response)
                            },
                            Err(error) => {
                                warn!("Error reading Wikipedia response: {}", error);
                                let mut response = Response::with(
                                    (iron::status::InternalServerError,
                                     "ERROR"));
                                response.headers.set(
                                    ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                                Ok(response)
                            }
                        }
                    },
                    Err(error) => {
                        warn!("Error calling Wikipedia: {}", error);
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             "ERROR: {}"));
                        response.headers.set(
                            ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let handler =
        WikipediaMinusWikipediansHandler::new(Wiki::new(wiki.to_string(), Client::new()),
                                              Client::new());
    Iron::new(handler).http(("localhost", port)).unwrap();
}

#[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!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   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).unwrap());
    }

    #[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!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   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>//! Muxed. A tmux project manager with no runtime dependencies.
extern crate common;
extern crate docopt;
extern crate edit;
extern crate load;
extern crate new;
extern crate snapshot;

use common::args::Args;
use docopt::Docopt;
use std::env;
use std::process::exit;

#[macro_export]
macro_rules! try_or_err (
    ($expr: expr) => ({
        match $expr {
            Ok(val) => val,
            Err(e) => {
                println!("Muxed ran in to a problem: {}", e);
                exit(1);
            }
        }
    })
);

static DISALLOWED_SHORTHAND_PROJECT_NAMES: [&str; 2] = ["new", "edit"];

static USAGE: &str = "
Usage:
    muxed [options] <project>
    muxed edit [options] <project>
    muxed new [options] <project>
    muxed snapshot [options] <project>
    muxed load [options] <project>
    muxed (-h | --help)
    muxed (-v | --version)

Flags:
    -d                  If you want to create a muxed session without connecting to it
    -f                  Overwrite existing file if one exists
    --debug             Prints debug information while executing (project opening only)
    -h, --help          Prints help information
    -v, --version       Prints version information

Options:
    -p <project_dir>    The directory your project config files live in. Defaults to ~/.muxed/
    -t <session>        The name of the running TMUX session to codify

Args:
    <project>           The name of your project to open

Subcommands:
    edit <project>                   Edit an existing project file
    new <project>                    To create a new project file
    snapshot -t <session> <project>  Capture a running session and create a config file for it
";

/// The main execution method.
/// Currently accepts a single option. The option represents a configuration
/// file in the same naming format. Given a project file name `projectName.yml`
/// in the `~/.muxed/` directory.
///
/// # Examples
///
/// ~/.muxed/projectName.yml
///
/// ```
/// root: ~/projects/muxed/
/// windows:
///     - cargo: "cargo build"
///     - vim: "vim ."
///     - git: ""
/// ```
///
/// You can run the command:
///
/// ```
/// $ ./muxed projectName
/// ```
pub fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    if args.flag_v {
        println!("Muxed {}", env!("CARGO_PKG_VERSION"));
        exit(0);
    };

    if args.cmd_new {
        try_or_err!(new::exec(args))
    } else if args.cmd_edit {
        try_or_err!(edit::exec(args))
    } else if args.cmd_snapshot {
        try_or_err!(snapshot::exec(args))
    } else if args.cmd_load {
        try_or_err!(load::exec(args))
    } else if DISALLOWED_SHORTHAND_PROJECT_NAMES.contains(&args.arg_project.as_ref()) {
        println!(
            "Tried to call sub-command '{}' without a project. Please specify a project name.",
            args.arg_project
        );
        exit(1);
    } else {
        try_or_err!(load::exec(args))
    }
}
<commit_msg>I like this style slightly better because it seperates out the explicit commands with any defaulting behavior<commit_after>//! Muxed. A tmux project manager with no runtime dependencies.
extern crate common;
extern crate docopt;
extern crate edit;
extern crate load;
extern crate new;
extern crate snapshot;

use common::args::Args;
use docopt::Docopt;
use std::env;
use std::process::exit;

#[macro_export]
macro_rules! try_or_err (
    ($expr: expr) => ({
        match $expr {
            Ok(val) => val,
            Err(e) => {
                println!("Muxed ran in to a problem: {}", e);
                exit(1);
            }
        }
    })
);

static DISALLOWED_SHORTHAND_PROJECT_NAMES: [&str; 2] = ["new", "edit"];

static USAGE: &str = "
Usage:
    muxed [options] <project>
    muxed edit [options] <project>
    muxed new [options] <project>
    muxed snapshot [options] <project>
    muxed load [options] <project>
    muxed (-h | --help)
    muxed (-v | --version)

Flags:
    -d                  If you want to create a muxed session without connecting to it
    -f                  Overwrite existing file if one exists
    --debug             Prints debug information while executing (project opening only)
    -h, --help          Prints help information
    -v, --version       Prints version information

Options:
    -p <project_dir>    The directory your project config files live in. Defaults to ~/.muxed/
    -t <session>        The name of the running TMUX session to codify

Args:
    <project>           The name of your project to open

Subcommands:
    edit <project>                   Edit an existing project file
    new <project>                    To create a new project file
    snapshot -t <session> <project>  Capture a running session and create a config file for it
";

/// The main execution method.
/// Currently accepts a single option. The option represents a configuration
/// file in the same naming format. Given a project file name `projectName.yml`
/// in the `~/.muxed/` directory.
///
/// # Examples
///
/// ~/.muxed/projectName.yml
///
/// ```
/// root: ~/projects/muxed/
/// windows:
///     - cargo: "cargo build"
///     - vim: "vim ."
///     - git: ""
/// ```
///
/// You can run the command:
///
/// ```
/// $ ./muxed projectName
/// ```
pub fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    if args.flag_v {
        println!("Muxed {}", env!("CARGO_PKG_VERSION"));
        exit(0);
    };

    if args.cmd_new {
        try_or_err!(new::exec(args))
    } else if args.cmd_edit {
        try_or_err!(edit::exec(args))
    } else if args.cmd_snapshot {
        try_or_err!(snapshot::exec(args))
    } else if args.cmd_load {
        try_or_err!(load::exec(args))
    } else {
        if DISALLOWED_SHORTHAND_PROJECT_NAMES.contains(&args.arg_project.as_ref()) {
            println!(
                "Tried to call sub-command '{}' without a project. Please specify a project name.",
                args.arg_project
            );
            exit(1);
        } else {
            try_or_err!(load::exec(args))
        }
    }
}
<|endoftext|>