<commit_before>#![feature(deque_extras)]
#![feature(box_syntax)]
#![feature(plugin)]
#![plugin(peg_syntax_ext)]

extern crate glob;

use std::collections::HashMap;
use std::fs::File;
use std::io::{stdout, Read, Write};
use std::env;
use std::process;
use std::thread;

use self::directory_stack::DirectoryStack;
use self::input_editor::readln;
use self::peg::{parse, Job};
use self::variables::Variables;
use self::history::History;
use self::flow_control::{FlowControl, is_flow_control_command, Statement};
use self::status::{SUCCESS, NO_SUCH_COMMAND, TERMINATED};

pub mod directory_stack;
pub mod to_num;
pub mod input_editor;
pub mod peg;
pub mod variables;
pub mod history;
pub mod flow_control;
pub mod status;

/// This struct will contain all of the data structures related to this
/// instance of the shell.
pub struct Shell {
    variables: Variables,
    flow_control: FlowControl,
    directory_stack: DirectoryStack,
    history: History,
}

impl Shell {
    /// Panics if DirectoryStack construction fails
    pub fn new() -> Self {
        Shell {
            variables: Variables::new(),
            flow_control: FlowControl::new(),
            directory_stack: DirectoryStack::new().expect(""),
            history: History::new(),
        }
    }

    pub fn print_prompt(&self) {
        self.print_prompt_prefix();
        match self.flow_control.current_statement {
            Statement::For(_, _) => self.print_for_prompt(),
            Statement::Default => self.print_default_prompt(),
        }
        if let Err(message) = stdout().flush() {
            println!("{}: failed to flush prompt to stdout", message);
        }

    }

    // TODO eventually this thing should be gone
    fn print_prompt_prefix(&self) {
        let prompt_prefix = self.flow_control.modes.iter().rev().fold(String::new(), |acc, mode| {
            acc +
            if mode.value {
                "+ "
            } else {
                "- "
            }
        });
        print!("{}", prompt_prefix);
    }

    fn print_for_prompt(&self) {
        print!("for> ");
    }

    fn print_default_prompt(&self) {
        let cwd = env::current_dir().ok().map_or("?".to_string(),
                                                 |ref p| p.to_str().unwrap_or("?").to_string());
        print!("ion:{}# ", cwd);
    }

    fn on_command(&mut self, command_string: &str, commands: &HashMap<&str, Command>) {
        self.history.add(command_string.to_string());

        let mut jobs = parse(command_string);

        // Execute commands
        for job in jobs.drain(..) {
            if self.flow_control.collecting_block {
                // TODO move this logic into "end" command
                if job.command == "end" {
                    self.flow_control.collecting_block = false;
                    let block_jobs: Vec<Job> = self.flow_control
                                                   .current_block
                                                   .jobs
                                                   .drain(..)
                                                   .collect();
                    let mut variable = String::new();
                    let mut values: Vec<String> = vec![];
                    if let Statement::For(ref var, ref vals) = self.flow_control.current_statement {
                        variable = var.clone();
                        values = vals.clone();
                    }
                    for value in values {
                        self.variables.set_var(&variable, &value);
                        for job in block_jobs.iter() {
                            self.run_job(job, commands);
                        }
                    }
                    self.flow_control.current_statement = Statement::Default;
                } else {
                    self.flow_control.current_block.jobs.push(job);
                }
            } else {
                if self.flow_control.skipping() && !is_flow_control_command(&job.command) {
                    continue;
                }
                self.run_job(&job, commands);
            }
        }
    }

    fn run_job(&mut self, job: &Job, commands: &HashMap<&str, Command>) {
        let mut job = self.variables.expand_job(job);
        job.expand_globs();
        let exit_status = if let Some(command) = commands.get(job.command.as_str()) {
            Some((*command.main)(job.args.as_slice(), self))
        } else {
            self.run_external_commmand(job)
        };
        if let Some(code) = exit_status {
            self.variables.set_var("?", &code.to_string());
        }
    }

    /// Returns an exit code if a command was run
    fn run_external_commmand(&mut self, job: Job) -> Option<i32> {
        if job.background {
            thread::spawn(move || {
                let mut command = Shell::build_command(&job);
                command.stdin(process::Stdio::null());
                if let Ok(mut child) = command.spawn() {
                    Shell::wait_and_get_status(&mut child, &job.command);
                }
            });
            None
        } else {
            let mut command = Shell::build_command(&job);
            match command.spawn() {
                Ok(mut child) => Some(Shell::wait_and_get_status(&mut child, &job.command)),
                Err(_) => {
                    println!("ion: command not found: {}", job.command);
                    Some(NO_SUCH_COMMAND)
                }
            }
        }
    }

    fn build_command(job: &Job) -> process::Command {
        let mut command = process::Command::new(&job.command);
        for i in 1..job.args.len() {
            if let Some(arg) = job.args.get(i) {
                command.arg(arg);
            }
        }
        command
    }

    // TODO don't pass in command and do printing outside this function
    fn wait_and_get_status(child: &mut process::Child, command: &str) -> i32 {
        match child.wait() {
            Ok(status) => {
                if let Some(code) = status.code() {
                    code
                } else {
                    println!("{}: child ended by signal", command);
                    TERMINATED
                }
            }
            Err(err) => {
                println!("{}: Failed to wait: {}", command, err);
                100 // TODO what should we return here?
            }
        }
    }
}

/// Structure which represents a Terminal's command.
/// This command structure contains a name, and the code which run the
/// functionnality associated to this one, with zero, one or several argument(s).
/// # Example
/// ```
/// let my_command = Command {
///     name: "my_command",
///     help: "Describe what my_command does followed by a newline showing usage",
///     main: box|args: &[String], &mut Shell| -> i32 {
///         println!("Say 'hello' to my command! :-D");
///     }
/// }
/// ```
pub struct Command {
    pub name: &'static str,
    pub help: &'static str,
    pub main: Box<Fn(&[String], &mut Shell) -> i32>,
}

impl Command {
    /// Return the map from command names to commands
    pub fn map() -> HashMap<&'static str, Self> {
        let mut commands: HashMap<&str, Self> = HashMap::new();

