diff options
Diffstat (limited to 'tvix/cli')
-rw-r--r-- | tvix/cli/Cargo.toml | 10 | ||||
-rw-r--r-- | tvix/cli/default.nix | 5 | ||||
-rw-r--r-- | tvix/cli/src/main.rs | 123 |
3 files changed, 138 insertions, 0 deletions
diff --git a/tvix/cli/Cargo.toml b/tvix/cli/Cargo.toml new file mode 100644 index 000000000000..605b2c80651d --- /dev/null +++ b/tvix/cli/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tvix-cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +tvix-eval = { path = "../eval" } +rustyline = "10.0.0" +clap = { version = "3.2.22", features = ["derive", "env"] } +dirs = "4.0.0" diff --git a/tvix/cli/default.nix b/tvix/cli/default.nix new file mode 100644 index 000000000000..7cb91979fd62 --- /dev/null +++ b/tvix/cli/default.nix @@ -0,0 +1,5 @@ +{ depot, pkgs, lib, ... }: + +depot.tvix.crates.workspaceMembers.tvix-cli.build.override { + runTests = true; +} diff --git a/tvix/cli/src/main.rs b/tvix/cli/src/main.rs new file mode 100644 index 000000000000..1c148cad3873 --- /dev/null +++ b/tvix/cli/src/main.rs @@ -0,0 +1,123 @@ +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 mut eval = tvix_eval::Evaluation::new(code, path); + let result = eval.evaluate(); + + let source_map = eval.source_map(); + 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(); + } +} |