<commit_before>use std::env;
use std::old_io;
use std::old_io::fs::PathExtensions;
use std::borrow::ToOwned;

use atomicwrites::{GenericAtomicFile,AtomicFile,AllowOverwrite};

use utils;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                if e.desc.len() > 0 {
                    writeln!(&mut old_io::stdio::stderr(), "{}: {}", $errmsg, e).unwrap();
                };
                env::set_exit_status(1);
                return;
            }
        }
    )
}

macro_rules! get_pwd {
    () => (
        match env::current_dir() {
            Ok(x) => x,
            Err(e) => panic!(format!("Failed to get current working directory: {}", e))
        }
    )
}

fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(e) => match e {
            env::VarError::NotPresent => None,
            env::VarError::NotUnicode(_) => panic!(format!("{} is not unicode.", key))
        }
    }
}

fn build_index(outfile: &Path, dir: &Path) -> old_io::IoResult<()> {
    if !dir.is_dir() {
        return Err(old_io::IoError {
            kind: old_io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let af: AtomicFile = GenericAtomicFile::new(outfile, AllowOverwrite);
    let entries = try!(old_io::fs::readdir(dir));
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in entries.iter() {
            if !entry.is_file() || !entry.filename_str().unwrap_or("").ends_with(".vcf") {
                continue;
            }

            let contact = match utils::Contact::from_file(entry.clone()) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", entry.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_str(index_string.as_slice()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", entry.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Several errors happened while generating the index.",
            detail: None
        })
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    let mut args = env::args();
    let program = args.next().unwrap_or("mates".to_string());

    let help = format!("Usage: {} COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If the file is cleared,
        the contact is removed. As a further convenience it also clears stdin, which is necessary
        for editors and most interactive programs to not act weird when piped to.",
        program);

    let print_help = |&:| {
        println!("{}", help);
    };

    let command = match args.next() {
        Some(x) => x,
        None => {
            print_help();
            env::set_exit_status(1);
            return;
        }
    };

    if command == "--help" || command == "help" || command == "-h" {
        print_help();
        return;
    }

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            println!("Error while reading configuration: {}", e);
            env::set_exit_status(1);
            return;
        }
    };

    match command.as_slice() {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            main_try!(build_index(&config.index_path, &config.vdir_path), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(mutt_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(file_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(email_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "add" => {
            let mut stdin = old_io::stdin();
            let email = main_try!(stdin.lock().read_to_string(), "Failed to read email");
            let contact = main_try!(utils::add_contact_from_email(
                &config.vdir_path,
                email.as_slice()
            ), "Failed to add contact");
            println!("{}", contact.path.display());

            let mut index_fp = main_try!(old_io::File::open_mode(
                &config.index_path,
                old_io::Append,
                old_io::Write),
                "Failed to open index"
            );

            let index_entry = main_try!(utils::index_item_from_contact(&contact), "Failed to generate index");
            main_try!(index_fp.write_str(index_entry.as_slice()), "Failed to write to index");
        },
        "edit" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(edit_contact(&config, query.as_slice()), "Failed to edit contact");
        },
        _ => {
            println!("Invalid command: {}", command);
            print_help();
            env::set_exit_status(1);
        }
    };
}

fn edit_contact(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    let results = if get_pwd!().join(query).is_file() {
        vec![Path::new(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "No such contact.",
            detail: None
        })
    } else if results.len() > 1 {
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Ambiguous query.",
            detail: None
        })
    }

    let fpath = &results[0];
    let mut process = try!(old_io::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg(format!("$0 -- \"$1\" < $2"))
        .arg(config.editor_cmd.as_slice())
        .arg(fpath.as_str().unwrap())
        .arg("/dev/tty")
        .stdin(old_io::process::InheritFd(0))
        .stdout(old_io::process::InheritFd(1))
        .stderr(old_io::process::InheritFd(2))
        .spawn());

    try!(process.wait());

    if try!(old_io::File::open(fpath).read_to_string()).as_slice().trim().len() == 0 {
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Contact emptied, file removed.",
            detail: None
        });
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: Path,
    pub vdir_path: Path,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => Path::new(x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd!().join(home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => Path::new(x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}
<commit_msg>Actually remove files<commit_after>use std::env;
use std::old_io;
use std::old_io::fs::PathExtensions;
use std::borrow::ToOwned;

use atomicwrites::{GenericAtomicFile,AtomicFile,AllowOverwrite};

use utils;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                if e.desc.len() > 0 {
                    writeln!(&mut old_io::stdio::stderr(), "{}: {}", $errmsg, e).unwrap();
                };
                env::set_exit_status(1);
                return;
            }
        }
    )
}

macro_rules! get_pwd {
    () => (
        match env::current_dir() {
            Ok(x) => x,
            Err(e) => panic!(format!("Failed to get current working directory: {}", e))
        }
    )
}

fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(e) => match e {
            env::VarError::NotPresent => None,
            env::VarError::NotUnicode(_) => panic!(format!("{} is not unicode.", key))
        }
    }
}

fn build_index(outfile: &Path, dir: &Path) -> old_io::IoResult<()> {
    if !dir.is_dir() {
        return Err(old_io::IoError {
            kind: old_io::MismatchedFileTypeForOperation,
            desc: "MATES_DIR must be a directory.",
            detail: None
        });
    };

    let af: AtomicFile = GenericAtomicFile::new(outfile, AllowOverwrite);
    let entries = try!(old_io::fs::readdir(dir));
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in entries.iter() {
            if !entry.is_file() || !entry.filename_str().unwrap_or("").ends_with(".vcf") {
                continue;
            }

            let contact = match utils::Contact::from_file(entry.clone()) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", entry.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_str(index_string.as_slice()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", entry.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Several errors happened while generating the index.",
            detail: None
        })
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    let mut args = env::args();
    let program = args.next().unwrap_or("mates".to_string());

    let help = format!("Usage: {} COMMAND
Commands:
    index:
        Rewrite/create the index.
    mutt-query <query>:
        Search for contact, output is usable for mutt's query_command.
    file-query <query>:
        Search for contact, return just the filename.
    email-query <query>:
        Search for contact, return \"name <email>\".
    add:
        Take mail from stdin, add sender to contacts. Print filename.
    edit <file-or-query>:
        Open contact (given by filepath or search-string) in $MATES_EDITOR. If the file is cleared,
        the contact is removed. As a further convenience it also clears stdin, which is necessary
        for editors and most interactive programs to not act weird when piped to.",
        program);

    let print_help = |&:| {
        println!("{}", help);
    };

    let command = match args.next() {
        Some(x) => x,
        None => {
            print_help();
            env::set_exit_status(1);
            return;
        }
    };

    if command == "--help" || command == "help" || command == "-h" {
        print_help();
        return;
    }

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            println!("Error while reading configuration: {}", e);
            env::set_exit_status(1);
            return;
        }
    };

    match command.as_slice() {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            main_try!(build_index(&config.index_path, &config.vdir_path), "Failed to build index");
        },
        "mutt-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(mutt_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "file-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(file_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "email-query" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(email_query(&config, query.as_slice()), "Failed to execute grep");
        },
        "add" => {
            let mut stdin = old_io::stdin();
            let email = main_try!(stdin.lock().read_to_string(), "Failed to read email");
            let contact = main_try!(utils::add_contact_from_email(
                &config.vdir_path,
                email.as_slice()
            ), "Failed to add contact");
            println!("{}", contact.path.display());

            let mut index_fp = main_try!(old_io::File::open_mode(
                &config.index_path,
                old_io::Append,
                old_io::Write),
                "Failed to open index"
            );

            let index_entry = main_try!(utils::index_item_from_contact(&contact), "Failed to generate index");
            main_try!(index_fp.write_str(index_entry.as_slice()), "Failed to write to index");
        },
        "edit" => {
            let query = args.next().unwrap_or("".to_string());
            main_try!(edit_contact(&config, query.as_slice()), "Failed to edit contact");
        },
        _ => {
            println!("Invalid command: {}", command);
            print_help();
            env::set_exit_status(1);
        }
    };
}

