1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
extern crate shlex;
extern crate getopts;

use std::env;
use getopts::Options;
use std::process::exit;
use std::process::Command;

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} <from> <to> [target_files]", program);
    print!("{}", opts.usage(&brief));
}

fn print_version() {
    println!("{}", env!("CARGO_PKG_VERSION"));
}

fn is_gsed_installed() -> bool {
    Command::new("which")
            .arg("gsed")
            .output()
            .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) })
            .status
            .success()
}

fn quote_args(args: &Vec<String>) -> (String, String, String) {
    let quoted_args: Vec<String> = args[1..].iter().map(|x| shlex::quote(x).to_string() ).collect();
    (
        quoted_args[0].clone(),
        quoted_args[1].clone(),
        if quoted_args.len() > 2 { quoted_args[2].clone() } else { ".".to_string() }
    )
}

pub fn substitute(args: &Vec<String>) -> () {
    let (from, to, path) = quote_args(&args);

    let output = Command::new("git")
                         .args(&["grep", "-l"])
                         .arg(&from)
                         .arg(&path)
                         .output()
                         .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });

    let stdout = String::from_utf8_lossy(&output.stdout);
    let target_files: Vec<&str> = stdout.lines_any().collect();
    if target_files.len() == 0 { exit(0); }
    let re = format!("s/{}/{}/g", &from, &to);

    if is_gsed_installed() {
        Command::new("gsed")
                .arg("-i")
                .arg(&re)
                .args(&target_files)
                .status()
                .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
    } else {
        Command::new("sed")
                .args(&["-i", "", "-e"])
                .arg(&re)
                .args(&target_files)
                .status()
                .unwrap_or_else(|e| { panic!("failed to execute process: {}", e) });
    }
}

pub fn run(args: env::Args) -> () {
    let args: Vec<String> = args.collect();
    let ref program = args[0];

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    opts.optflag("v", "version", "print version");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => { m }
        Err(f) => { panic!(f.to_string()) }
    };

    if matches.opt_present("v") {
        return print_version();
    } else if matches.opt_present("h") || args.len() <= 2 {
        return print_usage(&program, opts);
    }

    substitute(&args);
}