deadnix 0.1.0

Find unused code in Nix projects
use std::fs;

mod scope;
mod binding;
mod usage;
mod dead_code;
mod dead_code_tests;
mod report;
mod edit;
mod edit_tests;


fn main() {
    let matches = clap::App::new("deadnix")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Astro <astro@spaceboyz.net>")
        .about("Find dead code in .nix files")
        .arg(clap::Arg::with_name("NO_LAMBDA_ARG")
             .short("l")
             .long("no-lambda-arg")
             .help("Don't check lambda parameter arguments")
        )
        .arg(clap::Arg::with_name("NO_UNDERSCORE")
             .short("_")
             .long("no-underscore")
             .help("Don't check any bindings that start with a _")
        )
        .arg(clap::Arg::with_name("QUIET")
             .short("q")
             .long("quiet")
             .help("Don't print dead code report")
        )
        .arg(clap::Arg::with_name("EDIT")
             .short("e")
             .long("edit")
             .help("Remove unused code and write to source file")
        )
        .arg(clap::Arg::with_name("FILE_PATHS")
             .multiple(true)
             .help(".nix files, or directories with .nix files inside")
        )
        .get_matches();

    let settings = dead_code::Settings {
        no_lambda_arg: matches.is_present("NO_LAMBDA_ARG"),
        no_underscore: matches.is_present("NO_UNDERSCORE"),
    };
    let quiet = matches.is_present("QUIET");
    let edit = matches.is_present("EDIT");

    let file_paths = matches.values_of("FILE_PATHS")
        .expect("FILE_PATHS");
    let files = file_paths.flat_map(|path| {
        let meta = fs::metadata(path)
            .expect("fs::metadata");
        let files: Box<dyn Iterator<Item = String>> =
            if meta.is_dir() {
                Box::new(
                    walkdir::WalkDir::new(path)
                        .into_iter()
                        .map(|result| result.unwrap().path().display().to_string())
                        .filter(|path| path.ends_with(".nix"))
                )
            } else {
                Box::new(
                    Some(path.to_string()).into_iter()
                )
            };
        files
    });
    for file in files {
        let content = match fs::read_to_string(&file) {
            Ok(content) => content,
            Err(err) => {
                eprintln!("Error reading file {}: {}", file, err);
                continue;
            }
        };

        let ast = rnix::parse(&content);
        let mut failed = false;
        for error in ast.errors() {
            eprintln!("Error parsing file {}: {}", file, error);
            failed = true;
        }
        if failed {
            continue;
        }

        let results = settings.find_dead_code(ast.node());
        if !quiet && results.len() > 0 {
            crate::report::Report::new(file.to_string(), &content, results.clone())
                .print();
        }
        if edit {
            let new_ast = crate::edit::edit_dead_code(
                &content,
                ast.node(),
                results.into_iter()
            );
            fs::write(file, new_ast)
                .expect("fs::write");
        }
    }
}