fn edit_contact(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    let results = if get_pwd!().join(query).is_file() {
        vec![Path::new(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "No such contact.",
            detail: None
        })
    } else if results.len() > 1 {
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Ambiguous query.",
            detail: None
        })
    }

    let fpath = &results[0];
    let mut process = try!(old_io::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg(format!("$0 -- \"$1\" < $2"))
        .arg(config.editor_cmd.as_slice())
        .arg(fpath.as_str().unwrap())
        .arg("/dev/tty")
        .stdin(old_io::process::InheritFd(0))
        .stdout(old_io::process::InheritFd(1))
        .stderr(old_io::process::InheritFd(2))
        .spawn());

    try!(process.wait());

    if try!(old_io::File::open(fpath).read_to_string()).as_slice().trim().len() == 0 {
        try!(old_io::fs::unlink(fpath));
        return Err(old_io::IoError {
            kind: old_io::OtherIoError,
            desc: "Contact emptied, file removed.",
            detail: None
        });
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> old_io::IoResult<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: Path,
    pub vdir_path: Path,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => Path::new(x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd!().join(home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => Path::new(x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}
<|endoftext|>


<commit_before>use std::borrow::ToOwned;
use std::env;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io::{Read,Write};
use std::io;
use std::path;
use std::process;

use clap::{Arg,App,SubCommand};
use atomicwrites::{AtomicFile,AllowOverwrite};

use utils;
use utils::CustomPathExt;

fn get_pwd() -> path::PathBuf {
    env::current_dir().ok().expect("Failed to get CWD")
}

fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(e) => match e {
            env::VarError::NotPresent => None,
            env::VarError::NotUnicode(_) => panic!(format!("{} is not unicode.", key))
        }
    }
}

fn build_index(outfile: &path::Path, dir: &path::Path) -> io::Result<()> {
    if !dir.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "MATES_DIR must be a directory.",
        ));
    };

    let af = AtomicFile::new(&outfile, AllowOverwrite);
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in try!(fs::read_dir(dir)) {
            let entry = match entry {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while listing directory: {}", e);
                    errors = true;
                    continue;
                }
            };

            let pathbuf = entry.path();

            if pathbuf.str_extension().unwrap_or("") != "vcf" || !pathbuf.is_file() {
                continue;
            };

            let contact = match utils::Contact::from_file(&pathbuf) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_all(index_string.as_bytes()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Several errors happened while generating the index.",
        ))
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    match cli_main_raw() {
        Err(e) => {
            writeln!(&mut io::stderr(), "{}", e).unwrap();
            process::exit(1);
        },
        _ => ()
    };
}

pub fn cli_main_raw() -> Result<(), Box<Error>> {
    let matches = App::new("mates")
        .version("0.0.1")  // FIXME: Use package metadata
        .author("Markus Unterwaditzer")
        .about("A simple commandline addressbook")
        .subcommand(SubCommand::new("index")
                    .about("Rewrite/create the index"))
        .subcommand(SubCommand::new("mutt-query")
                    .about("Search for contact, output is usable for mutt's query_command.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("file-query")
                    .about("Search for contact, return just the filename.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("email-query")
                    .about("Search for contact, return \"name <email>\".")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("add")
                    .about("Take mail from stdin, add sender to contacts. Print filename."))
        .subcommand(SubCommand::new("edit")
                    .about(
                        "Open contact (given by filepath or search-string) in $MATES_EDITOR. If
                        the file is cleared, the contact is removed. As a further convenience it
                        also clears stdin, which is necessary for editors and most interactive
                        programs to not act weird when piped to."
                    )
                    .arg(Arg::new("file-or-query").index(1)))
        .get_matches();

    let command = match matches.subcommand_name() {
        Some(x) => x,
        None => {
            return Err(CliError::new("Command required. See --help for usage.").into());
        }
    };

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            return Err(CliError::new(format!("Error while reading configuration: {}", e)).into());
        }
    };

    let submatches = matches.subcommand_matches(command).expect("Internal error.");

    match command {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            try!(build_index(&config.index_path, &config.vdir_path));
        },
        "mutt-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(mutt_query(&config, &query[..]));
        },
        "file-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(file_query(&config, &query[..]));
        },
        "email-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(email_query(&config, &query[..]));
        },
        "add" => {
            let stdin = io::stdin();
            let mut email = String::new();
            try!(stdin.lock().read_to_string(&mut email));
            let contact = try!(utils::add_contact_from_email(
                &config.vdir_path,
                &email[..]
            ));
            println!("{}", contact.path.display());

            let mut index_fp = try!(fs::OpenOptions::new()
                                    .append(true)
                                    .write(true)
                                    .open(&config.index_path));

            let index_entry = try!(utils::index_item_from_contact(&contact));
            try!(index_fp.write_all(index_entry.as_bytes()));
        },
        "edit" => {
            let query = submatches.value_of("file-or-query").unwrap_or("");
            try!(edit_contact(&config, &query[..]));
        },
        _ => {
            return Err(CliError::new(format!("Invalid command: {}", command)).into());
        }
    };
    Ok(())
}

fn edit_contact(config: &Configuration, query: &str) -> io::Result<()> {
    let results = if get_pwd().join(query).is_file() {
        vec![path::PathBuf::from(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "No such contact.",
        ))
    } else if results.len() > 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Ambiguous query.",
        ))
    }

    let fpath = &results[0];
    let mut process = try!(process::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg("$0 \"$1\" < $2")
        .arg(&config.editor_cmd[..])
        .arg(fpath.as_os_str())
        .arg("/dev/tty")
        .stdin(process::Stdio::inherit())
        .stdout(process::Stdio::inherit())
        .stderr(process::Stdio::inherit())
        .spawn());

    try!(utils::handle_process(&mut process));

    let fcontent = {
        let mut fcontent = String::new();
        let mut file = try!(fs::File::open(fpath));
        try!(file.read_to_string(&mut fcontent));
        fcontent
    };

    if (&fcontent[..]).trim().len() == 0 {
        try!(fs::remove_file(fpath));
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Contact emptied, file removed.",
        ));
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: path::PathBuf,
    pub vdir_path: path::PathBuf,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => path::PathBuf::from(&x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd().join(&home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => path::PathBuf::from(&x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}

#[derive(PartialEq, Eq, Debug)]
pub struct CliError {
    desc: String,
}

pub type CliResult<T> = Result<T, CliError>;

impl Error for CliError {
    fn description(&self) -> &str {
        &self.desc[..]
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description().fmt(f)
    }
}

impl CliError {
    pub fn new<T: Into<String>>(desc: T) -> Self {
        CliError {
            desc: desc.into(),
        }
    }
}
<commit_msg>Fix up errorhandling a bit<commit_after>use std::borrow::ToOwned;
use std::env;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io::{Read,Write};
use std::io;
use std::path;
use std::process;

use clap::{Arg,App,SubCommand};
use atomicwrites::{AtomicFile,AllowOverwrite};

use utils;
use utils::CustomPathExt;

#[inline]
fn get_pwd() -> path::PathBuf {
    env::current_dir().ok().expect("Failed to get CWD")
}

#[inline]
fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(env::VarError::NotPresent) => None,
        Err(env::VarError::NotUnicode(_)) => panic!(format!("{} is not unicode.", key)),
    }
}

fn build_index(outfile: &path::Path, dir: &path::Path) -> MainResult<()> {
    if !dir.is_dir() {
        return Err(MainError::new("MATES_DIR must be a directory.").into());
    };

    let af = AtomicFile::new(&outfile, AllowOverwrite);
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in try!(fs::read_dir(dir)) {
            let entry = match entry {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while listing directory: {}", e);
                    errors = true;
                    continue;
                }
            };

            let pathbuf = entry.path();

            if pathbuf.str_extension().unwrap_or("") != "vcf" || !pathbuf.is_file() {
                continue;
            };

            let contact = match utils::Contact::from_file(&pathbuf) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_all(index_string.as_bytes()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(MainError::new("Several errors happened while generating the index.").into())
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    match cli_main_raw() {
        Err(e) => {
            writeln!(&mut io::stderr(), "{}", e).unwrap();
            process::exit(1);
        },
        _ => ()
    };
}

pub fn cli_main_raw() -> MainResult<()> {
    let matches = App::new("mates")
        .version("0.0.1")  // FIXME: Use package metadata
        .author("Markus Unterwaditzer")
        .about("A simple commandline addressbook")
        .subcommand(SubCommand::new("index")
                    .about("Rewrite/create the index"))
        .subcommand(SubCommand::new("mutt-query")
                    .about("Search for contact, output is usable for mutt's query_command.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("file-query")
                    .about("Search for contact, return just the filename.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("email-query")
                    .about("Search for contact, return \"name <email>\".")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("add")
                    .about("Take mail from stdin, add sender to contacts. Print filename."))
        .subcommand(SubCommand::new("edit")
                    .about(
                        "Open contact (given by filepath or search-string) in $MATES_EDITOR. If
                        the file is cleared, the contact is removed. As a further convenience it
                        also clears stdin, which is necessary for editors and most interactive
                        programs to not act weird when piped to."
                    )
                    .arg(Arg::new("file-or-query").index(1)))
        .get_matches();

    let command = match matches.subcommand_name() {
        Some(x) => x,
        None => {
            return Err(MainError::new("Command required. See --help for usage.").into());
        }
    };

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            return Err(MainError::new(format!("Error while reading configuration: {}", e)).into());
        }
    };

    let submatches = matches.subcommand_matches(command).expect("Internal error.");

    match command {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            try!(build_index(&config.index_path, &config.vdir_path));
        },
        "mutt-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(mutt_query(&config, &query[..]));
        },
        "file-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(file_query(&config, &query[..]));
        },
        "email-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            try!(email_query(&config, &query[..]));
        },
        "add" => {
            let stdin = io::stdin();
            let mut email = String::new();
            try!(stdin.lock().read_to_string(&mut email));
            let contact = try!(utils::add_contact_from_email(
                &config.vdir_path,
                &email[..]
            ));
            println!("{}", contact.path.display());

            let mut index_fp = try!(fs::OpenOptions::new()
                                    .append(true)
                                    .write(true)
                                    .open(&config.index_path));

            let index_entry = try!(utils::index_item_from_contact(&contact));
            try!(index_fp.write_all(index_entry.as_bytes()));
        },
        "edit" => {
            let query = submatches.value_of("file-or-query").unwrap_or("");
            try!(edit_contact(&config, &query[..]));
        },
        _ => {
            return Err(MainError::new(format!("Invalid command: {}", command)).into());
        }
    };
    Ok(())
}