        commands.insert("cd",
                        Command {
                            name: "cd",
                            help: "Change the current directory\n    cd <path>",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.cd(args)
                            },
                        });

        commands.insert("dirs",
                        Command {
                            name: "dirs",
                            help: "Display the current directory stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.dirs(args)
                            },
                        });

        commands.insert("exit",
                        Command {
                            name: "exit",
                            help: "To exit the curent session",
                            main: box |args: &[String], _: &mut Shell| -> i32 {
                                if let Some(status) = args.get(1) {
                                    if let Ok(status) = status.parse::<i32>() {
                                        process::exit(status);
                                    }
                                }
                                // TODO should use exit status of previously run command, not 0
                                process::exit(0);
                            },
                        });

        commands.insert("let",
                        Command {
                            name: "let",
                            help: "View, set or unset variables",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.variables.let_(args)
                            },
                        });

        commands.insert("read",
                        Command {
                            name: "read",
                            help: "Read some variables\n    read <variable>",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.variables.read(args)
                            },
                        });

        commands.insert("pushd",
                        Command {
                            name: "pushd",
                            help: "Push a directory to the stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.pushd(args)
                            },
                        });

        commands.insert("popd",
                        Command {
                            name: "popd",
                            help: "Pop a directory from the stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.popd(args)
                            },
                        });

        commands.insert("history",
                        Command {
                            name: "history",
                            help: "Display a log of all commands previously executed",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.history.history(args)
                            },
                        });

        commands.insert("if",
                        Command {
                            name: "if",
                            help: "Conditionally execute code",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.if_(args)
                            },
                        });

        commands.insert("else",
                        Command {
                            name: "else",
                            help: "Execute code if a previous condition was false",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.else_(args)
                            },
                        });

        commands.insert("end",
                        Command {
                            name: "end",
                            help: "End a code block",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.end(args)
                            },
                        });

        commands.insert("for",
                        Command {
                            name: "for",
                            help: "Iterate through a list",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.for_(args)
                            },
                        });

        let command_helper: HashMap<&'static str, &'static str> = commands.iter()
                                                                          .map(|(k, v)| {
                                                                              (*k, v.help)
                                                                          })
                                                                          .collect();

        commands.insert("help",
                        Command {
                            name: "help",
                            help: "Display helpful information about a given command, or list \
                                   commands if none specified\n    help <command>",
                            main: box move |args: &[String], _: &mut Shell| -> i32 {
                                if let Some(command) = args.get(1) {
                                    if command_helper.contains_key(command.as_str()) {
                                        match command_helper.get(command.as_str()) {
                                            Some(help) => println!("{}", help),
                                            None => {
                                                println!("Command helper not found [run 'help']...")
                                            }
                                        }
                                    } else {
                                        println!("Command helper not found [run 'help']...");
                                    }
                                } else {
                                    for (command, _help) in command_helper.iter() {
                                        println!("{}", command);
                                    }
                                }
                                SUCCESS
                            },
                        });

        commands
    }
}

fn main() {
    let commands = Command::map();
    let mut shell = Shell::new();

    for arg in env::args().skip(1) {
        let mut command_list = String::new();
        if let Ok(mut file) = File::open(&arg) {
            if let Err(message) = file.read_to_string(&mut command_list) {
                println!("{}: Failed to read {}", message, arg);
            }
        }
        shell.on_command(&command_list, &commands);
        return;
    }

    loop {
        shell.print_prompt();

        if let Some(command) = readln() {
            let command = command.trim();
            if !command.is_empty() {
                shell.on_command(command, &commands);
            }
        } else {
            break;
        }
    }
}
<commit_msg>Use 'if let' Instead<commit_after>#![feature(deque_extras)]
#![feature(box_syntax)]
#![feature(plugin)]
#![plugin(peg_syntax_ext)]

extern crate glob;

use std::collections::HashMap;
use std::fs::File;
use std::io::{stdout, Read, Write};
use std::env;
use std::process;
use std::thread;

use self::directory_stack::DirectoryStack;
use self::input_editor::readln;
use self::peg::{parse, Job};
use self::variables::Variables;
use self::history::History;
use self::flow_control::{FlowControl, is_flow_control_command, Statement};
use self::status::{SUCCESS, NO_SUCH_COMMAND, TERMINATED};

pub mod directory_stack;
pub mod to_num;
pub mod input_editor;
pub mod peg;
pub mod variables;
pub mod history;
pub mod flow_control;
pub mod status;

/// This struct will contain all of the data structures related to this
/// instance of the shell.
pub struct Shell {
    variables: Variables,
    flow_control: FlowControl,
    directory_stack: DirectoryStack,
    history: History,
}

impl Shell {
    /// Panics if DirectoryStack construction fails
    pub fn new() -> Self {
        Shell {
            variables: Variables::new(),
            flow_control: FlowControl::new(),
            directory_stack: DirectoryStack::new().expect(""),
            history: History::new(),
        }
    }

    pub fn print_prompt(&self) {
        self.print_prompt_prefix();
        match self.flow_control.current_statement {
            Statement::For(_, _) => self.print_for_prompt(),
            Statement::Default => self.print_default_prompt(),
        }
        if let Err(message) = stdout().flush() {
            println!("{}: failed to flush prompt to stdout", message);
        }

    }

    // TODO eventually this thing should be gone
    fn print_prompt_prefix(&self) {
        let prompt_prefix = self.flow_control.modes.iter().rev().fold(String::new(), |acc, mode| {
            acc +
            if mode.value {
                "+ "
            } else {
                "- "
            }
        });
        print!("{}", prompt_prefix);
    }

    fn print_for_prompt(&self) {
        print!("for> ");
    }

    fn print_default_prompt(&self) {
        let cwd = env::current_dir().ok().map_or("?".to_string(),
                                                 |ref p| p.to_str().unwrap_or("?").to_string());
        print!("ion:{}# ", cwd);
    }

    fn on_command(&mut self, command_string: &str, commands: &HashMap<&str, Command>) {
        self.history.add(command_string.to_string());

        let mut jobs = parse(command_string);

        // Execute commands
        for job in jobs.drain(..) {
            if self.flow_control.collecting_block {
                // TODO move this logic into "end" command
                if job.command == "end" {
                    self.flow_control.collecting_block = false;
                    let block_jobs: Vec<Job> = self.flow_control
                                                   .current_block
                                                   .jobs
                                                   .drain(..)
                                                   .collect();
                    let mut variable = String::new();
                    let mut values: Vec<String> = vec![];
                    if let Statement::For(ref var, ref vals) = self.flow_control.current_statement {
                        variable = var.clone();
                        values = vals.clone();
                    }
                    for value in values {
                        self.variables.set_var(&variable, &value);
                        for job in block_jobs.iter() {
                            self.run_job(job, commands);
                        }
                    }
                    self.flow_control.current_statement = Statement::Default;
                } else {
                    self.flow_control.current_block.jobs.push(job);
                }
            } else {
                if self.flow_control.skipping() && !is_flow_control_command(&job.command) {
                    continue;
                }
                self.run_job(&job, commands);
            }
        }
    }

