gxter-cli 0.2.0

A command-line utility creating and reading GTA 3/VC/SA GXT (text string) files
extern crate getopts;
use getopts::Options;
use gxter::GXTFile;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::BufReader; //for env::args()

#[cfg(feature = "pretty")]
mod pretty;

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} FILE [options]", program);
    print!("{}", opts.usage(&brief));
}

fn main() -> Result<(), gxter::GXTError> {
    let mut opts = Options::new();
    opts.optflag(
        "d",
        "decompile",
        "decompile a .gxt file into a text file, rather than the other way around",
    );
    opts.optopt(
        "n",
        "name-list",
        "when reading CRC32 hashes of string names, match against this list of names",
        "FILENAME",
    );

    #[cfg(feature = "pretty")]
    opts.optflag(
        "p",
        "pretty-print",
        "print the contents of a GXT or text file with color formatting",
    );

    opts.optopt("o", "output", "output file name", "NAME");
    opts.optopt("c", "character-table", "custom character table", "FILENAME");
    opts.optflag(
        "s",
        "sanitize-tags",
        "do not use the character table while reading tags",
    );
    opts.optflag(
        "K",
        "key-sort",
        "arrange strings in the same order as their keys",
    );
    opts.optflag(
        "O",
        "offset-sort",
        "arrange strings in the same order as their data locations",
    );
    opts.optflag("h", "help", "print this help menu");

    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();

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

    if matches.opt_present("h") {
        print_usage(&program, opts);
        return Ok(());
    }

    let decompile = matches.opt_present("d");

    #[cfg(feature = "pretty")]
    let do_pretty_print = matches.opt_present("p");

    let sanitize_tags = matches.opt_present("s");

    let input_filename = if !matches.free.is_empty() {
        //if we have any non-parsed arguments
        matches.free[0].clone() //treat the first of them as a file name
    } else {
        //otherwise
        print_usage(&program, opts); //return an error message
        return Ok(());
    };

    let data_ordering = if matches.opt_present("key-sort") {
        gxter::ImportOrdering::Key
    } else if matches.opt_present("offset-sort") {
        gxter::ImportOrdering::Offset
    } else {
        gxter::ImportOrdering::Native
    };

    let custom_table: Option<gxter::GXTCharacterTable> = match matches.opt_str("character-table") {
        Some(name) => {
            let _f = File::open(&name).expect("Unable to open character table file");
            let mut file = BufReader::new(_f);

            Some(gxter::read_custom_table(&mut file).unwrap())
        }
        None => None,
    };

    let name_list: Option<HashMap<u32, String>> = match matches.opt_str("name-list") {
        Some(name) => {
            let _f = File::open(&name).expect("Unable to open name list file");
            let mut file = BufReader::new(_f);

            Some(gxter::read_name_list(&mut file).unwrap())
        }
        None => None,
    };

    #[cfg(feature = "pretty")]
    if do_pretty_print {
        let gxt = if decompile {
            let _f = File::open(&input_filename).expect("Unable to open GXT file");
            let mut file = BufReader::new(_f);

            GXTFile::read_from_gxt(
                &mut file,
                &Some(data_ordering),
                &custom_table,
                &name_list,
                Some(sanitize_tags),
            )
            .expect("Unable to decompile GXT file")
        } else {
            let _f = File::open(&input_filename).expect("Unable to open text file");
            let mut file = BufReader::new(_f);

            GXTFile::read_from_text(&mut file).expect("Unable to decompile GXT file")
        };

        for (k, v) in gxt.main_table {
            pretty::pretty_print(&k, &v, &gxt.format).unwrap();
        }

        for (k, v) in gxt.aux_tables {
            println!("[{k}]");
            for (k, v) in v {
                pretty::pretty_print(&k, &v, &gxt.format).unwrap();
            }
            println!("");
        }

        return Ok(());
    }

    if decompile {
        let _f = File::open(&input_filename).expect("Unable to open GXT file");
        let mut file = BufReader::new(_f);

        let gxt = GXTFile::read_from_gxt(
            &mut file,
            &Some(data_ordering),
            &custom_table,
            &name_list,
            Some(sanitize_tags),
        )
        .expect("Unable to decompile GXT file");

        let output = matches.opt_str("o");
        match output {
            Some(ofn) => {
                let mut outfile = File::create(ofn).expect("Unable to open output file");
                gxt.write_to_text(&mut outfile).unwrap();
            }
            None => {
                let mut stdout = io::stdout();
                gxt.write_to_text(&mut stdout).unwrap();
            }
        };
        Ok(())
    } else {
        let output = matches.opt_str("o");

        match output {
            Some(ofn) => {
                let _f = File::open(&input_filename).expect("Unable to open text file");
                let mut file = BufReader::new(_f);

                let gxt = GXTFile::read_from_text(&mut file).expect("Unable to decompile GXT file");

                let mut outfile = File::create(ofn).expect("Unable to open output file");
                gxt.write_to_gxt(&mut outfile, &custom_table).unwrap();
            }
            None => {
                eprintln!("No output file name specified!");
            }
        }
        Ok(())
    }
}