fn edit_contact(config: &Configuration, query: &str) -> MainResult<()> {
    let results = if get_pwd().join(query).is_file() {
        vec![path::PathBuf::from(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(MainError::new("No such contact.").into());
    } else if results.len() > 1 {
        return Err(MainError::new("Ambiguous query.").into());
    }

    let fpath = &results[0];
    let mut process = try!(process::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg("$0 \"$1\" < $2")
        .arg(&config.editor_cmd[..])
        .arg(fpath.as_os_str())
        .arg("/dev/tty")
        .stdin(process::Stdio::inherit())
        .stdout(process::Stdio::inherit())
        .stderr(process::Stdio::inherit())
        .spawn());

    try!(utils::handle_process(&mut process));

    let fcontent = {
        let mut fcontent = String::new();
        let mut file = try!(fs::File::open(fpath));
        try!(file.read_to_string(&mut fcontent));
        fcontent
    };

    if (&fcontent[..]).trim().len() == 0 {
        try!(fs::remove_file(fpath));
        return Err(MainError::new("Contact emptied, file removed.").into());
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> MainResult<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: path::PathBuf,
    pub vdir_path: path::PathBuf,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => path::PathBuf::from(&x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd().join(&home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => path::PathBuf::from(&x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}

#[derive(PartialEq, Eq, Debug)]
pub struct MainError {
    desc: String,
}

pub type MainResult<T> = Result<T, Box<Error>>;

impl Error for MainError {
    fn description(&self) -> &str {
        &self.desc[..]
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

impl fmt::Display for MainError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.description().fmt(f)
    }
}

impl MainError {
    pub fn new<T: Into<String>>(desc: T) -> Self {
        MainError {
            desc: desc.into(),
        }
    }
}
<|endoftext|>


<commit_before>use std::fs;
use std::fs::PathExt;
use std::io;
use std::io::{Read,Write};
use std::process;
use std::path;
use std::env;
use std::borrow::ToOwned;
use std::error::Error;

use clap::{Arg,App,SubCommand};
use atomicwrites::{AtomicFile,AllowOverwrite};

use utils;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                if e.description().len() > 0 {
                    writeln!(&mut io::stderr(), "{}: {}", $errmsg, e).unwrap();
                };
                env::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_pwd() -> path::PathBuf {
    env::current_dir().ok().expect("Failed to get CWD")
}

fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(e) => match e {
            env::VarError::NotPresent => None,
            env::VarError::NotUnicode(_) => panic!(format!("{} is not unicode.", key))
        }
    }
}

fn build_index(outfile: &path::Path, dir: &path::Path) -> io::Result<()> {
    if !dir.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "MATES_DIR must be a directory.",
        ));
    };

    let af = AtomicFile::new(&outfile, AllowOverwrite);
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in try!(fs::read_dir(dir)) {
            let entry = match entry {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while listing directory: {}", e);
                    errors = true;
                    continue;
                }
            };

            let pathbuf = entry.path();

            if !(*pathbuf).is_file() || match pathbuf.extension() {
                Some(x) => x.to_str().unwrap_or("") != "vcf",
                None => false
            } {
                continue;
            }

            let contact = match utils::Contact::from_file(&pathbuf) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_all(index_string.as_bytes()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Several errors happened while generating the index.",
        ))
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    let matches = App::new("mates")
        .version("0.0.1")  // FIXME: Use package metadata
        .author("Markus Unterwaditzer")
        .about("A simple commandline addressbook")
        .subcommand(SubCommand::new("index")
                    .about("Rewrite/create the index"))
        .subcommand(SubCommand::new("mutt-query")
                    .about("Search for contact, output is usable for mutt's query_command.")
                    .arg(Arg::new("query").required(true).index(1)))
        .subcommand(SubCommand::new("file-query")
                    .about("Search for contact, return just the filename.")
                    .arg(Arg::new("query").required(true).index(1)))
        .subcommand(SubCommand::new("email-query")
                    .about("Search for contact, return \"name <email>\".")
                    .arg(Arg::new("query").required(true).index(1)))
        .subcommand(SubCommand::new("add")
                    .about("Take mail from stdin, add sender to contacts. Print filename."))
        .subcommand(SubCommand::new("edit")
                    .about(
                        "Open contact (given by filepath or search-string) in $MATES_EDITOR. If
                        the file is cleared, the contact is removed. As a further convenience it
                        also clears stdin, which is necessary for editors and most interactive
                        programs to not act weird when piped to."
                    )
                    .arg(Arg::new("file-or-query").required(true).index(1)))
        .get_matches();

    let command = match matches.subcommand_name() {
        Some(x) => x,
        None => {
            println!("Command required. See --help for usage.");
            env::set_exit_status(1);
            return;
        }
    };

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            println!("Error while reading configuration: {}", e);
            env::set_exit_status(1);
            return;
        }
    };

    let submatches = matches.subcommand_matches(command).expect("Internal error.");

    match command {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            main_try!(build_index(&config.index_path, &config.vdir_path), "Failed to build index");
        },
        "mutt-query" => {
            let query = submatches.value_of("query")
                .expect("Internal error: query arg should've been required.");
            main_try!(mutt_query(&config, &query[..]), "Failed to execute grep");
        },
        "file-query" => {
            let query = submatches.value_of("query")
                .expect("Internal error: query arg should've been required.");
            main_try!(file_query(&config, &query[..]), "Failed to execute grep");
        },
        "email-query" => {
            let query = submatches.value_of("query")
                .expect("Internal error: query arg should've been required.");
            main_try!(email_query(&config, &query[..]), "Failed to execute grep");
        },
        "add" => {
            let stdin = io::stdin();
            let mut email = String::new();
            main_try!(stdin.lock().read_to_string(&mut email), "Failed to read email");
            let contact = main_try!(utils::add_contact_from_email(
                &config.vdir_path,
                &email[..]
            ), "Failed to add contact");
            println!("{}", contact.path.display());

            let mut index_fp = main_try!(
                fs::OpenOptions::new()
                .append(true)
                .write(true)
                .open(&config.index_path),
                "Failed to open index"
            );

            let index_entry = main_try!(utils::index_item_from_contact(&contact), "Failed to generate index");
            main_try!(index_fp.write_all(index_entry.as_bytes()), "Failed to write to index");
        },
        "edit" => {
            let query = submatches.value_of("file-or-query")
                .expect("Internal error: query arg should've been required.");
            main_try!(edit_contact(&config, &query[..]), "Failed to edit contact");
        },
        _ => {
            println!("Invalid command: {}", command);
            env::set_exit_status(1);
        }
    };
}

