guartcl 0.6.0-beta1

Enhanced Jim Tcl.
Documentation
use std::{
    env,
    process::{self},
};

use bpaf::*;
use guartcl::{
    GUARTCL_VERSION,
    compile::{EmbeddedSources, TCL_SECTION},
    guardian_interp,
};
use jimtcl::{
    JimError, JimResult,
    cli::{Jimsh, TclShellInvocation, jimsh_args},
    tcl_error,
};
use log::debug;

/// Enhanced Jim Tcl shell, with Rust-based superpowers.
#[derive(Debug, Clone)]
struct GuardianShell {
    usage: bool,
    shell: TclShellInvocation,
}

fn usage_switch() -> impl Parser<bool> {
    short('U').long("usage").switch()
}

fn guarsh_parser() -> OptionParser<GuardianShell> {
    construct!(GuardianShell {
        usage(usage_switch()),
    shell(jimsh_args()),
    })
    .to_options()
}

fn main() -> JimResult<process::ExitCode> {
    env_logger::try_init().map_err(|_| tcl_error!("cannot initialize logging"))?;

    let embedded = libsui::find_section(TCL_SECTION)
        .map_err(|e| tcl_error!("error searching for Tcl section: {}", e))?;
    let rc = if let Some(data) = embedded {
        run_embedded(data)?
    } else {
        run_shell()?
    };

    let rc = u8::try_from(rc).map_err(|e| tcl_error!("out-of-bounds exit code {}", e))?;
    Ok(rc.into())
}

fn run_embedded(data: &[u8]) -> JimResult<i32> {
    let embed: EmbeddedSources =
        serde_json::from_slice(data).map_err(|e| tcl_error!("embedded decode error: {}", e))?;
    let interp = guardian_interp()?;
    let mut args = env::args_os();
    let argv0 = args.next();
    let args: Vec<_> = args.collect();
    interp.set_script_args(argv0, &args)?;
    if embed.options.usage {
        let args: Vec<_> = env::args().collect();
        guartcl::usage::parse_args_source(&interp, &embed.source, &args)?;
    }
    debug!("running embedded script");
    let result = interp.eval_source(&embed.name, 1, &embed.source);
    interp.error_stack_trace(&result)?;
    interp.resolve_exit(result)
}

fn run_shell() -> JimResult<i32> {
    let argv0 = env::args_os().next();
    let cli = guarsh_parser()
        .version(
            format!(
                "{} (Jim version 0.{})",
                GUARTCL_VERSION,
                jimtcl::JIM_VERSION
            )
            .as_str(),
        )
        .run();

    let interp = guardian_interp()?;
    let jimsh = Jimsh::from_interp(interp, argv0)?;

    if cli.usage
        && let Some(script) = cli.shell.script_file()
    {
        let mut args = Vec::with_capacity(cli.shell.args.len() + 1);
        args.push(script.to_str().ok_or(JimError::InvalidUtf8)?.to_owned());
        for arg in &cli.shell.args {
            let arg = arg.to_str().ok_or(JimError::InvalidUtf8)?.to_owned();
            args.push(arg);
        }
        guartcl::usage::parse_args(&jimsh.interp, script.as_ref(), &args)?;
    }
    jimsh.run(&cli.shell)
}