rusty-tags 2.4.0

Create ctags/etags for a cargo project and all of its dependencies
extern crate toml;
extern crate rustc_serialize;
extern crate tempfile;
extern crate num_cpus;
extern crate scoped_threadpool;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate lazy_static;

#[macro_use(defer)]
extern crate scopeguard;

use std::path::Path;
use std::io::{self, Write};
use std::process::Command;
use std::env;
use std::fs::{File, remove_file};
use tempfile::NamedTempFile;
use rustc_serialize::json::Json;

use rt_result::RtResult;
use dependencies::dependency_trees;
use tags::{update_tags, create_tags, move_tags};
use config::Config;
use dirs::rusty_tags_locks_dir;

mod rt_result;
mod dependencies;
mod dirs;
mod tags;
mod types;
mod config;

fn main() {
    execute().unwrap_or_else(|err| {
        writeln!(&mut io::stderr(), "{}", err).unwrap();
        std::process::exit(1);
    });
}

fn execute() -> RtResult<()> {
    let config = Config::from_command_args()?;
    update_all_tags(&config)?;
    Ok(())
}

fn update_all_tags(config: &Config) -> RtResult<()> {
    let metadata = fetch_source_and_metadata(&config)?;
    update_std_lib_tags(&config)?;

    let dep_trees = dependency_trees(&config, &metadata)?;
    for tree in &dep_trees {
        let lock_file = rusty_tags_locks_dir()?.join(tree.source.hash());
        if lock_file.is_file() {
            if ! config.quiet {
                println!("Already creating tags for '{}', if this isn't the case remove the lock file '{}'",
                         tree.source.name, lock_file.display());
            }

            continue;
        }

        File::create(&lock_file)?;
        defer! {
            if lock_file.is_file() {
                let _ = remove_file(&lock_file);
            }
        };

        if ! config.quiet {
            println!("Creating tags for '{}' ...", tree.source.name);
        }

        update_tags(&config, &tree)?;
    }

    Ok(())
}

fn fetch_source_and_metadata(config: &Config) -> RtResult<Json> {
    if ! config.quiet {
        println!("Fetching source and metadata ...");
    }

    env::set_current_dir(&config.start_dir)?;

    let mut cmd = Command::new("cargo");
    cmd.arg("metadata");

    let output = cmd.output()
        .map_err(|err| format!("'cargo' execution failed: {}\nIs 'cargo' correctly installed?", err))?;

    if ! output.status.success() {
        let mut msg = String::from_utf8_lossy(&output.stderr).into_owned();
        if msg.is_empty() {
            msg = String::from_utf8_lossy(&output.stdout).into_owned();
        }

        return Err(msg.into());
    }

    Ok(Json::from_str(&String::from_utf8_lossy(&output.stdout))?)
}

fn update_std_lib_tags(config: &Config) -> RtResult<()> {
    let src_path_str = env::var("RUST_SRC_PATH");
    if ! src_path_str.is_ok() {
        return Ok(());
    }

    let src_path_str = src_path_str.unwrap();
    let src_path = Path::new(&src_path_str);
    if ! src_path.is_dir() {
        return Err(format!("Missing rust source code at '{}'!", src_path.display()).into());
    }

    let std_lib_tags = src_path.join(config.tags_spec.file_name());
    if std_lib_tags.is_file() && ! config.force_recreate {
        return Ok(());
    }

    let possible_src_dirs = [
        "liballoc",
        "libarena",
        "libbacktrace",
        "libcollections",
        "libcore",
        "libflate",
        "libfmt_macros",
        "libgetopts",
        "libgraphviz",
        "liblog",
        "librand",
        "librbml",
        "libserialize",
        "libstd",
        "libsyntax",
        "libterm"
    ];

    let mut src_dirs = Vec::new();
    for dir in &possible_src_dirs {
        let src_dir = src_path.join(&dir);
        if src_dir.is_dir() {
            src_dirs.push(src_dir);
        }
    }

    if ! config.quiet {
        println!("Creating tags for the standard library ...");
    }

    let tmp_std_lib_tags = NamedTempFile::new_in(&src_path)?;
    create_tags(config, &src_dirs, tmp_std_lib_tags.path())?;
    move_tags(config, tmp_std_lib_tags.path(), &std_lib_tags)?;

    Ok(())
}