fn edit_contact(config: &Configuration, query: &str) -> io::Result<()> {
    let results = if get_pwd().join(query).is_file() {
        vec![path::PathBuf::from(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "No such contact.",
        ))
    } else if results.len() > 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Ambiguous query.",
        ))
    }

    let fpath = &results[0];
    let mut process = try!(process::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg("$0 \"$1\" < $2")
        .arg(&config.editor_cmd[..])
        .arg(fpath.as_os_str())
        .arg("/dev/tty")
        .stdin(process::Stdio::inherit())
        .stdout(process::Stdio::inherit())
        .stderr(process::Stdio::inherit())
        .spawn());

    try!(utils::handle_process(&mut process));

    let fcontent = {
        let mut fcontent = String::new();
        let mut file = try!(fs::File::open(fpath));
        try!(file.read_to_string(&mut fcontent));
        fcontent
    };

    if (&fcontent[..]).trim().len() == 0 {
        try!(fs::remove_file(fpath));
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Contact emptied, file removed.",
        ));
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: path::PathBuf,
    pub vdir_path: path::PathBuf,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => path::PathBuf::from(&x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd().join(&home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => path::PathBuf::from(&x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}
<commit_msg>Make query default to empty string<commit_after>use std::fs;
use std::fs::PathExt;
use std::io;
use std::io::{Read,Write};
use std::process;
use std::path;
use std::env;
use std::borrow::ToOwned;
use std::error::Error;

use clap::{Arg,App,SubCommand};
use atomicwrites::{AtomicFile,AllowOverwrite};

use utils;

macro_rules! main_try {
    ($result: expr, $errmsg: expr) => (
        match $result {
            Ok(m) => m,
            Err(e) => {
                if e.description().len() > 0 {
                    writeln!(&mut io::stderr(), "{}: {}", $errmsg, e).unwrap();
                };
                env::set_exit_status(1);
                return;
            }
        }
    )
}

fn get_pwd() -> path::PathBuf {
    env::current_dir().ok().expect("Failed to get CWD")
}

fn get_envvar(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(x) => Some(x),
        Err(e) => match e {
            env::VarError::NotPresent => None,
            env::VarError::NotUnicode(_) => panic!(format!("{} is not unicode.", key))
        }
    }
}

fn build_index(outfile: &path::Path, dir: &path::Path) -> io::Result<()> {
    if !dir.is_dir() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "MATES_DIR must be a directory.",
        ));
    };

    let af = AtomicFile::new(&outfile, AllowOverwrite);
    let mut errors = false;

    try!(af.write(|outf| {
        for entry in try!(fs::read_dir(dir)) {
            let entry = match entry {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while listing directory: {}", e);
                    errors = true;
                    continue;
                }
            };

            let pathbuf = entry.path();

            if !(*pathbuf).is_file() || match pathbuf.extension() {
                Some(x) => x.to_str().unwrap_or("") != "vcf",
                None => false
            } {
                continue;
            }

            let contact = match utils::Contact::from_file(&pathbuf) {
                Ok(x) => x,
                Err(e) => {
                    println!("Error while reading {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };

            match utils::index_item_from_contact(&contact) {
                Ok(index_string) => {
                    try!(outf.write_all(index_string.as_bytes()));
                },
                Err(e) => {
                    println!("Error while indexing {}: {}", pathbuf.display(), e);
                    errors = true;
                    continue
                }
            };
        };
        Ok(())
    }));

    if errors {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Several errors happened while generating the index.",
        ))
    } else {
        Ok(())
    }
}

pub fn cli_main() {
    let matches = App::new("mates")
        .version("0.0.1")  // FIXME: Use package metadata
        .author("Markus Unterwaditzer")
        .about("A simple commandline addressbook")
        .subcommand(SubCommand::new("index")
                    .about("Rewrite/create the index"))
        .subcommand(SubCommand::new("mutt-query")
                    .about("Search for contact, output is usable for mutt's query_command.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("file-query")
                    .about("Search for contact, return just the filename.")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("email-query")
                    .about("Search for contact, return \"name <email>\".")
                    .arg(Arg::new("query").index(1)))
        .subcommand(SubCommand::new("add")
                    .about("Take mail from stdin, add sender to contacts. Print filename."))
        .subcommand(SubCommand::new("edit")
                    .about(
                        "Open contact (given by filepath or search-string) in $MATES_EDITOR. If
                        the file is cleared, the contact is removed. As a further convenience it
                        also clears stdin, which is necessary for editors and most interactive
                        programs to not act weird when piped to."
                    )
                    .arg(Arg::new("file-or-query").index(1)))
        .get_matches();

    let command = match matches.subcommand_name() {
        Some(x) => x,
        None => {
            println!("Command required. See --help for usage.");
            env::set_exit_status(1);
            return;
        }
    };

    let config = match Configuration::new() {
        Ok(x) => x,
        Err(e) => {
            println!("Error while reading configuration: {}", e);
            env::set_exit_status(1);
            return;
        }
    };

    let submatches = matches.subcommand_matches(command).expect("Internal error.");

    match command {
        "index" => {
            println!("Rebuilding index file \"{}\"...", config.index_path.display());
            main_try!(build_index(&config.index_path, &config.vdir_path), "Failed to build index");
        },
        "mutt-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            main_try!(mutt_query(&config, &query[..]), "Failed to execute grep");
        },
        "file-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            main_try!(file_query(&config, &query[..]), "Failed to execute grep");
        },
        "email-query" => {
            let query = submatches.value_of("query").unwrap_or("");
            main_try!(email_query(&config, &query[..]), "Failed to execute grep");
        },
        "add" => {
            let stdin = io::stdin();
            let mut email = String::new();
            main_try!(stdin.lock().read_to_string(&mut email), "Failed to read email");
            let contact = main_try!(utils::add_contact_from_email(
                &config.vdir_path,
                &email[..]
            ), "Failed to add contact");
            println!("{}", contact.path.display());

            let mut index_fp = main_try!(
                fs::OpenOptions::new()
                .append(true)
                .write(true)
                .open(&config.index_path),
                "Failed to open index"
            );

            let index_entry = main_try!(utils::index_item_from_contact(&contact), "Failed to generate index");
            main_try!(index_fp.write_all(index_entry.as_bytes()), "Failed to write to index");
        },
        "edit" => {
            let query = submatches.value_of("file-or-query").unwrap_or("");
            main_try!(edit_contact(&config, &query[..]), "Failed to edit contact");
        },
        _ => {
            println!("Invalid command: {}", command);
            env::set_exit_status(1);
        }
    };
}

fn edit_contact(config: &Configuration, query: &str) -> io::Result<()> {
    let results = if get_pwd().join(query).is_file() {
        vec![path::PathBuf::from(query)]
    } else {
        try!(utils::file_query(config, query)).into_iter().collect()
    };

    if results.len() < 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "No such contact.",
        ))
    } else if results.len() > 1 {
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Ambiguous query.",
        ))
    }

    let fpath = &results[0];
    let mut process = try!(process::Command::new("sh")
        .arg("-c")
        // clear stdin, http://unix.stackexchange.com/a/77593
        .arg("$0 \"$1\" < $2")
        .arg(&config.editor_cmd[..])
        .arg(fpath.as_os_str())
        .arg("/dev/tty")
        .stdin(process::Stdio::inherit())
        .stdout(process::Stdio::inherit())
        .stderr(process::Stdio::inherit())
        .spawn());

    try!(utils::handle_process(&mut process));

    let fcontent = {
        let mut fcontent = String::new();
        let mut file = try!(fs::File::open(fpath));
        try!(file.read_to_string(&mut fcontent));
        fcontent
    };

    if (&fcontent[..]).trim().len() == 0 {
        try!(fs::remove_file(fpath));
        return Err(io::Error::new(
            io::ErrorKind::Other,
            "Contact emptied, file removed.",
        ));
    };

    Ok(())
}

fn mutt_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    println!("");  // For some reason mutt requires an empty line
    for item in try!(utils::index_query(config, query)) {
        if item.email.len() > 0 && item.name.len() > 0 {
            println!("{}\t{}", item.email, item.name);
        };
    };
    Ok(())
}

fn file_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for path in try!(utils::file_query(config, query)).iter() {
        println!("{}", path.display());
    };
    Ok(())
}

fn email_query<'a>(config: &Configuration, query: &str) -> io::Result<()> {
    for item in try!(utils::index_query(config, query)) {
        if item.name.len() > 0 && item.email.len() > 0 {
            println!("{} <{}>", item.name, item.email);
        };
    };
    Ok(())
}

pub struct Configuration {
    pub index_path: path::PathBuf,
    pub vdir_path: path::PathBuf,
    pub editor_cmd: String,
    pub grep_cmd: String
}

