use std::{fs, path::PathBuf};
use clap::Parser;
use rustyline::{error::ReadlineError, Editor};
use tvix_eval::Value; //{Error, EvalWarning, Evaluation, Value};
#[derive(Parser)]
struct Args {
/// Path to a script to evaluate
script: Option<PathBuf>,
#[clap(long, short = 'E')]
expr: Option<String>,
// TODO: port these options here directly
// #[clap(flatten)]
// eval_options: tvix_eval::Options,
}
/// Interprets the given code snippet, printing out warnings, errors
/// and the result itself. The return value indicates whether
/// evaluation succeeded.
fn interpret(code: &str, path: Option<PathBuf>) -> bool {
let eval = tvix_eval::Evaluation::new(code, path);
let source_map = eval.source_map();
let result = eval.evaluate();
for error in &result.errors {
error.fancy_format_stderr(&source_map);
}
for warning in &result.warnings {
warning.fancy_format_stderr(&source_map);
}
if let Some(value) = result.value.as_ref() {
println_result(value, /* TODO raw = */ false);
}
// inform the caller about any errors
result.errors.is_empty()
}
fn main() {
let args = Args::parse();
if let Some(file) = args.script {
run_file(file /* TODO, args.eval_options*/)
} else if let Some(expr) = args.expr {
if !interpret(&expr, None) {
std::process::exit(1);
}
} else {
run_prompt(/* TODO args.eval_options */)
}
}
fn run_file(mut path: PathBuf /* TODO: , eval_options: tvix_eval::Options */) {
if path.is_dir() {
path.push("default.nix");
}
let contents = fs::read_to_string(&path).expect("failed to read the input file");
if !interpret(&contents, Some(path)) {
std::process::exit(1);
}
}
fn println_result(result: &Value, raw: bool) {
if raw {
println!("{}", result.to_str().unwrap().as_str())
} else {
println!("=> {} :: {}", result, result.type_of())
}
}
fn state_dir() -> Option<PathBuf> {
let mut path = dirs::data_dir();
if let Some(p) = path.as_mut() {
p.push("tvix")
}
path
}
fn run_prompt(/* TODO eval_options: tvix_eval::Options */) {
let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
let history_path = match state_dir() {
// Attempt to set up these paths, but do not hard fail if it
// doesn't work.
Some(mut path) => {
let _ = std::fs::create_dir_all(&path);
path.push("history.txt");
let _ = rl.load_history(&path);
Some(path)
}
None => None,
};
loop {
let readline = rl.readline("tvix-repl> ");
match readline {
Ok(line) => {
if line.is_empty() {
continue;
}
rl.add_history_entry(&line);
interpret(&line, None);
}
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
Err(err) => {
eprintln!("error: {}", err);
break;
}
}
}
if let Some(path) = history_path {
rl.save_history(&path).unwrap();
}
}