    fn run_job(&mut self, job: &Job, commands: &HashMap<&str, Command>) {
        let mut job = self.variables.expand_job(job);
        job.expand_globs();
        let exit_status = if let Some(command) = commands.get(job.command.as_str()) {
            Some((*command.main)(job.args.as_slice(), self))
        } else {
            self.run_external_commmand(job)
        };
        if let Some(code) = exit_status {
            self.variables.set_var("?", &code.to_string());
        }
    }

    /// Returns an exit code if a command was run
    fn run_external_commmand(&mut self, job: Job) -> Option<i32> {
        if job.background {
            thread::spawn(move || {
                let mut command = Shell::build_command(&job);
                command.stdin(process::Stdio::null());
                if let Ok(mut child) = command.spawn() {
                    Shell::wait_and_get_status(&mut child, &job.command);
                }
            });
            None
        } else {
            if let Ok(mut child) = Shell::build_command(&job).spawn() {
                Some(Shell::wait_and_get_status(&mut child, &job.command))
            } else {
                println!("ion: command not found: {}", job.command);
                Some(NO_SUCH_COMMAND)
            }
        }
    }

    fn build_command(job: &Job) -> process::Command {
        let mut command = process::Command::new(&job.command);
        for i in 1..job.args.len() {
            if let Some(arg) = job.args.get(i) {
                command.arg(arg);
            }
        }
        command
    }

    // TODO don't pass in command and do printing outside this function
    fn wait_and_get_status(child: &mut process::Child, command: &str) -> i32 {
        match child.wait() {
            Ok(status) => {
                if let Some(code) = status.code() {
                    code
                } else {
                    println!("{}: child ended by signal", command);
                    TERMINATED
                }
            }
            Err(err) => {
                println!("{}: Failed to wait: {}", command, err);
                100 // TODO what should we return here?
            }
        }
    }
}

/// Structure which represents a Terminal's command.
/// This command structure contains a name, and the code which run the
/// functionnality associated to this one, with zero, one or several argument(s).
/// # Example
/// ```
/// let my_command = Command {
///     name: "my_command",
///     help: "Describe what my_command does followed by a newline showing usage",
///     main: box|args: &[String], &mut Shell| -> i32 {
///         println!("Say 'hello' to my command! :-D");
///     }
/// }
/// ```
pub struct Command {
    pub name: &'static str,
    pub help: &'static str,
    pub main: Box<Fn(&[String], &mut Shell) -> i32>,
}

impl Command {
    /// Return the map from command names to commands
    pub fn map() -> HashMap<&'static str, Self> {
        let mut commands: HashMap<&str, Self> = HashMap::new();

        commands.insert("cd",
                        Command {
                            name: "cd",
                            help: "Change the current directory\n    cd <path>",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.cd(args)
                            },
                        });

        commands.insert("dirs",
                        Command {
                            name: "dirs",
                            help: "Display the current directory stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.dirs(args)
                            },
                        });

        commands.insert("exit",
                        Command {
                            name: "exit",
                            help: "To exit the curent session",
                            main: box |args: &[String], _: &mut Shell| -> i32 {
                                if let Some(status) = args.get(1) {
                                    if let Ok(status) = status.parse::<i32>() {
                                        process::exit(status);
                                    }
                                }
                                // TODO should use exit status of previously run command, not 0
                                process::exit(0);
                            },
                        });

        commands.insert("let",
                        Command {
                            name: "let",
                            help: "View, set or unset variables",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.variables.let_(args)
                            },
                        });

        commands.insert("read",
                        Command {
                            name: "read",
                            help: "Read some variables\n    read <variable>",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.variables.read(args)
                            },
                        });

        commands.insert("pushd",
                        Command {
                            name: "pushd",
                            help: "Push a directory to the stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.pushd(args)
                            },
                        });

        commands.insert("popd",
                        Command {
                            name: "popd",
                            help: "Pop a directory from the stack",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.directory_stack.popd(args)
                            },
                        });

        commands.insert("history",
                        Command {
                            name: "history",
                            help: "Display a log of all commands previously executed",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.history.history(args)
                            },
                        });

        commands.insert("if",
                        Command {
                            name: "if",
                            help: "Conditionally execute code",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.if_(args)
                            },
                        });

        commands.insert("else",
                        Command {
                            name: "else",
                            help: "Execute code if a previous condition was false",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.else_(args)
                            },
                        });

        commands.insert("end",
                        Command {
                            name: "end",
                            help: "End a code block",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.end(args)
                            },
                        });

        commands.insert("for",
                        Command {
                            name: "for",
                            help: "Iterate through a list",
                            main: box |args: &[String], shell: &mut Shell| -> i32 {
                                shell.flow_control.for_(args)
                            },
                        });

        let command_helper: HashMap<&'static str, &'static str> = commands.iter()
                                                                          .map(|(k, v)| {
                                                                              (*k, v.help)
                                                                          })
                                                                          .collect();

        commands.insert("help",
                        Command {
                            name: "help",
                            help: "Display helpful information about a given command, or list \
                                   commands if none specified\n    help <command>",
                            main: box move |args: &[String], _: &mut Shell| -> i32 {
                                if let Some(command) = args.get(1) {
                                    if command_helper.contains_key(command.as_str()) {
                                        match command_helper.get(command.as_str()) {
                                            Some(help) => println!("{}", help),
                                            None => {
                                                println!("Command helper not found [run 'help']...")
                                            }
                                        }
                                    } else {
                                        println!("Command helper not found [run 'help']...");
                                    }
                                } else {
                                    for (command, _help) in command_helper.iter() {
                                        println!("{}", command);
                                    }
                                }
                                SUCCESS
                            },
                        });

        commands
    }
}

fn main() {
    let commands = Command::map();
    let mut shell = Shell::new();

    for arg in env::args().skip(1) {
        let mut command_list = String::new();
        if let Ok(mut file) = File::open(&arg) {
            if let Err(message) = file.read_to_string(&mut command_list) {
                println!("{}: Failed to read {}", message, arg);
            }
        }
        shell.on_command(&command_list, &commands);
        return;
    }

    loop {
        shell.print_prompt();

        if let Some(command) = readln() {
            let command = command.trim();
            if !command.is_empty() {
                shell.on_command(command, &commands);
            }
        } else {
            break;
        }
    }
}
<|endoftext|>


<commit_before>extern crate regex;
extern crate clap;
extern crate walkdir;
extern crate ansi_term;