impl Configuration {
    pub fn new() -> Result<Configuration, String> {
        Ok(Configuration {
            index_path: match get_envvar("MATES_INDEX") {
                Some(x) => path::PathBuf::from(&x),
                None => match get_envvar("HOME") {
                    Some(home) => get_pwd().join(&home).join(".mates_index"),
                    None => return Err("Unable to determine user's home directory.".to_owned())
                }
            },
            vdir_path: match get_envvar("MATES_DIR") {
                Some(x) => path::PathBuf::from(&x),
                None => return Err("MATES_DIR must be set to your vdir path (directory of vcf-files).".to_owned())
            },
            editor_cmd: match get_envvar("MATES_EDITOR") {
                Some(x) => x,
                None => match get_envvar("EDITOR") {
                    Some(x) => x,
                    None => return Err("MATES_EDITOR or EDITOR must be set.".to_owned())
                }
            },
            grep_cmd: match get_envvar("MATES_GREP") {
                Some(x) => x,
                None => "grep -i".to_owned()
            }
        })
    }
}
<|endoftext|>


<commit_before>//! The `optimizer` module contains iterators that allow a user to iterate over
//! `Program`s in order of increasing content
//!
//! The content is a measure of how complex a Program is.

use std::ops::{Rem, Div};
use super::TIS_100::{Program, Instruction, Source, Destination, Register};

/// Allows one to determine the content of certain constructs, e.g. `Program`s
/// or `Instruction`s
pub trait Content {
    /// the content of the construct
    fn content(&self) -> u32;
}

impl Content for Program {
    fn content(&self) -> u32 {
        let Program(ref instructions) = *self;

        instructions.iter().fold(0 as u32, |sum, instruction| sum + instruction.content())
    }
}

impl Content for Instruction {
    fn content(&self) -> u32 {
        match *self {
            Instruction::NOP => 1,
            Instruction::MOV(ref source, ref destination) => 1 + source.content() + destination.content(),
            Instruction::SWP => 1,
            Instruction::SAV => 1,
            Instruction::ADD(ref source) => 1 + source.content(),
            Instruction::SUB(ref source) => 1 + source.content(),
        }
    }
}

impl Content for Source {
    fn content(&self) -> u32 {
        match *self {
            Source::Literal(value) => 1 + (value.abs() as u32),
            _ => 1,
        }
    }
}

impl Content for Destination {
    fn content(&self) -> u32 {
        1
    }
}

/// Iterator over Programs
pub struct ProgramIterator {
    current: u32,
}

impl ProgramIterator {
    /// Create a `ProgramIterator`
    pub fn new() -> ProgramIterator {
        ProgramIterator { current: 0 }
    }
}

impl Iterator for ProgramIterator {
    type Item = Program;

    fn next(&mut self) -> Option<Program> {
        let instructions: Vec<Instruction> =
            digits_of(self.current, 33)
            .iter()
            .map(|digit| instruction_map(*digit))
            .collect();
        self.current += 1;
        Some(Program(instructions))
    }
}

fn digits_of(mut n: u32, base: u32) -> Vec<u32> {
    let mut digits: Vec<u32> = vec![];
    while n > 0 {
        digits.push(n.rem(base));
        n = n.div(base);
    }
    digits
}

fn instruction_map(index: u32) -> Instruction {
    match index % 33 {
        0 => Instruction::NOP,
        1 => Instruction::SWP,
        2 => Instruction::SAV,
        3 => Instruction::ADD(Source::Port),
        4 => Instruction::ADD(Source::Register(Register::NIL)),
        5 => Instruction::ADD(Source::Register(Register::ACC)),
        6 => Instruction::ADD(Source::Literal(0)),
        7 => Instruction::ADD(Source::Literal(1)),
        8 => Instruction::ADD(Source::Literal(-1)),
        9 => Instruction::SUB(Source::Port),
        10 => Instruction::SUB(Source::Register(Register::NIL)),
        11 => Instruction::SUB(Source::Register(Register::ACC)),
        12 => Instruction::SUB(Source::Literal(0)),
        13 => Instruction::SUB(Source::Literal(1)),
        14 => Instruction::SUB(Source::Literal(-1)),
        15 => Instruction::MOV(Source::Port, Destination::Port),
        16 => Instruction::MOV(Source::Register(Register::NIL), Destination::Port),
        17 => Instruction::MOV(Source::Register(Register::ACC), Destination::Port),
        18 => Instruction::MOV(Source::Literal(0), Destination::Port),
        19 => Instruction::MOV(Source::Literal(1), Destination::Port),
        20 => Instruction::MOV(Source::Literal(-1), Destination::Port),
        21 => Instruction::MOV(Source::Port, Destination::Register(Register::NIL)),
        22 => Instruction::MOV(Source::Register(Register::NIL), Destination::Register(Register::NIL)),
        23 => Instruction::MOV(Source::Register(Register::ACC), Destination::Register(Register::NIL)),
        24 => Instruction::MOV(Source::Literal(0), Destination::Register(Register::NIL)),
        25 => Instruction::MOV(Source::Literal(1), Destination::Register(Register::NIL)),
        26 => Instruction::MOV(Source::Literal(-1), Destination::Register(Register::NIL)),
        27 => Instruction::MOV(Source::Port, Destination::Register(Register::ACC)),
        28 => Instruction::MOV(Source::Register(Register::NIL), Destination::Register(Register::ACC)),
        29 => Instruction::MOV(Source::Register(Register::ACC), Destination::Register(Register::ACC)),
        30 => Instruction::MOV(Source::Literal(0), Destination::Register(Register::ACC)),
        31 => Instruction::MOV(Source::Literal(1), Destination::Register(Register::ACC)),
        32 => Instruction::MOV(Source::Literal(-1), Destination::Register(Register::ACC)),
        _ => Instruction::NOP,
    }
}

#[cfg(test)]
mod tests {
    use super::super::TIS_100::{Program,Instruction,Source,Destination,Register};
    use super::*;

    #[test]
    fn should_iterate_over_programs() {
        let programs: Vec<Program> = ProgramIterator::new().take(34).collect();

        assert_eq!(Program(vec![]), programs[0]);
        assert_eq!(Program(vec![Instruction::SWP]), programs[1]);
        assert_eq!(Program(vec![Instruction::SAV]), programs[2]);
        assert_eq!(Program(vec![
            Instruction::MOV(
                Source::Literal(-1),
                Destination::Register(Register::ACC)),
        ]), programs[32]);
        assert_eq!(Program(vec![
            Instruction::NOP,
            Instruction::SWP,
        ]), programs[33])
    }
}
<commit_msg>Reduce elementaire program iterator<commit_after>//! The `optimizer` module contains iterators that allow a user to iterate over
//! `Program`s in order of increasing content
//!
//! The content is a measure of how complex a Program is.

use std::ops::{Rem, Div};
use super::TIS_100::{Program, Instruction, Source, Destination, Register};

/// Allows one to determine the content of certain constructs, e.g. `Program`s
/// or `Instruction`s
pub trait Content {
    /// the content of the construct
    fn content(&self) -> u32;
}

impl Content for Program {
    fn content(&self) -> u32 {
        let Program(ref instructions) = *self;

        instructions.iter().fold(0 as u32, |sum, instruction| sum + instruction.content())
    }
}

impl Content for Instruction {
    fn content(&self) -> u32 {
        match *self {
            Instruction::NOP => 1,
            Instruction::MOV(ref source, ref destination) => 1 + source.content() + destination.content(),
            Instruction::SWP => 1,
            Instruction::SAV => 1,
            Instruction::ADD(ref source) => 1 + source.content(),
            Instruction::SUB(ref source) => 1 + source.content(),
        }
    }
}

impl Content for Source {
    fn content(&self) -> u32 {
        match *self {
            Source::Literal(value) => 1 + (value.abs() as u32),
            _ => 1,
        }
    }
}

impl Content for Destination {
    fn content(&self) -> u32 {
        1
    }
}

/// Iterator over Programs
pub struct ProgramIterator {
    current: u32,
}

impl ProgramIterator {
    /// Create a `ProgramIterator`
    pub fn new() -> ProgramIterator {
        ProgramIterator { current: 0 }
    }
}

impl Iterator for ProgramIterator {
    type Item = Program;

    fn next(&mut self) -> Option<Program> {
        let instructions: Vec<Instruction> =
            digits_of(self.current, 18)
            .iter()
            .map(|digit| instruction_map(*digit))
            .collect();
        self.current += 1;
        Some(Program(instructions))
    }
}

fn digits_of(mut n: u32, base: u32) -> Vec<u32> {
    let mut digits: Vec<u32> = vec![];
    loop {
        digits.push(n.rem(base));
        n = n.div(base);
        if n == 0 { break; }
    }
    digits
}

