about summary refs log tree commit diff
path: root/tvix/cli
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/cli')
-rw-r--r--tvix/cli/Cargo.toml10
-rw-r--r--tvix/cli/default.nix5
-rw-r--r--tvix/cli/src/main.rs123
3 files changed, 138 insertions, 0 deletions
diff --git a/tvix/cli/Cargo.toml b/tvix/cli/Cargo.toml
new file mode 100644
index 0000000000..605b2c8065
--- /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 0000000000..7cb91979fd
--- /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 0000000000..1c148cad38
--- /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();
+    }
+}