use std::io::Write;

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

use clap::{Arg, App, AppSettings};
use regex::Regex;
use walkdir::WalkDir;
use ansi_term::Colour::Blue;

use std::env;
use std::path::PathBuf;
use std::ops::Deref;

fn find(rgx: Regex, dirs: Vec<PathBuf>, paint: bool) {
    let painter;
    let replacement = if paint {
        painter = Blue.paint("$search");
        painter.deref()
    } else {
        "$search"
    };

    for d in dirs.iter() {
        let wkd = WalkDir::new(d);

        for f in wkd.into_iter() {
            let entry;
            match f {
                Ok(e) => entry = e,
                Err(err) => {
                    println_stderr!("{}", err);
                    continue;
                }
            }

            // paths are ascii so should always be valid unicode
            // the edge case is super rare so just ignore it
            let s = &entry.path().to_string_lossy();

            if rgx.is_match(s) {
                let display = rgx.replace_all(s, replacement);
                println!("{}", display);
            }
        }
    }
}

fn main() {
    let matches = App::new("fnd, a simpler way to find files")
        .version("0.2.0")
        .author("Mathew Robinson <mrobinson@praelatus.io>")
        .about("Finds files")
        .setting(AppSettings::TrailingVarArg)
        .arg(Arg::with_name("color")
             .long("no_color")
             .short("nc")
             .help("When specified will not highlight matches with ansi \
                    term color codes this is useful when piping output"))
        .arg(Arg::with_name("REGEX")
             .required(true)
             .index(1)
             .help("The REGEX to search for, defaults to fuzzy finding"))
        .arg(Arg::with_name("DIRS")
            .multiple(true)
            .help("Where to search for SEARCH")
            .default_value("."))
        .get_matches();

    let search = matches.value_of("REGEX").unwrap();
    let dirs: Vec<&str> = matches.values_of("DIRS").unwrap().collect();

    let mut d: Vec<PathBuf> = dirs.iter()
        .map(|x| {
            if *x == "." {
                return env::current_dir().unwrap().to_path_buf();
            }

            PathBuf::from(x)
        })
        .collect();

    d = d.iter()
        .map(|x| std::fs::canonicalize(x).unwrap())
        .collect();

    let mut re = "(?P<search>".to_string();
    re.push_str(search);
    re.push_str(")");

    let rgx = Regex::new(&re).unwrap();

    find(rgx, d, !matches.is_present("color"));
}
<commit_msg>make painting work again<commit_after>extern crate regex;
extern crate clap;
extern crate walkdir;
extern crate ansi_term;

use std::io::Write;

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

use clap::{Arg, App, AppSettings};
use regex::Regex;
use walkdir::WalkDir;
use ansi_term::Colour::Blue;

use std::env;
use std::path::PathBuf;

fn find(rgx: Regex, dirs: Vec<PathBuf>, paint: bool) {
    let painter;
    let replacement = if paint {
        painter = Blue.paint("$search");
        format!("{}", painter)
    } else {
        "$search".to_string()
    };

    for d in dirs.iter() {
        let wkd = WalkDir::new(d);

        for f in wkd.into_iter() {
            let entry;
            match f {
                Ok(e) => entry = e,
                Err(err) => {
                    println_stderr!("{}", err);
                    continue;
                }
            }

            // paths are ascii so should always be valid unicode
            // the edge case is super rare so just ignore it
            let s = &entry.path().to_string_lossy();

            if rgx.is_match(s) {
                let display = rgx.replace_all(s, replacement.as_str());
                println!("{}", display);
            }
        }
    }
}

fn main() {
    let matches = App::new("fnd, a simpler way to find files")
        .version("0.2.0")
        .author("Mathew Robinson <mrobinson@praelatus.io>")
        .about("Finds files")
        .setting(AppSettings::TrailingVarArg)
        .arg(Arg::with_name("color")
             .long("no_color")
             .short("nc")
             .help("When specified will not highlight matches with ansi \
                    term color codes this is useful when piping output"))
        .arg(Arg::with_name("REGEX")
             .required(true)
             .index(1)
             .help("The REGEX to search for, defaults to fuzzy finding"))
        .arg(Arg::with_name("DIRS")
            .multiple(true)
            .help("Where to search for SEARCH")
            .default_value("."))
        .get_matches();

    let search = matches.value_of("REGEX").unwrap();
    let dirs: Vec<&str> = matches.values_of("DIRS").unwrap().collect();

    let mut d: Vec<PathBuf> = dirs.iter()
        .map(|x| {
            if *x == "." {
                return env::current_dir().unwrap().to_path_buf();
            }

            PathBuf::from(x)
        })
        .collect();

    d = d.iter()
        .map(|x| std::fs::canonicalize(x).unwrap())
        .collect();

    let mut re = "(?P<search>".to_string();
    re.push_str(search);
    re.push_str(")");

    let rgx = Regex::new(&re).unwrap();

    find(rgx, d, !matches.is_present("color"));
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//! Command line tool to exercise pulldown-cmark.

extern crate getopts;

extern crate pulldown_cmark;

use pulldown_cmark::Parser;
use pulldown_cmark::{Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
use pulldown_cmark::html;

use std::env;
use std::io;
use std::io::{Read, Write};
use std::path::Path;
use std::fs::File;

fn render_html(text: &str, opts: Options) -> String {
    let mut s = String::with_capacity(text.len() * 3 / 2);
    let p = Parser::new_ext(text, opts);
    html::push_html(&mut s, p);
    s
}

fn dry_run(text:&str, opts: Options) {
    let p = Parser::new_ext(text, opts);
    /*
    let events = p.collect::<Vec<_>>();
    let count = events.len();
    */
    let count = p.count();
    println!("{} events", count);
}

fn print_events(text: &str, opts: Options) {
    let mut p = Parser::new_ext(text, opts);
    loop {
        print!("{}: ", p.get_offset());
        if let Some(event) = p.next() {
            println!("{:?}", event);
        } else {
            break;
        }
    }
    println!("EOF");
}

fn read_file(filename: &str) -> String {
    let path = Path::new(filename);
    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", path.display(), why),
        Ok(file) => file
    };
    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Err(why) => panic!("couldn't open {}: {}", path.display(), why),
        Ok(_) => s
    }
}

fn find_test_delim(text: &str) -> Option<usize> {
    if text.starts_with(".\n") { Some(0) }
    else { text.find("\n.\n").map(|pos| pos + 1) }
}