fn instruction_map(index: u32) -> Instruction {
    match index.rem(18) {
        0 => Instruction::SWP,
        1 => Instruction::SAV,
        2 => Instruction::ADD(Source::Port),
        3 => Instruction::ADD(Source::Register(Register::ACC)),
        4 => Instruction::ADD(Source::Literal(1)),
        5 => Instruction::SUB(Source::Port),
        6 => Instruction::SUB(Source::Register(Register::ACC)),
        7 => Instruction::SUB(Source::Literal(1)),
        8 => Instruction::MOV(Source::Port, Destination::Port),
        9 => Instruction::MOV(Source::Register(Register::ACC), Destination::Port),
        10 => Instruction::MOV(Source::Literal(0), Destination::Port),
        11 => Instruction::MOV(Source::Literal(1), Destination::Port),
        12 => Instruction::MOV(Source::Literal(-1), Destination::Port),
        13 => Instruction::MOV(Source::Port, Destination::Register(Register::ACC)),
        14 => Instruction::MOV(Source::Register(Register::ACC), Destination::Register(Register::ACC)),
        15 => Instruction::MOV(Source::Literal(0), Destination::Register(Register::ACC)),
        16 => Instruction::MOV(Source::Literal(1), Destination::Register(Register::ACC)),
        17 => Instruction::MOV(Source::Literal(-1), Destination::Register(Register::ACC)),
        _ => Instruction::NOP,
    }
}

#[cfg(test)]
mod tests {
    use super::super::TIS_100::{Program,Instruction,Source,Destination,Register};
    use super::*;

    #[test]
    fn should_iterate_over_programs() {
        let programs: Vec<Program> = ProgramIterator::new().take(20).collect();

        assert_eq!(Program(vec![Instruction::SWP]), programs[0]);
        assert_eq!(Program(vec![Instruction::SAV]), programs[1]);
        assert_eq!(Program(vec![
            Instruction::MOV(
                Source::Literal(-1),
                Destination::Register(Register::ACC)),
        ]), programs[17]);
        assert_eq!(Program(vec![
            Instruction::SWP,
            Instruction::SAV,
        ]), programs[18])
    }
}
<|endoftext|>


<commit_before>//! A build dependency for Cargo libraries to find libraries in a `vcpkg` tree.
//!
//! A number of environment variables are available to globally configure which
//! libraries are selected.
//!
//! * `VCPKG_ROOT` - Set the directory to look in for a vcpkg installation. If
//! it is not set, vcpkg will use the user-wide installation if one has been
//! set up with `vcpkg integrate install`
//!
//! * `FOO_NO_VCPKG` - if set, vcpkg will not attempt to find the
//! library named `foo`.
//!
//! There are also a number of environment variables which can configure how a
//! library is linked to (dynamically vs statically). These variables control
//! whether the `--static` flag is passed. Note that this behavior can be
//! overridden by configuring explicitly on `Config`. The variables are checked
//! in the following order:
//!
//! * `FOO_STATIC` - find the static version of `foo`
//! * `FOO_DYNAMIC` - find the dll version of  `foo`
//! * `VCPKG_ALL_STATIC` - find the static version of all libraries
//! * `VCPKG_ALL_DYNAMIC` - find the dll version of all libraries
//!
//! If the search was successful all appropriate Cargo metadata will be printed
//! on stdout.
//!
//! This cargo build helper is derived from and intended to work like the
//! `pkg-config` build helper to the extent that is possible, but `pkg-config`
//! the tool has functionality that `vcpkg` does not. In particular, vcpkg
//! does not allow mapping from a package name to the libs that it provides,
//! so this build helper must be called once for each library that is required
//! rather than once for the overall package. A better interface is no doubt
//! possible.
//!
//! There is a companion crate `vcpkg_cli` that allows testing of environment
//! and flag combinations.
//!
//! ```no_run
//! C:\src> vcpkg_cli probe -l static mysqlclient
//! Found library mysqlclient
//! Include paths:
//!         C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\include
//! Library paths:
//!         C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\lib
//! Cargo metadata:
//!         cargo:rustc-link-search=native=C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\lib
//!         cargo:rustc-link-lib=static=mysqlclient
//! ```

use std::ascii::AsciiExt;
use std::env;
use std::error;
use std::fs::File;
use std::fmt;
use std::io::{BufRead, BufReader};
use std::path::{PathBuf, Path};

// #[derive(Clone)]
pub struct Config {
    statik: Option<bool>,
    cargo_metadata: bool,
}

#[derive(Debug)]
pub struct Library {
    pub link_paths: Vec<PathBuf>,
    pub include_paths: Vec<PathBuf>,
    pub cargo_metadata: Vec<String>,
}

enum MSVCTarget {
    X86,
    X64,
}

impl fmt::Display for MSVCTarget {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MSVCTarget::X86 => write!(f, "x86-windows"),
            MSVCTarget::X64 => write!(f, "x64-windows"),
        }
    }
}

#[derive(Debug)] // need Display?
pub enum Error {
    /// Aborted because of `*_NO_VCPKG` environment variable.
    ///
    /// Contains the name of the responsible environment variable.
    EnvNoPkgConfig(String),

    /// Only MSVC ABI is supported
    NotMSVC,

    /// Can't find a vcpkg tree
    VcpkgNotFound(String),

    /// Library not found in vcpkg tree
    LibNotFound(String),

    #[doc(hidden)]
    __Nonexhaustive,
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::EnvNoPkgConfig(_) => "vcpkg requested to be aborted",
            Error::NotMSVC => "vcpkg only can only find libraries for MSVC ABI 64 bit builds",
            Error::VcpkgNotFound(_) => "could not find vcpkg tree",
            Error::LibNotFound(_) => "could not find library in vcpkg tree",
            Error::__Nonexhaustive => panic!(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            // Error::Command { ref cause, .. } => Some(cause),
            _ => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name),
            Error::NotMSVC => {
                write!(f,
                       "this vcpkg build helper can only find libraries built for the MSVC ABI.")
            }
            Error::VcpkgNotFound(ref detail) => write!(f, "Could not find vcpkg tree: {}", detail),
            Error::LibNotFound(ref detail) => {
                write!(f, "Could not find library in vcpkg tree {}", detail)
            }
            Error::__Nonexhaustive => panic!(),
        }
    }
}

pub fn probe_library(name: &str) -> Result<Library, Error> {
    Config::new().probe(name)
}

fn find_vcpkg_root() -> Result<PathBuf, Error> {

    // prefer the setting from the environment is there is one
    if let Some(path) = env::var_os("VCPKG_ROOT") {
        return Ok(PathBuf::from(path));
    }

    // see if there is a per-user vcpkg tree that has been integrated into msbuild
    // using `vcpkg integrate install`
    let local_app_data = env::var("LOCALAPPDATA").map_err(|_| Error::NotMSVC)?; // not present or can't utf8
    let vcpkg_user_targets_path =
        Path::new(local_app_data.as_str()).join("vcpkg").join("vcpkg.user.targets");

    let file = File::open(vcpkg_user_targets_path).map_err(|_| Error::NotMSVC)?; // TODO:
    let file = BufReader::new(&file);

    for line in file.lines() {
        let line = line.map_err(|_| Error::NotMSVC)?;
        let mut split = line.split("Project=\"");
        split.next(); // eat anything before Project="
        if let Some(found) = split.next() {
            // " is illegal in a Windows pathname
            if let Some(found) = found.split_terminator("\"").next() {
                return Ok(PathBuf::from(found));
            }
        }
    }

    Err(Error::NotMSVC)
}

fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {

    let mut vcpkg_root_path = path.clone();
    vcpkg_root_path.push(".vcpkg-root");

    if vcpkg_root_path.exists() {
        Ok(())
    } else {
        Err(Error::NotMSVC)
    }
}

impl Config {
    pub fn new() -> Config {
        Config {
            statik: None,
            cargo_metadata: true,
        }
    }

    /// Indicate whether to look for a static lib.
    ///
    /// This will override the inference from environment variables described in
    /// the crate documentation.
    pub fn statik(&mut self, statik: bool) -> &mut Config {
        self.statik = Some(statik);
        self
    }

