blr-lang 0.1.0

A language implementation that provides type safe dataframes
Documentation
use std::env;
use std::path::PathBuf;

use blr_lang::val_to_string;
use snafu::{ResultExt as _, Snafu};
use tokio::{
    fs::File,
    io::{self, AsyncReadExt},
};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("io error at {path}"))]
    IO { path: String, source: io::Error },
    #[snafu(display("blr error"))]
    BLR { source: blr_lang::Error },
}

fn print_usage() {
    eprintln!("Usage: blr <file.blr> | blr snapshot-diff --base <dir1> --head <dir2>");
    eprintln!();
    eprintln!("Commands:");
    eprintln!("  snapshot-diff    Compare BLR_SNAPSHOT outputs from two branches");
    eprintln!();
    eprintln!("Options (for snapshot-diff):");
    eprintln!("  --base <dir>     Base snapshot directory");
    eprintln!("  --head <dir>     Head snapshot directory");
}

fn run_snapshot_diff(args: &[String]) -> i32 {
    let mut base: Option<PathBuf> = None;
    let mut head: Option<PathBuf> = None;

    let mut i = 2;
    while i < args.len() {
        match args[i].as_str() {
            "--base" => {
                i += 1;
                if i < args.len() {
                    base = Some(PathBuf::from(&args[i]));
                } else {
                    eprintln!("Error: --base requires a directory argument");
                    return 1;
                }
            }
            "--head" => {
                i += 1;
                if i < args.len() {
                    head = Some(PathBuf::from(&args[i]));
                } else {
                    eprintln!("Error: --head requires a directory argument");
                    return 1;
                }
            }
            "--help" | "-h" => {
                print_usage();
                return 0;
            }
            _ => {
                eprintln!("Unknown option: {}", args[i]);
                print_usage();
                return 1;
            }
        }
        i += 1;
    }

    let base = match base {
        Some(b) => b,
        None => {
            eprintln!("Error: --base is required");
            print_usage();
            return 1;
        }
    };

    let head = match head {
        Some(h) => h,
        None => {
            eprintln!("Error: --head is required");
            print_usage();
            return 1;
        }
    };

    if !base.exists() {
        eprintln!("Error: base directory '{}' does not exist", base.display());
        return 1;
    }

    if !head.exists() {
        eprintln!("Error: head directory '{}' does not exist", head.display());
        return 1;
    }

    let result = blr_lang::snapshot_diff::compare_snapshots(&base, &head);
    let buffer = result.render_terminal();
    let output = buffer.into_inner();
    print!("{}", String::from_utf8_lossy(&output));

    0
}

#[tokio::main]
#[snafu::report]
async fn main() -> Result<(), Error> {
    tracing_subscriber::registry()
        .with(fmt::layer().pretty())
        .with(EnvFilter::from_default_env())
        .init();

    let args: Vec<String> = env::args().collect();

    if args.len() > 1 && args[1] == "snapshot-diff" {
        let code = run_snapshot_diff(&args);
        std::process::exit(code);
    }

    let mut src = String::new();
    if let Some(path) = env::args().nth(1) {
        File::open(&path)
            .await
            .context(IOSnafu { path: path.clone() })?
            .read_to_string(&mut src)
            .await
            .context(IOSnafu { path })?;
    } else {
        io::stdin()
            .read_to_string(&mut src)
            .await
            .context(IOSnafu {
                path: "-".to_string(),
            })?;
    };

    let value = blr_lang::run(&src, ".").await.context(BLRSnafu {})?;
    println!("{}", val_to_string(value));

    Ok(())
}