decart 0.0.9

Octo cartridge encoder/decoder
Documentation
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use atty::Stream;
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
use colored_json::prelude::*;
use decart::{from_file, OctoCart};

use syntect::easy::HighlightLines;
use syntect::highlighting::{Style, ThemeSet};
use syntect::parsing::{SyntaxDefinition, SyntaxSetBuilder};
use syntect::util::{as_24_bit_terminal_escaped, LinesWithEndings};

pub fn main() {
    let matches = App::new(crate_name!())
        .version(crate_version!())
        .author(crate_authors!())
        .about(crate_description!())
        .subcommand(
            App::new("decode")
            .about("Decode an Octocart. If no options are supplied, the full JSON payload will be printed to stdout.")
            .arg(Arg::with_name("print program")
                .short('p')
                .long("print-program")
                .help("instead of printing the entire JSON payload to stdout, print just the Octo program source code.")
            )
            .arg(Arg::with_name("write files")
                .short('w')
                .long("write-files")
                .takes_value(true)
                .value_name("output file stem")
                .help("Instead of printing to stdout, create two files with the contents from the Octocart: \
                An .8o file with the program source code, and an .octo.rc file with the runtime options. \
                If you supply a value here, it will be used as the filenames (plus extensions); \
                if not, the filename of the Octocart will be used.")
                .min_values(0)
                .max_values(1)
            )
            .arg(
                Arg::with_name("OCTOCART")
                .help("Octo cartridge file (GIF)")
                .required(true)
                .value_name("OCTOCART")
            )
        )
        .subcommand(
            App::new("encode")
            .about("Encode an Octocart")
        )
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("decode") {
        let filename = Path::new(matches.value_of("OCTOCART").unwrap());
        let cart: OctoCart = from_file(filename).expect("Failed to read Octocart file");

        if matches.is_present("write files") {
            let path = filename.parent().unwrap();
            let stem = filename.file_stem().unwrap();
            let default_base = format!("{}/{}", path.display(), stem.to_str().unwrap());
            let base = matches.value_of("write files").unwrap_or(&default_base);
            let octo_file_path = format!("{}.{}", base, "8o");
            let mut octo_file = File::create(Path::new(&octo_file_path))
                .unwrap_or_else(|_| panic!("Failed to create {}", octo_file_path));
            octo_file
                .write_all(cart.program.as_bytes())
                .unwrap_or_else(|_| panic!("Failed to write program to {}", octo_file_path));
            let rc_file_path = format!("{}.{}", base, "octo.rc");
            let mut rc_file = File::create(Path::new(&rc_file_path))
                .unwrap_or_else(|_| panic!("Failed to create {}", rc_file_path));
            rc_file
                .write_all(octopt::Options::to_ini(cart.options).as_bytes())
                .unwrap_or_else(|_| panic!("Failed to write options to {}", rc_file_path));

            println!(
                "Wrote files:\nCode:    {}\nOptions: {}",
                octo_file_path, rc_file_path
            );
        } else if matches.is_present("print program") {
            if atty::is(Stream::Stdout) {
                let mut ssb = SyntaxSetBuilder::new();
                ssb.add(
                    SyntaxDefinition::load_from_str(
                        include_str!("octo-sublime/Octo.sublime-syntax"),
                        false,
                        None,
                    )
                    .unwrap(),
                );
                let ps = ssb.build();
                let syntax = ps.find_syntax_by_extension("8o").unwrap();

                let mut theme_cursor = std::io::Cursor::new(include_bytes!("Monokai.tmTheme"));
                let theme = ThemeSet::load_from_reader(&mut theme_cursor).unwrap();

                let mut h = HighlightLines::new(syntax, &theme);
                let mut s = Vec::new();
                for line in LinesWithEndings::from(&cart.program) {
                    let ranges: Vec<(Style, &str)> =
                        h.highlight_line(line, &ps).expect("Highlighting failed");
                    let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
                    s.push(escaped);
                }
                println!("{}", s.join(""));
            } else {
                println!("{}", &cart.program);
            }
        } else {
            println!(
                "{}",
                cart.to_string()
                    .to_colored_json_auto()
                    .expect("Failed to print Octocart JSON payload")
            );
        }
    } else if let Some(_matches) = matches.subcommand_matches("encode") {
        todo!();
    }
}