    /// Define whether metadata should be emitted for cargo allowing it to
    /// automatically link the binary. Defaults to `true`.
    pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
        self.cargo_metadata = cargo_metadata;
        self
    }

    /// Find the library `name` in a vcpkg tree.
    ///
    /// This will use all configuration previously set to select the
    /// architecture and linkage.
    pub fn probe(&self, name: &str) -> Result<Library, Error> {

        let abort_var_name = format!("{}_NO_VCPKG", envify(name));
        if env::var_os(&abort_var_name).is_some() {
            return Err(Error::EnvNoPkgConfig(abort_var_name));
        }

        let msvc_arch = msvc_target()?;

        let vcpkg_root = find_vcpkg_root()?;
        validate_vcpkg_root(&vcpkg_root)?;

        let mut lib = Library::new();

        let static_lib = self.is_static(name);

        let mut base = vcpkg_root;
        base.push("installed");
        let static_appendage = if static_lib {
            "-static"
        } else {
            ""
        };

        base.push(format!("{}{}", msvc_arch.to_string(), static_appendage));

        let lib_path = base.join("lib");
        let include_path = base.join("include");
        lib.cargo_metadata
            .push(format!("cargo:rustc-link-search=native={}",
                          lib_path.to_str().expect("failed to convert string type")));

        if static_lib {
            lib.cargo_metadata.push(format!("cargo:rustc-link-lib=static={}", name));
        } else {
            lib.cargo_metadata.push(format!("cargo:rustc-link-lib={}", name));
        }

        lib.include_paths.push(include_path);
        lib.link_paths.push(lib_path.clone());

        // actually verify that the library exists
        let mut lib_location = PathBuf::from(lib_path);
        lib_location.push(name);
        lib_location.set_extension("lib");
        println!("{:?}", lib_location);
        if !lib_location.exists() {

            return Err(Error::NotMSVC);
        }

        if self.cargo_metadata {
            for line in &lib.cargo_metadata {
                println!("{}", line);
            }
        }

        Ok(lib)
    }

    fn is_static(&self, name: &str) -> bool {
        self.statik.unwrap_or_else(|| infer_static(name))
    }
}

impl Library {
    pub fn new() -> Library {
        Library {
            include_paths: Vec::new(),
            link_paths: Vec::new(),
            cargo_metadata: Vec::new(),
        }
    }
}

fn infer_static(name: &str) -> bool {
    let name = envify(name);
    if env::var_os(&format!("{}_STATIC", name)).is_some() {
        true
    } else if env::var_os(&format!("{}_DYNAMIC", name)).is_some() {
        false
    } else if env::var_os("VCPKG_ALL_STATIC").is_some() {
        true
    } else if env::var_os("VCPKG_ALL_DYNAMIC").is_some() {
        false
    } else {
        false
    }
}

fn envify(name: &str) -> String {
    name.chars()
        .map(|c| c.to_ascii_uppercase())
        .map(|c| {
            if c == '-' {
                '_'
            } else {
                c
            }
        })
        .collect()
}

fn msvc_target() -> Result<MSVCTarget, Error> {
    let target = env::var("TARGET").unwrap_or(String::new());
    if !target.contains("-pc-windows-msvc") {
        Err(Error::NotMSVC)
    } else if target.starts_with("x86_64-") {
        Ok(MSVCTarget::X64)
    } else {
        // everything else is x86
        Ok(MSVCTarget::X86)
    }
}<commit_msg>add Config::lib_name() and Config::lib_names()<commit_after>//! A build dependency for Cargo libraries to find libraries in a `vcpkg` tree.
//!
//! A number of environment variables are available to globally configure which
//! libraries are selected.
//!
//! * `VCPKG_ROOT` - Set the directory to look in for a vcpkg installation. If
//! it is not set, vcpkg will use the user-wide installation if one has been
//! set up with `vcpkg integrate install`
//!
//! * `FOO_NO_VCPKG` - if set, vcpkg will not attempt to find the
//! library named `foo`.
//!
//! There are also a number of environment variables which can configure how a
//! library is linked to (dynamically vs statically). These variables control
//! whether the `--static` flag is passed. Note that this behavior can be
//! overridden by configuring explicitly on `Config`. The variables are checked
//! in the following order:
//!
//! * `FOO_STATIC` - find the static version of `foo`
//! * `FOO_DYNAMIC` - find the dll version of  `foo`
//! * `VCPKG_ALL_STATIC` - find the static version of all libraries
//! * `VCPKG_ALL_DYNAMIC` - find the dll version of all libraries
//!
//! If the search was successful all appropriate Cargo metadata will be printed
//! on stdout.
//!
//! This cargo build helper is derived from and intended to work like the
//! `pkg-config` build helper to the extent that is possible, but `pkg-config`
//! the tool has functionality that `vcpkg` does not. In particular, vcpkg
//! does not allow mapping from a package name to the libs that it provides,
//! so this build helper must be called once for each library that is required
//! rather than once for the overall package. A better interface is no doubt
//! possible.
//!
//! There is a companion crate `vcpkg_cli` that allows testing of environment
//! and flag combinations.
//!
//! ```no_run
//! C:\src> vcpkg_cli probe -l static mysqlclient
//! Found library mysqlclient
//! Include paths:
//!         C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\include
//! Library paths:
//!         C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\lib
//! Cargo metadata:
//!         cargo:rustc-link-search=native=C:\src\diesel_build\vcpkg-dll\installed\x64-windows-static\lib
//!         cargo:rustc-link-lib=static=mysqlclient
//! ```

use std::ascii::AsciiExt;
use std::env;
use std::error;
use std::fs::{self, File};
use std::fmt;
use std::io::{BufRead, BufReader};
use std::path::{PathBuf, Path};

// #[derive(Clone)]
pub struct Config {
    statik: Option<bool>,
    cargo_metadata: bool,
    required_libs: Vec<LibNames>, // copy_to_target: bool,
}

#[derive(Debug)]
pub struct Library {
    pub link_paths: Vec<PathBuf>,
    pub include_paths: Vec<PathBuf>,
    pub cargo_metadata: Vec<String>,

    /// libraries found are static
    pub is_static: bool,
    pub found_dlls: Vec<PathBuf>,
}

enum MSVCTarget {
    X86,
    X64,
}

impl fmt::Display for MSVCTarget {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            MSVCTarget::X86 => write!(f, "x86-windows"),
            MSVCTarget::X64 => write!(f, "x64-windows"),
        }
    }
}

#[derive(Debug)] // need Display?
pub enum Error {
    /// Aborted because of `*_NO_VCPKG` environment variable.
    ///
    /// Contains the name of the responsible environment variable.
    EnvNoPkgConfig(String),

    /// Only MSVC ABI is supported
    NotMSVC,

    /// Can't find a vcpkg tree
    VcpkgNotFound(String),

    /// Library not found in vcpkg tree
    LibNotFound(String),

    #[doc(hidden)]
    __Nonexhaustive,
}