fn run_spec(spec_text: &str, args: &[String], opts: Options) {
    //println!("spec length={}, args={:?}", spec_text.len(), args);
    let (first, last) = if args.is_empty() {
        (None, None)
    } else {
        let mut iter = args[0].split("..");
        let first = iter.next().and_then(|s| s.parse().ok());
        let last = match iter.next() {
            Some(s) => s.parse().ok(),
            None => first
        };
        (first, last)
    };

    let mut test_number = 1;
    let mut tests_failed = 0;
    let mut tests_run = 0;
    let mut line_count = 0;
    let mut tail = spec_text;
    loop {
        let rest = match find_test_delim(tail) {
            Some(pos) => &tail[pos + 2 ..],
            None => break
        };
        let (source, rest) = match find_test_delim(rest) {
            Some(pos) => (&rest[..pos], &rest[pos + 2 ..]),
            None => break
        };
        let (html, rest) = match find_test_delim(rest) {
            Some(pos) => (&rest[.. pos], &rest[pos + 2 ..]),
            None => break
        };
        if first.map_or(true, |fst| fst <= test_number) &&
                last.map_or(true, |lst| test_number <= lst) {
            if tests_run == 0 || line_count == 0 || (test_number % 10 == 0) {
                if line_count > 30 {
                    println!("");
                    line_count = 0;
                } else if line_count > 0 {
                    print!(" ");
                }
                print!("[{}]", test_number);
            } else if line_count > 0 && (test_number % 10) == 5 {
                print!(" ");
            }
            let our_html = render_html(&source.replace("→", "\t").replace("\n", "\r\n"), opts);
            if our_html == html {
                print!(".");
            } else {
                if tests_failed == 0 {
                    print!("FAIL {}:\n---input---\n{}\n---wanted---\n{}\n---got---\n{}",
                        test_number, source, html, our_html);
                } else {
                    print!("X");
                }
                tests_failed += 1;
            }
            let _ = io::stdout().flush();
            tests_run += 1;
            line_count += 1;
        }
        tail = rest;
        test_number += 1;
    }
    println!("\n{}/{} tests passed", tests_run - tests_failed, tests_run)
}

pub fn main() {
    let args: Vec<_> = env::args().collect();
    let mut opts = getopts::Options::new();
    opts.optflag("d", "dry-run", "dry run, produce no output");
    opts.optflag("e", "events", "print event sequence instead of rendering");
    opts.optflag("T", "enable-tables", "enable GitHub-style tables");
    opts.optflag("F", "enable-footnotes", "enable Hoedown-style footnotes");
    opts.optopt("s", "spec", "run tests from spec file", "FILE");
    opts.optopt("b", "bench", "run benchmark", "FILE");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string())
    };
    let mut opts = Options::empty();
    if matches.opt_present("enable-tables") {
        opts.insert(OPTION_ENABLE_TABLES);
    }
    if matches.opt_present("enable-footnotes") {
        opts.insert(OPTION_ENABLE_FOOTNOTES);
    }
    if let Some(filename) = matches.opt_str("spec") {
        run_spec(&read_file(&filename), &matches.free, opts);
    } else if let Some(filename) = matches.opt_str("bench") {
        let inp = read_file(&filename);
        for _ in 0..1000 {
            let _ = render_html(&inp, opts);
        }
    } else {
        let mut input = String::new();
        if let Err(why) = io::stdin().read_to_string(&mut input) {
            panic!("couldn't read from stdin: {}", why)
        }
        if matches.opt_present("events") {
            print_events(&input, opts);
        } else if matches.opt_present("dry-run") {
            dry_run(&input, opts);
        } else {
            print!("{}", render_html(&input, opts));
        }
    }
}
<commit_msg>Add `help` option for printing usage information<commit_after>// Copyright 2015 Google Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

//! Command line tool to exercise pulldown-cmark.

extern crate getopts;

extern crate pulldown_cmark;

use pulldown_cmark::Parser;
use pulldown_cmark::{Options, OPTION_ENABLE_TABLES, OPTION_ENABLE_FOOTNOTES};
use pulldown_cmark::html;

use std::env;
use std::io;
use std::io::{Read, Write};
use std::path::Path;
use std::fs::File;

fn render_html(text: &str, opts: Options) -> String {
    let mut s = String::with_capacity(text.len() * 3 / 2);
    let p = Parser::new_ext(text, opts);
    html::push_html(&mut s, p);
    s
}

fn dry_run(text:&str, opts: Options) {
    let p = Parser::new_ext(text, opts);
    /*
    let events = p.collect::<Vec<_>>();
    let count = events.len();
    */
    let count = p.count();
    println!("{} events", count);
}

fn print_events(text: &str, opts: Options) {
    let mut p = Parser::new_ext(text, opts);
    loop {
        print!("{}: ", p.get_offset());
        if let Some(event) = p.next() {
            println!("{:?}", event);
        } else {
            break;
        }
    }
    println!("EOF");
}

fn read_file(filename: &str) -> String {
    let path = Path::new(filename);
    let mut file = match File::open(&path) {
        Err(why) => panic!("couldn't open {}: {}", path.display(), why),
        Ok(file) => file
    };
    let mut s = String::new();
    match file.read_to_string(&mut s) {
        Err(why) => panic!("couldn't open {}: {}", path.display(), why),
        Ok(_) => s
    }
}

fn find_test_delim(text: &str) -> Option<usize> {
    if text.starts_with(".\n") { Some(0) }
    else { text.find("\n.\n").map(|pos| pos + 1) }
}

fn run_spec(spec_text: &str, args: &[String], opts: Options) {
    //println!("spec length={}, args={:?}", spec_text.len(), args);
    let (first, last) = if args.is_empty() {
        (None, None)
    } else {
        let mut iter = args[0].split("..");
        let first = iter.next().and_then(|s| s.parse().ok());
        let last = match iter.next() {
            Some(s) => s.parse().ok(),
            None => first
        };
        (first, last)
    };

    let mut test_number = 1;
    let mut tests_failed = 0;
    let mut tests_run = 0;
    let mut line_count = 0;
    let mut tail = spec_text;
    loop {
        let rest = match find_test_delim(tail) {
            Some(pos) => &tail[pos + 2 ..],
            None => break
        };
        let (source, rest) = match find_test_delim(rest) {
            Some(pos) => (&rest[..pos], &rest[pos + 2 ..]),
            None => break
        };
        let (html, rest) = match find_test_delim(rest) {
            Some(pos) => (&rest[.. pos], &rest[pos + 2 ..]),
            None => break
        };
        if first.map_or(true, |fst| fst <= test_number) &&
                last.map_or(true, |lst| test_number <= lst) {
            if tests_run == 0 || line_count == 0 || (test_number % 10 == 0) {
                if line_count > 30 {
                    println!("");
                    line_count = 0;
                } else if line_count > 0 {
                    print!(" ");
                }
                print!("[{}]", test_number);
            } else if line_count > 0 && (test_number % 10) == 5 {
                print!(" ");
            }
            let our_html = render_html(&source.replace("→", "\t").replace("\n", "\r\n"), opts);
            if our_html == html {
                print!(".");
            } else {
                if tests_failed == 0 {
                    print!("FAIL {}:\n---input---\n{}\n---wanted---\n{}\n---got---\n{}",
                        test_number, source, html, our_html);
                } else {
                    print!("X");
                }
                tests_failed += 1;
            }
            let _ = io::stdout().flush();
            tests_run += 1;
            line_count += 1;
        }
        tail = rest;
        test_number += 1;
    }
    println!("\n{}/{} tests passed", tests_run - tests_failed, tests_run)
}

