hhh 1.0.1

The hhh Binary File Processor
Documentation
// hhh
// Copyright (c) 2023 by Stacy Prowell.  All rights reserved.
// https://gitlab.com/sprowell/hhh

//! Implement the command line interface.

use chrono::prelude::Utc;
use clap::Parser;
use hhh::configure_logging;
use hhh::directive_def::directives_help;
use hhh::directive_def::parse_and_do_directive;
use hhh::directive_def::set_groups;
use hhh::get_config_file;
use hhh::options::HhhArgs;
use hhh::read_configuration::read_configuration_file;
use hhh::read_hexdump;
use hhh::write_hexdump;
use log::debug;
use log::error;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::stdout;
use std::io::Write;
use std::process::ExitCode;
use trivet::Tools;

/// Entry point from the command line.
fn main() -> ExitCode {
    // Initialize logging.
    configure_logging();

    // Read the configuration file, if enabled.
    let mut args = HhhArgs::parse();
    debug!("Command line: {:?}", args);
    if !args.no_configuration_file {
        match get_config_file() {
            Err(error) => {
                error!("{}", error);
                return ExitCode::FAILURE;
            }
            Ok(path) => {
                debug!("Configuration file: {:?}", &path);
                match read_configuration_file(&path, &mut args) {
                    Ok(_) => {}
                    Err(error) => {
                        error!("{}", error);
                        return ExitCode::FAILURE;
                    }
                }
            }
        }
    }

    // Parse command line arguments.
    if args.groups_per_line == 0 {
        set_groups(&mut args);
    }
    args.bytes_per_group = 1.max(args.bytes_per_group);

    // See if the user wants a list of directives.
    if args.list_directives {
        directives_help();
        return ExitCode::SUCCESS;
    }

    // Execute any directives that were found, in order.  This consumes the directives.
    let directives = std::mem::take(&mut args.directives);
    for directive in directives {
        match parse_and_do_directive(&directive, &mut args) {
            Ok(None) => {}
            Ok(Some(msg)) => {
                error!("ERROR: {}", msg);
                return ExitCode::FAILURE;
            }
            Err(msg) => {
                error!("ERROR: {}", msg);
                return ExitCode::FAILURE;
            }
        }
    }

    // Figure out where the output should go.
    let sink = match args.output {
        Some(ref path) => match File::create(path) {
            Err(msg) => {
                error!("ERROR: Unable to create output file: {}", msg);
                return ExitCode::FAILURE;
            }
            Ok(file) => Box::new(file),
        },
        None => Box::new(stdout()) as Box<dyn Write>,
    };

    // Either generate or parse a hexdump.
    if args.parse {
        // Try to parse the input files and use them to generate a binary.  We parse all the input
        // files and then write the bytes to the output file or stream.
        if let Err(msg) = read_hexdump(args, sink) {
            error!("ERROR: Failed to parse hexdump: {}", msg);
            return ExitCode::FAILURE;
        };
        ExitCode::SUCCESS
    } else {
        let mut map = BTreeMap::new();
        let tools = Tools::new();
        if args.meta {
            // Add the standard metadata.
            let cmd = std::env::args()
                .skip(1)
                .map(|arg| format!("\"{}\"", tools.encode_string(&arg)))
                .collect::<Vec<_>>()
                .join(" ");
            map.insert("Command-Line".to_string(), cmd);
            map.insert("UTC".to_string(), Utc::now().to_string());

            // Handle metadata settings from the configuration.
            for (key, value) in &args.set_meta {
                if value.is_empty() {
                    // Remove this metadata item if it is present.
                    map.remove(key);
                } else {
                    // Insert this key,value pair into the map, possibly replacing a prior value.
                    map.insert(key.to_owned(), value.to_owned());
                }
            }
        }
        if let Err(msg) = write_hexdump(args, map, sink) {
            error!("ERROR: Failed to generate hexdump: {}", msg);
            return ExitCode::FAILURE;
        };
        ExitCode::SUCCESS
    }
}