impl error::Error for Error {
    fn description(&self) -> &str {
        match *self {
            Error::EnvNoPkgConfig(_) => "vcpkg requested to be aborted",
            Error::NotMSVC => "vcpkg only can only find libraries for MSVC ABI 64 bit builds",
            Error::VcpkgNotFound(_) => "could not find vcpkg tree",
            Error::LibNotFound(_) => "could not find library in vcpkg tree",
            // Error::LibNotFound(_) => "could not find library in vcpkg tree",
            Error::__Nonexhaustive => panic!(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            // Error::Command { ref cause, .. } => Some(cause),
            _ => None,
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match *self {
            Error::EnvNoPkgConfig(ref name) => write!(f, "Aborted because {} is set", name),
            Error::NotMSVC => {
                write!(f,
                       "this vcpkg build helper can only find libraries built for the MSVC ABI.")
            }
            Error::VcpkgNotFound(ref detail) => write!(f, "Could not find vcpkg tree: {}", detail),
            Error::LibNotFound(ref detail) => {
                write!(f, "Could not find library in vcpkg tree {}", detail)
            }
            Error::__Nonexhaustive => panic!(),
        }
    }
}

pub fn probe_library(name: &str) -> Result<Library, Error> {
    Config::new().probe(name)
}

fn find_vcpkg_root() -> Result<PathBuf, Error> {

    // prefer the setting from the environment is there is one
    if let Some(path) = env::var_os("VCPKG_ROOT") {
        return Ok(PathBuf::from(path));
    }

    // see if there is a per-user vcpkg tree that has been integrated into msbuild
    // using `vcpkg integrate install`
    let local_app_data = env::var("LOCALAPPDATA")
        .map_err(|_| {
            Error::VcpkgNotFound("Failed to read LOCALAPPDATA environment variable".to_string())
        })?; // not present or can't utf8
    let vcpkg_user_targets_path =
        Path::new(local_app_data.as_str()).join("vcpkg").join("vcpkg.user.targets");

    let file = File::open(vcpkg_user_targets_path.clone())
        .map_err(|_| {
            Error::VcpkgNotFound("No vcpkg.user.targets found. run 'vcpkg integrate install' or \
                                  set VCPKG_ROOT environment variable."
                .to_string())
        })?;
    let file = BufReader::new(&file);

    for line in file.lines() {
        let line = line.map_err(|_| {
                Error::VcpkgNotFound(format!("Parsing of {} failed.",
                                             vcpkg_user_targets_path.to_string_lossy().to_owned()))
            })?;
        let mut split = line.split("Project=\"");
        split.next(); // eat anything before Project="
        if let Some(found) = split.next() {
            // " is illegal in a Windows pathname
            if let Some(found) = found.split_terminator("\"").next() {
                let mut vcpkg_root = PathBuf::from(found);
                if !(vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop() && vcpkg_root.pop()) {
                    return Err(Error::VcpkgNotFound(format!("Could not find vcpkg root above {}",
                                                            found)));
                }
                return Ok(vcpkg_root);
            }
        }
    }

    Err(Error::VcpkgNotFound(format!("Project location not found parsing {}.",
                                     vcpkg_user_targets_path.to_string_lossy().to_owned())))
}

fn validate_vcpkg_root(path: &PathBuf) -> Result<(), Error> {

    let mut vcpkg_root_path = path.clone();
    vcpkg_root_path.push(".vcpkg-root");

    if vcpkg_root_path.exists() {
        Ok(())
    } else {
        Err(Error::VcpkgNotFound(format!("Could not find vcpkg root at {}",
                                         vcpkg_root_path.to_string_lossy())))
    }
}

/// names of the libraries
struct LibNames {
    lib_stem: String,
    dll_stem: String,
}

impl Config {
    pub fn new() -> Config {
        Config {
            // override environment selection of static or dll
            statik: None,

            // should the cargo metadata actually be emitted
            cargo_metadata: true,
/*
            // should include lines be included in the cargo metadata

            //include_includes: false,
*/
            required_libs: Vec::new(),

           // copy_to_target: false,
        }
    }

    /// Indicate whether to look for a static lib.
    ///
    /// This will override the inference from environment variables described in
    /// the crate documentation.
    pub fn statik(&mut self, statik: bool) -> &mut Config {
        self.statik = Some(statik);
        self
    }

    /// Override the name of the library to look for if it differs from the package name.
    ///
    /// This may be called more than once if multiple libs are required.
    /// All libs must be found for the probe to succeed. `.probe()` must
    /// be run with a different configuration to look for libraries under one of several names.
    /// `.libname("ssleay32")` will look for ssleay32.lib and also ssleay32.dll if
    /// dynamic linking is selected.
    pub fn lib_name(&mut self, lib_stem: &str) -> &mut Config {
        self.required_libs.push(LibNames {
            lib_stem: lib_stem.to_owned(),
            dll_stem: lib_stem.to_owned(),
        });
        self
    }

    /// Override the name of the library to look for if it differs from the package name.
    ///
    /// This may be called more than once if multiple libs are required.
    /// All libs must be found for the probe to succeed. `.probe()` must
    /// be run with a different configuration to look for libraries under one of several names.
    /// `.lib_names("libcurl_imp","curl")` will look for libcurl_imp.lib and also curl.dll if
    /// dynamic linking is selected.
    pub fn lib_names(&mut self, lib_stem: &str, dll_stem: &str) -> &mut Config {
        self.required_libs.push(LibNames {
            lib_stem: lib_stem.to_owned(),
            dll_stem: dll_stem.to_owned(),
        });
        self
    }

    /// Define whether metadata should be emitted for cargo allowing it to
    /// automatically link the binary. Defaults to `true`.
    pub fn cargo_metadata(&mut self, cargo_metadata: bool) -> &mut Config {
        self.cargo_metadata = cargo_metadata;
        self
    }

    /// Find the library `port_name` in a vcpkg tree.
    ///
    /// This will use all configuration previously set to select the
    /// architecture and linkage.
    pub fn probe(&mut self, port_name: &str) -> Result<Library, Error> {

        // if no overrides have been selected, then the vcpkg port name
        // is the the .lib name and the .dll name
        if self.required_libs.is_empty() {
            self.required_libs.push(LibNames {
                lib_stem: port_name.to_owned(),
                dll_stem: port_name.to_owned(),
            });
        }

        let abort_var_name = format!("{}_NO_VCPKG", envify(port_name));
        if env::var_os(&abort_var_name).is_some() {
            return Err(Error::EnvNoPkgConfig(abort_var_name));
        }

        let msvc_arch = msvc_target()?;

        let vcpkg_root = find_vcpkg_root()?;
        validate_vcpkg_root(&vcpkg_root)?;

        let static_lib = self.is_static(port_name);

        let mut lib = Library::new(static_lib);

        let mut base = vcpkg_root;
        base.push("installed");
        let static_appendage = if static_lib {
            "-static"
        } else {
            ""
        };

        let vcpkg_triple = format!("{}{}", msvc_arch.to_string(), static_appendage);
        base.push(vcpkg_triple);

        let lib_path = base.join("lib");
        let bin_path = base.join("bin");
        let include_path = base.join("include");
        lib.cargo_metadata
            .push(format!("cargo:rustc-link-search=native={}",
                          lib_path.to_str().expect("failed to convert string type")));
        if !static_lib {
            lib.cargo_metadata
                .push(format!("cargo:rustc-link-search=native={}",
                              bin_path.to_str().expect("failed to convert string type")));
        }
        lib.include_paths.push(include_path);
        lib.link_paths.push(lib_path.clone());
        drop(port_name);
        for required_lib in &self.required_libs {
            if static_lib {
                lib.cargo_metadata
                    .push(format!("cargo:rustc-link-lib=static={}", required_lib.lib_stem));
            } else {
                lib.cargo_metadata.push(format!("cargo:rustc-link-lib={}", required_lib.lib_stem));
            }

            // verify that the library exists
            let mut lib_location = PathBuf::from(lib_path.clone());
            lib_location.push(required_lib.lib_stem.clone());
            lib_location.set_extension("lib");

            if !lib_location.exists() {
                return Err(Error::LibNotFound(lib_location.to_string_lossy().into()));
            }

            // verify that the DLL exists
            if !static_lib {
                let mut lib_location = PathBuf::from(bin_path.clone());
                lib_location.push(required_lib.dll_stem.clone());
                lib_location.set_extension("dll");

                if !lib_location.exists() {
                    return Err(Error::LibNotFound(lib_location.to_string_lossy().into()));
                }
                lib.found_dlls.push(lib_location);
            }
        }

        // if self.copy_to_target {
        //     if let Some(target_dir) = env::var_os("OUT_DIR") {
        //         for file in &lib.found_dlls {
        //             let mut dest_path = Path::new(target_dir.as_os_str()).to_path_buf();
        //             dest_path.push(Path::new(file.file_name().unwrap()));
        //             fs::copy(file, &dest_path)
        //                 .map_err(|_| {
        //                     Error::LibNotFound(format!("Can't copy file {} to {}",
        //                                                file.to_string_lossy(),
        //                                                dest_path.to_string_lossy()))
        //                 })?;

        //             println!("warning: copied {} to {}",
        //                      file.to_string_lossy(),
        //                      dest_path.to_string_lossy());
        //         }
        //     } else {
        //         return Err(Error::LibNotFound("Can't copy file".to_owned())); // TODO:
        //     }
        // }

        if self.cargo_metadata {
            for line in &lib.cargo_metadata {
                println!("{}", line);
            }
        }
        Ok(lib)
    }

    fn is_static(&self, name: &str) -> bool {
        self.statik.unwrap_or_else(|| infer_static(name))
    }
}

impl Library {
    pub fn new(is_static: bool) -> Library {
        Library {
            include_paths: Vec::new(),
            link_paths: Vec::new(),
            cargo_metadata: Vec::new(),
            is_static: is_static,
            found_dlls: Vec::new(),
        }
    }
}

fn infer_static(name: &str) -> bool {
    let name = envify(name);
    if env::var_os(&format!("{}_STATIC", name)).is_some() {
        true
    } else if env::var_os(&format!("{}_DYNAMIC", name)).is_some() {
        false
    } else if env::var_os("VCPKG_ALL_STATIC").is_some() {
        true
    } else if env::var_os("VCPKG_ALL_DYNAMIC").is_some() {
        false
    } else {
        true
    }
}

fn envify(name: &str) -> String {
    name.chars()
        .map(|c| c.to_ascii_uppercase())
        .map(|c| {
            if c == '-' {
                '_'
            } else {
                c
            }
        })
        .collect()
}

fn msvc_target() -> Result<MSVCTarget, Error> {
    let target = env::var("TARGET").unwrap_or(String::new());
    if !target.contains("-pc-windows-msvc") {
        Err(Error::NotMSVC)
    } else if target.starts_with("x86_64-") {
        Ok(MSVCTarget::X64)
    } else {
        // everything else is x86
        Ok(MSVCTarget::X86)
    }
}<|endoftext|>