fn brief<ProgramName>(program: ProgramName) -> String
        where ProgramName: std::fmt::Display {
    return format!("Usage: {} FILE [options]", program);
}

pub fn main() {
    let args: Vec<_> = env::args().collect();
    let mut opts = getopts::Options::new();
    opts.optflag("h", "help", "this help message");
    opts.optflag("d", "dry-run", "dry run, produce no output");
    opts.optflag("e", "events", "print event sequence instead of rendering");
    opts.optflag("T", "enable-tables", "enable GitHub-style tables");
    opts.optflag("F", "enable-footnotes", "enable Hoedown-style footnotes");
    opts.optopt("s", "spec", "run tests from spec file", "FILE");
    opts.optopt("b", "bench", "run benchmark", "FILE");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string())
    };
    if matches.opt_present("help") {
        println!("{}", opts.usage(&brief(&args[0])));
        return;
    }
    let mut opts = Options::empty();
    if matches.opt_present("enable-tables") {
        opts.insert(OPTION_ENABLE_TABLES);
    }
    if matches.opt_present("enable-footnotes") {
        opts.insert(OPTION_ENABLE_FOOTNOTES);
    }
    if let Some(filename) = matches.opt_str("spec") {
        run_spec(&read_file(&filename), &matches.free, opts);
    } else if let Some(filename) = matches.opt_str("bench") {
        let inp = read_file(&filename);
        for _ in 0..1000 {
            let _ = render_html(&inp, opts);
        }
    } else {
        let mut input = String::new();
        if let Err(why) = io::stdin().read_to_string(&mut input) {
            panic!("couldn't read from stdin: {}", why)
        }
        if matches.opt_present("events") {
            print_events(&input, opts);
        } else if matches.opt_present("dry-run") {
            dry_run(&input, opts);
        } else {
            print!("{}", render_html(&input, opts));
        }
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> (Option<String>, Option<String>) {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");

    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            (Some(user), Some(repo_name))

            //We'll do that separately
            // if github::is_github_url(&url) {
            //     let gh_token = env::var("GH_TOKEN")
            //         .unwrap_or_else(|err| print_exit!("GH_TOKEN not set: {:?}", err));
            //     config_builder.gh_token(gh_token);
            // }

            //We'll do that separately
            // let cargo_token = env::var("CARGO_TOKEN")
            //     .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

            // config_builder.cargo_token(cargo_token);
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            (None, None)
        }
    }
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    let (user, repo) = get_user_and_repo(&repository_path);
    if user.is_some() {
        config_builder.user(user.unwrap());
    }
    if repo.is_some() {
        config_builder.user(repo.unwrap());
    }
}
<commit_msg>refactor: Retrieve GitHub token from ENV variable<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;
mod utils;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use travis_after_all::Build;
use utils::user_repo_from_url;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w W, --write=W        Write changes to files (default: yes if CI is set, otherwise no).
  -r R, --release=R      Create release on GitHub and publish on crates.io (only in write mode) [default: yes].
  -b B, --branch=B       The branch on which releases should happen. [default: master].
";

const COMMITTER_ERROR_MESSAGE: &'static str = r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr($fmt);
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: Option<String>,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn push_to_github(config: &config::Config, tag_name: &str) {
    logger::stdout("Pushing new commit and tag");
    git::push(&config, &tag_name)
        .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

    logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
    thread::sleep(Duration::from_secs(1));
}

fn release_on_github(config: &config::Config, tag_message: &str, tag_name: &str) {
    if github::can_release(&config) {
        logger::stdout("Creating GitHub release");
        github::release(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));
    } else {
        logger::stdout("Project not hosted on GitHub. Skipping release step");
    }
}

fn release_on_cratesio(config: &config::Config) {
    logger::stdout("Publishing crate on crates.io");
    if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
        print_exit!("Failed to publish on crates.io");
    }
}

fn generate_changelog(repository_path: &str, version: &Version, new_version: &String) -> String {
    logger::stdout(format!("New version would be: {}", new_version));
    logger::stdout("Would write the following Changelog:");
    match changelog::generate(repository_path, &version.to_string(), new_version) {
        Ok(log) => log,
        Err(err) => {
            logger::stderr(format!("Generating Changelog failed: {:?}", err));
            process::exit(1)
        }
    }
}

fn write_changelog(repository_path: &str, version: &Version, new_version: &str) {
    logger::stdout("Writing Changelog");
    changelog::write(repository_path, &version.to_string(), &new_version)
        .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));
}

fn print_changelog(changelog: &str) {
    logger::stdout("====================================");
    logger::stdout(changelog);
    logger::stdout("====================================");
    logger::stdout("Would create annotated git tag");
}

fn package_crate(config: &config::Config, repository_path: &str, new_version: &str) {
    if config.release_mode {
        logger::stdout("Updating lockfile");
        if !cargo::update_lockfile(repository_path) {
            print_exit!("`cargo fetch` failed. See above for the cargo error message.");
        }
    }

    git::commit_files(&config, &new_version)
        .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

    logger::stdout("Package crate");
    if !cargo::package(repository_path) {
        print_exit!("`cargo package` failed. See above for the cargo error message.");
    }
}

fn get_repo(repository_path: &str) -> git2::Repository {
    match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    }
}

fn get_repository_path(args: &Args) -> String {
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is
                                                            not a directory"));
    let repo_path = path.to_str().unwrap_or_else(|| print_exit!("Path is not valid unicode"));
    repo_path.to_string()
}

fn get_signature<'a>(repository_path: String) -> git2::Signature<'a> {
    let repo = get_repo(&repository_path);
    let signature = match git::get_signature(&repo) {
        Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(COMMITTER_ERROR_MESSAGE);
                process::exit(1);
            }
    };

    signature.to_owned()
}

