/// This an example usage of the rustpython_compiler crate.
/// This program reads, parses, and compiles a file you provide
/// to RustPython bytecode, and then displays the output in the
/// `dis.dis` format.
///
/// example usage:
/// $ cargo run --release --example dis demo*.py

#[macro_use]
extern crate clap;
extern crate env_logger;
#[macro_use]
extern crate log;

use clap::{App, Arg};
use rustpython_compiler as compiler;
use std::error::Error;
use std::fs;
use std::path::Path;

fn main() {
    env_logger::init();
    let app = App::new("dis")
        .version(crate_version!())
        .author(crate_authors!())
        .about("Compiles and disassembles python script files for viewing their bytecode.")
        .arg(
            Arg::with_name("scripts")
                .help("Scripts to scan")
                .multiple(true)
                .required(true),
        )
        .arg(
            Arg::with_name("mode")
                .help("The mode to compile the scripts in")
                .long("mode")
                .short("m")
                .default_value("exec")
                .possible_values(&["exec", "single", "eval"])
                .takes_value(true),
        )
        .arg(
            Arg::with_name("no_expand")
                .help(
                    "Don't expand CodeObject LoadConst instructions to show \
                     the instructions inside",
                )
                .long("no-expand")
                .short("x"),
        )
        .arg(
            Arg::with_name("optimize")
                .help("The amount of optimization to apply to the compiled bytecode")
                .short("O")
                .multiple(true),
        );
    let matches = app.get_matches();

    let mode = matches.value_of_lossy("mode").unwrap().parse().unwrap();
    let expand_code_objects = !matches.is_present("no_expand");
    let optimize = matches.occurrences_of("optimize") as u8;
    let scripts = matches.values_of_os("scripts").unwrap();

    let opts = compiler::CompileOpts {
        optimize,
        ..Default::default()
    };

    for script in scripts.map(Path::new) {
        if script.exists() && script.is_file() {
            let res = display_script(script, mode, opts.clone(), expand_code_objects);
            if let Err(e) = res {
                error!("Error while compiling {:?}: {}", script, e);
            }
        } else {
            eprintln!("{script:?} is not a file.");
        }
    }
}

fn display_script(
    path: &Path,
    mode: compiler::Mode,
    opts: compiler::CompileOpts,
    expand_code_objects: bool,
) -> Result<(), Box<dyn Error>> {
    let source = fs::read_to_string(path)?;
    let code = compiler::compile(&source, mode, path.to_string_lossy().into_owned(), opts)?;
    println!("{}:", path.display());
    if expand_code_objects {
        println!("{}", code.display_expand_code_objects());
    } else {
        println!("{code}");
    }
    Ok(())
}
