triton-tui 3.0.0

Terminal User Interface to help debugging programs written for Triton VM.
use std::path::PathBuf;

use clap::Args;
use clap::Parser;
use clap::value_parser;
use directories::ProjectDirs;
use lazy_static::lazy_static;

lazy_static! {
    pub(crate) static ref PROJECT_NAME: String =
        env!("CARGO_CRATE_NAME").to_uppercase().to_string();
    pub(crate) static ref DATA_FOLDER: Option<PathBuf> =
        std::env::var(format!("{}_DATA", PROJECT_NAME.clone()))
            .ok()
            .map(PathBuf::from);
    pub(crate) static ref CONFIG_FOLDER: Option<PathBuf> =
        std::env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
            .ok()
            .map(PathBuf::from);
    pub(crate) static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone());
    pub(crate) static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
}

pub(crate) const DEFAULT_INTERRUPT_CYCLE: u32 = 1_000_000;
pub(crate) const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
pub(crate) const EXAMPLE_PROGRAM_PATH: &str = "examples/program.tasm";
include!(concat!(env!("OUT_DIR"), "/version.rs"));

#[derive(Debug, Clone, PartialEq, Parser)]
#[command(author, version = version(), about)]
pub(crate) struct TuiArgs {
    #[command(flatten)]
    pub input_args: Option<InputArgs>,

    /// JSON file containing an entire VM state, including program and inputs.
    /// Conflicts with command line options program, input, and non-determinism.
    #[arg(long, value_name = "file", group = "state")]
    pub initial_state: Option<String>,

    /// The maximum number of cycles to run after any interaction,
    /// preventing a frozen TUI in infinite loops
    #[arg(
        long,
        value_name = "u32",
        default_value = DEFAULT_INTERRUPT_CYCLE.to_string(),
        value_parser = value_parser!(u32).range(1..)
    )]
    pub interrupt_cycle: u32,
}

#[derive(Debug, Clone, PartialEq, Args)]
#[group(required = false, multiple = true, conflicts_with = "state")]
pub(crate) struct InputArgs {
    /// File containing the program to run
    pub program: String,

    /// File containing public input
    #[arg(short, long, value_name = "file")]
    pub input: Option<String>,

    /// JSON file containing all non-determinism
    #[arg(short, long, value_name = "file")]
    pub non_determinism: Option<String>,
}

impl Default for TuiArgs {
    fn default() -> Self {
        let program = format!("{MANIFEST_DIR}/{EXAMPLE_PROGRAM_PATH}");
        let input_args = Some(InputArgs {
            program,
            input: None,
            non_determinism: None,
        });
        Self {
            input_args,
            initial_state: None,
            interrupt_cycle: DEFAULT_INTERRUPT_CYCLE,
        }
    }
}

fn project_directory() -> Option<ProjectDirs> {
    ProjectDirs::from(
        "org.triton-vm.triton-tui",
        "Triton VM",
        env!("CARGO_PKG_NAME"),
    )
}

pub(crate) fn get_data_dir() -> PathBuf {
    DATA_FOLDER
        .clone()
        .or_else(|| project_directory().map(|dirs| dirs.data_local_dir().to_path_buf()))
        .unwrap_or_else(|| PathBuf::from(".").join(".data"))
}

pub(crate) fn get_config_dir() -> PathBuf {
    CONFIG_FOLDER
        .clone()
        .or_else(|| project_directory().map(|dirs| dirs.config_local_dir().to_path_buf()))
        .unwrap_or_else(|| PathBuf::from(".").join(".config"))
}

pub(crate) fn version() -> String {
    let author = clap::crate_authors!();
    let config_dir_path = get_config_dir().display().to_string();
    let data_dir_path = get_data_dir().display().to_string();

    format!(
        "{CURR_VERSION}\n\n\
        Authors: {author}\n\n\
        Config directory: {config_dir_path}\n\
        Data directory:   {data_dir_path}"
    )
}