fn get_user_and_repo(repository_path: &str) -> (Option<String>, Option<String>) {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            let (user, repo_name) = user_repo_from_url(&url)
                .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));

            (Some(user), Some(repo_name))

            //We'll do that separately
            // let cargo_token = env::var("CARGO_TOKEN")
            //     .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

            // config_builder.cargo_token(cargo_token);
        },
        Err(err) => {
            logger::warn(format!("Could not determine the origin remote url: {:?}", err));
            logger::warn("semantic-rs can't push changes or create a release on GitHub");
            (None, None)
        }
    }
}

fn get_github_token(repository_path: &str) -> Option<String> {
    let repo = get_repo(repository_path);
    let remote_or_none = repo.find_remote("origin");
    match remote_or_none {
        Ok(remote) => {
            let url = remote.url().expect("Remote URL is not valid UTF-8").to_owned();
            if github::is_github_url(&url) {
                match env::var("GH_TOKEN") {
                    Ok(token) => Some(token),
                    Err(err) => {
                        logger::warn(format!("GH_TOKEN not set: {:?}", err));
                        None
                    }
                }
            } else {
                None
            }
        },
        Err(_) => None
    }
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");
    println!("semantic.rs 🚀");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut config_builder = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    // If write mode is requested OR denied,
    // adhere to the user's wish,
    // otherwise we decide based on whether we are running in CI.
    let write_mode = match args.flag_write {
        None => ci_env_set(),
        Some(ref flag) => string_to_bool(flag)
    };

    // We can only release, if we are allowed to write
    let release_mode = write_mode && string_to_bool(&args.flag_release);
    let repository_path = get_repository_path(&args);

    config_builder.write(write_mode);
    config_builder.release(release_mode);
    config_builder.branch(args.flag_branch.clone());
    config_builder.repository_path(repository_path.clone());
    config_builder.signature(get_signature(repository_path.clone()));
    let (user, repo) = get_user_and_repo(&repository_path);
    if user.is_some() {
        config_builder.user(user.unwrap());
    }
    if repo.is_some() {
        config_builder.user(repo.unwrap());
    }
    let gh_token = get_github_token(&repository_path);
    if gh_token.is_some() {
        config_builder.gh_token(gh_token.unwrap());
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
extern crate github;
#[macro_use]
extern crate lazy_static;
extern crate regex;

use irc::client::prelude::*;
use regex::Regex;

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

    // This could be in a JSON config, but then we need to figure out how
    // to find that JSON config
    let irc_config: Config = Config {
        owners: Some(vec![format!("dbaron")]),
        nickname: Some(format!("wgmeeting-github-bot")),
        alt_nicks: Some(vec![format!("wgmeeting-github-bot-"),
                             format!("wgmeeting-github-bot--")]),
        username: Some(format!("dbaron-gh-bot")),
        realname: Some(format!("Bot to add meeting minutes to github issues.")),
        server: Some(format!("irc.w3.org")),
        port: Some(6667),
        use_ssl: Some(false),
        encoding: Some(format!("UTF-8")),
        channels: Some(vec![format!("#cssbottest")]),
        user_info: Some(format!("Bot to add meeting minutes to github issues.")),
        // FIXME: why doesn't this work as documented?
        //source: Some(format!("https://github.com/dbaron/wgmeeting-github-ircbot")),
        ..Default::default()
    };

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

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

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let mynick = server.current_nickname();
                        if target == mynick {
                            handle_bot_command(&server, msg, source, None)
                        } else if target.starts_with('#') {
                            let source_ = String::from(*source);
                            let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                                ChannelLine {
                                    source: source_,
                                    is_action: true,
                                    message: String::from(&msg[8..msg.len() - 1]),
                                }
                            } else {
                                ChannelLine {
                                    source: source_,
                                    is_action: false,
                                    message: msg.clone(),
                                }
                            };

                            // FIXME: This needs to handle requests in /me
                            match check_command_in_channel(mynick, msg) {
                                Some(ref command) => {
                                    handle_bot_command(&server, command, target, Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            _ => (),
        }
    }
}

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

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

    let send_line = |response_username: Option<&str>, line: &str| {
        let adjusted_line = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

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

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

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

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

struct ChannelData {
    current_topic: Option<TopicData>,
}

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

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

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

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

impl ChannelData {
    fn new() -> ChannelData {
        ChannelData { current_topic: None }
    }

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

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

                data.lines.push(line);

                response
            }
        }
    }

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

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

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        // FIXME: Do something with the data rather than throwing it away!
        self.current_topic = None;
    }
}

fn extract_github_url(message: &str) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
        static ref ALLOWED_REPOS: [String; 1] = [format!("dbaron/wgmeeting-github-ircbot")];
    }
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in ALLOWED_REPOS.into_iter() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}
<commit_msg>Send the data to another thread in order to post to github.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
extern crate github;
#[macro_use]
extern crate lazy_static;
extern crate regex;

use irc::client::prelude::*;
use regex::Regex;
use std::thread;

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

    // This could be in a JSON config, but then we need to figure out how
    // to find that JSON config
    let irc_config: Config = Config {
        owners: Some(vec![format!("dbaron")]),
        nickname: Some(format!("wgmeeting-github-bot")),
        alt_nicks: Some(vec![format!("wgmeeting-github-bot-"),
                             format!("wgmeeting-github-bot--")]),
        username: Some(format!("dbaron-gh-bot")),
        realname: Some(format!("Bot to add meeting minutes to github issues.")),
        server: Some(format!("irc.w3.org")),
        port: Some(6667),
        use_ssl: Some(false),
        encoding: Some(format!("UTF-8")),
        channels: Some(vec![format!("#cssbottest")]),
        user_info: Some(format!("Bot to add meeting minutes to github issues.")),
        // FIXME: why doesn't this work as documented?
        //source: Some(format!("https://github.com/dbaron/wgmeeting-github-ircbot")),
        ..Default::default()
    };

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

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

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        // FIXME: trailing \n
                        warn!("PRIVMSG without a source! {}", message);
                    }
                    Some(ref source) => {
                        let mynick = server.current_nickname();
                        if target == mynick {
                            handle_bot_command(&server, msg, source, None)
                        } else if target.starts_with('#') {
                            let source_ = String::from(*source);
                            let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                                ChannelLine {
                                    source: source_,
                                    is_action: true,
                                    message: String::from(&msg[8..msg.len() - 1]),
                                }
                            } else {
                                ChannelLine {
                                    source: source_,
                                    is_action: false,
                                    message: msg.clone(),
                                }
                            };

                            // FIXME: This needs to handle requests in /me
                            match check_command_in_channel(mynick, msg) {
                                Some(ref command) => {
                                    handle_bot_command(&server, command, target, Some(source))
                                }
                                None => {
                                    if let Some(response) = channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            // FIXME: trailing \n
                            warn!("UNEXPECTED TARGET {} in message {}", target, message);
                        }
                    }
                }
            }
            _ => (),
        }
    }
}

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

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

    let send_line = |response_username: Option<&str>, line: &str| {
        let adjusted_line = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

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

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

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

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

struct ChannelData {
    current_topic: Option<TopicData>,
}

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

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

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

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

impl ChannelData {
    fn new() -> ChannelData {
        ChannelData { current_topic: None }
    }

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

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

                data.lines.push(line);

                response
            }
        }
    }

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

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

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

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

