twrap 0.1.0

A TUI wrapper for applying rules to live terminal screens.
use clap::Parser;
use regex::Regex;
use std::{ffi::OsString, path::PathBuf};

#[derive(Debug, Clone, Parser)]
#[command(
    name = "twrap",
    version,
    about = "Wrap an existing TUI and add highlights plus screenshots"
)]
pub(crate) struct Cli {
    #[arg(short = 'e', long = "highlight")]
    pub(crate) highlight: Vec<String>,

    #[arg(short = 'H', long, default_value = "#fff59d")]
    pub(crate) highlight_color: String,

    #[arg(short = 'x', long = "highlight-command")]
    pub(crate) highlight_command: Option<String>,

    #[arg(short = 'S', long, default_value_t = false)]
    pub(crate) highlight_capture_tui_screenshot: bool,

    #[arg(
        short = 'o',
        long = "screenshot-dir",
        default_value = "tmp/twrap-artifacts"
    )]
    pub(crate) screenshot_dir: PathBuf,

    #[arg(short = 'p', long = "screenshot-prefix")]
    pub(crate) screenshot_prefix: Option<String>,

    #[arg(short = 'k', long = "screenshot-key", default_value = "ctrl-g")]
    pub(crate) screenshot_key: ScreenshotKey,

    #[arg(short = 'b', long = "bind")]
    pub(crate) bind: Vec<String>,

    #[arg(
        short = 'R',
        long = "replace",
        value_names = ["PATTERN", "TEXT"],
        num_args = 2
    )]
    pub(crate) replace: Vec<String>,

    #[arg(short = 'M', long = "mask")]
    pub(crate) mask: Vec<String>,

    #[arg(short = 'c', long = "mask-char", default_value = "*")]
    pub(crate) mask_char: String,

    #[arg(short = 'C', long = "startup-capture-ms", default_value_t = 0)]
    pub(crate) startup_capture_ms: u64,

    #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
    pub(crate) command: Vec<OsString>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ScreenshotKey {
    CtrlG,
    CtrlT,
    CtrlBackslash,
}

impl std::str::FromStr for ScreenshotKey {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "ctrl-g" => Ok(Self::CtrlG),
            "ctrl-t" => Ok(Self::CtrlT),
            "ctrl-\\" => Ok(Self::CtrlBackslash),
            _ => Err("supported values: ctrl-g, ctrl-t, ctrl-\\".to_string()),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Rgb(pub(crate) u8, pub(crate) u8, pub(crate) u8);

#[derive(Debug, Clone)]
pub(crate) struct HighlightRule {
    pub(crate) key: String,
    pub(crate) pattern: String,
    pub(crate) regex: Regex,
    pub(crate) color: Rgb,
    pub(crate) command: Option<Vec<String>>,
    pub(crate) capture_tui_screenshot: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct KeyBinding {
    pub(crate) trigger: KeyChord,
    pub(crate) action: BindingAction,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct KeyChord {
    pub(crate) label: String,
    pub(crate) bytes: Vec<u8>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum BindingAction {
    Send(Vec<u8>),
    Screenshot,
}

#[derive(Debug, Clone)]
pub(crate) struct OutputTransformRule {
    pub(crate) regex: Regex,
    pub(crate) kind: OutputTransformKind,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum OutputTransformKind {
    Replace(String),
    Mask(char),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ScreenLine {
    pub(crate) text: String,
    pub(crate) char_spans: Vec<(usize, usize)>,
    pub(crate) cell_count: usize,
}

#[derive(Debug, Clone)]
pub(crate) struct Runtime {
    pub(crate) command: Vec<OsString>,
    pub(crate) startup_capture_ms: u64,
    pub(crate) screenshot_dir: PathBuf,
    pub(crate) screenshot_prefix: Option<String>,
    pub(crate) highlight_rules: Vec<HighlightRule>,
    pub(crate) key_bindings: Vec<KeyBinding>,
    pub(crate) output_transforms: Vec<OutputTransformRule>,
}