struct GithubCommentTask {
    data: TopicData,
}

impl GithubCommentTask {
    fn new(data_: TopicData) -> GithubCommentTask {
        GithubCommentTask { data: data_ }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(& self) {
        // FIXME: Post a comment in github.
        unimplemented!();
    }
}
<|endoftext|>


<commit_before>extern crate argparse;
extern crate rand;

use std::io::{self, BufRead, BufReader};
use std::fs::File;
use std::path::Path;
use std::cmp;
use rand::{thread_rng, Rng};
use argparse::{ArgumentParser, Store, StoreTrue};

fn main() {
	let mut filepath: String = String::new();
    let mut n: usize = 1;
    let mut deny_duplicates: bool = false;
	{
		let mut parser = ArgumentParser::new();
		parser.refer(&mut n).add_option(&["-n"], Store, "number of choice");
		parser.refer(&mut deny_duplicates).add_option(&["-d"], StoreTrue, "deny duplicates");
        parser.refer(&mut filepath).add_argument("file", Store, "source filepath");
		parser.parse_args_or_exit();
	}

    let items = match if filepath.len() > 0 { read_from_file(filepath.as_str()) }
                        else { read_from_stdin() } {
        Ok(items) => items,
        Err(e) => panic!(e.to_string())
    };

    if deny_duplicates {
        pickup_deny_duplicates(items, n);
    } else {
        pickup(items, n);
    }
}

fn read_from_stdin() -> io::Result<Vec<String>> {
    let stdin = io::stdin();
    let mut reader = stdin.lock();

    let mut items : Vec<String> = Vec::new();
    let mut buffer = String::new();
    while reader.read_line(&mut buffer)? > 0 {
        items.push(buffer.trim().to_string());
        buffer.clear();
    }
    return Ok(items);
}

fn read_from_file(filepath: &str) -> io::Result<Vec<String>> {
	let path = Path::new(filepath);
	let file = File::open(&path)?;
    let mut reader = BufReader::new(file);

    let mut items : Vec<String> = Vec::new();
    let mut buffer = String::new();
    while reader.read_line(&mut buffer)? > 0 {
        items.push(buffer.trim().to_string());
        buffer.clear();
    }
    return Ok(items);
}

fn pickup(items: Vec<String>, n: usize) {
    if items.len() == 0 || n == 0 {
        return;
    }
    let mut rng = thread_rng();
    for _ in 0..n {
        println!("{}", rng.choose(&items).unwrap());
    }
}

fn pickup_deny_duplicates(items: Vec<String>, n: usize) {
    if items.len() == 0 || n == 0 {
        return;
    }
    let n = cmp::min(n, items.len());
    if n == 1 {
        pickup(items, n);
        return;
    }
    let mut rng = thread_rng();
    let mut indexes: Vec<usize> = (0..items.len()).collect();
    rng.shuffle(&mut indexes);
    for _ in 0..n {
        println!("{}", items[indexes.pop().unwrap()]);
    }
}<commit_msg>replace tab to space<commit_after>extern crate argparse;
extern crate rand;

use std::io::{self, BufRead, BufReader};
use std::fs::File;
use std::path::Path;
use std::cmp;
use rand::{thread_rng, Rng};
use argparse::{ArgumentParser, Store, StoreTrue};

fn main() {
    let mut filepath: String = String::new();
    let mut n: usize = 1;
    let mut deny_duplicates: bool = false;
    {
        let mut parser = ArgumentParser::new();
        parser.refer(&mut n).add_option(&["-n"], Store, "number of choice");
        parser.refer(&mut deny_duplicates).add_option(&["-d"], StoreTrue, "deny duplicates");
        parser.refer(&mut filepath).add_argument("file", Store, "source filepath");
        parser.parse_args_or_exit();
    }

    let items = match if filepath.len() > 0 { read_from_file(filepath.as_str()) }
                        else { read_from_stdin() } {
        Ok(items) => items,
        Err(e) => panic!(e.to_string())
    };

    if deny_duplicates {
        pickup_deny_duplicates(items, n);
    } else {
        pickup(items, n);
    }
}

fn read_from_stdin() -> io::Result<Vec<String>> {
    let stdin = io::stdin();
    let mut reader = stdin.lock();

    let mut items : Vec<String> = Vec::new();
    let mut buffer = String::new();
    while reader.read_line(&mut buffer)? > 0 {
        items.push(buffer.trim().to_string());
        buffer.clear();
    }
    return Ok(items);
}

fn read_from_file(filepath: &str) -> io::Result<Vec<String>> {
    let path = Path::new(filepath);
    let file = File::open(&path)?;
    let mut reader = BufReader::new(file);

    let mut items : Vec<String> = Vec::new();
    let mut buffer = String::new();
    while reader.read_line(&mut buffer)? > 0 {
        items.push(buffer.trim().to_string());
        buffer.clear();
    }
    return Ok(items);
}

fn pickup(items: Vec<String>, n: usize) {
    if items.len() == 0 || n == 0 {
        return;
    }
    let mut rng = thread_rng();
    for _ in 0..n {
        println!("{}", rng.choose(&items).unwrap());
    }
}

fn pickup_deny_duplicates(items: Vec<String>, n: usize) {
    if items.len() == 0 || n == 0 {
        return;
    }
    let n = cmp::min(n, items.len());
    if n == 1 {
        pickup(items, n);
        return;
    }
    let mut rng = thread_rng();
    let mut indexes: Vec<usize> = (0..items.len()).collect();
    rng.shuffle(&mut indexes);
    for _ in 0..n {
        println!("{}", items[indexes.pop().unwrap()]);
    }
}<|endoftext|>