feat(cli): support eval file

This commit is contained in:
2026-02-16 21:48:46 +08:00
parent f0a0593d4c
commit 16a8480d29
5 changed files with 96 additions and 68 deletions

View File

@@ -4,7 +4,7 @@
[no-exit-message] [no-exit-message]
@eval expr: @eval expr:
cargo run -- eval '{{expr}}' cargo run -- eval --expr '{{expr}}'
[no-exit-message] [no-exit-message]
@replr: @replr:
@@ -12,7 +12,7 @@
[no-exit-message] [no-exit-message]
@evalr expr: @evalr expr:
cargo run --release -- eval '{{expr}}' cargo run --release -- eval --expr '{{expr}}'
[no-exit-message] [no-exit-message]
@repli: @repli:
@@ -20,4 +20,4 @@
[no-exit-message] [no-exit-message]
@evali expr: @evali expr:
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval '{{expr}}' cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval --expr '{{expr}}'

View File

@@ -12,6 +12,8 @@ use crate::ir::{
Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk, Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
ToIr as _, WithLookup, ToIr as _, WithLookup,
}; };
#[cfg(feature = "inspector")]
use crate::runtime::inspector::InspectorServer;
use crate::runtime::{Runtime, RuntimeContext}; use crate::runtime::{Runtime, RuntimeContext};
use crate::store::{DaemonStore, Store, StoreConfig}; use crate::store::{DaemonStore, Store, StoreConfig};
use crate::value::{Symbol, Value}; use crate::value::{Symbol, Value};
@@ -48,7 +50,7 @@ pub struct Context {
ctx: Ctx, ctx: Ctx,
runtime: Runtime<Ctx>, runtime: Runtime<Ctx>,
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
_inspector_server: Option<crate::runtime::inspector::InspectorServer>, _inspector_server: Option<InspectorServer>,
} }
macro_rules! eval { macro_rules! eval {

View File

@@ -1,7 +1,8 @@
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, Args};
use hashbrown::HashSet; use hashbrown::HashSet;
use nix_js::context::Context; use nix_js::context::Context;
use nix_js::error::Source; use nix_js::error::Source;
@@ -25,10 +26,22 @@ struct Cli {
#[derive(Subcommand)] #[derive(Subcommand)]
enum Command { enum Command {
Eval { expr: String }, Eval {
#[clap(flatten)]
source: ExprSource
},
Repl, Repl,
} }
#[derive(Args)]
#[group(required = true, multiple = false)]
struct ExprSource {
#[clap(short, long)]
expr: Option<String>,
#[clap(short, long)]
file: Option<PathBuf>
}
fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> { fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
{ {
@@ -50,9 +63,15 @@ fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
Ok(Context::new()?) Ok(Context::new()?)
} }
fn run_eval(context: &mut Context, expr: String) -> Result<()> { fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
let src = Source::new_eval(expr)?; let src = if let Some(expr) = src.expr {
match context.eval(src) { Source::new_eval(expr)?
} else if let Some(file) = src.file {
Source::new_file(file)?
} else {
unreachable!()
};
match context.eval_shallow(src) {
Ok(value) => { Ok(value) => {
println!("{value}"); println!("{value}");
} }
@@ -131,7 +150,9 @@ fn main() -> Result<()> {
)?; )?;
match cli.command { match cli.command {
Command::Eval { expr } => run_eval(&mut context, expr), Command::Eval { source } => {
run_eval(&mut context, source)
}
Command::Repl => run_repl(&mut context), Command::Repl => run_repl(&mut context),
} }
} }

View File

@@ -248,9 +248,9 @@ async fn server(
.boxed_local(); .boxed_local();
let json_version_response = json!({ let json_version_response = json!({
"Browser": name, "Browser": name,
"Protocol-Version": "1.3", "Protocol-Version": "1.3",
"V8-Version": deno_core::v8::VERSION_STRING, "V8-Version": deno_core::v8::VERSION_STRING,
}); });
// Create the server manually so it can use the Local Executor // Create the server manually so it can use the Local Executor
@@ -269,19 +269,20 @@ async fn server(
let mut accept = pin!(listener.accept()); let mut accept = pin!(listener.accept());
let stream = tokio::select! { let stream = tokio::select! {
accept_result = &mut accept => { accept_result =
match accept_result { &mut accept => {
Ok((s, _)) => s, match accept_result {
Err(err) => { Ok((s, _)) => s,
eprintln!("Failed to accept inspector connection: {:?}", err); Err(err) => {
continue; eprintln!("Failed to accept inspector connection: {:?}", err);
} continue;
} }
}, }
},
_ = &mut shutdown_rx => { _ = &mut shutdown_rx => {
break; break;
} }
}; };
let io = TokioIo::new(stream); let io = TokioIo::new(stream);
@@ -332,15 +333,15 @@ async fn server(
let mut shutdown_rx = pin!(shutdown_server_rx.recv()); let mut shutdown_rx = pin!(shutdown_server_rx.recv());
tokio::select! { tokio::select! {
result = conn.as_mut() => { result = conn.as_mut() => {
if let Err(err) = result { if let Err(err) = result {
eprintln!("Failed to serve connection: {:?}", err); eprintln!("Failed to serve connection: {:?}", err);
}
},
_ = &mut shutdown_rx => {
conn.as_mut().graceful_shutdown();
let _ = conn.await;
} }
},
_ = &mut shutdown_rx => {
conn.as_mut().graceful_shutdown();
let _ = conn.await;
}
} }
}); });
} }
@@ -348,9 +349,9 @@ async fn server(
.boxed_local(); .boxed_local();
tokio::select! { tokio::select! {
_ = register_inspector_handler => {}, _ = register_inspector_handler => {},
_ = deregister_inspector_handler => unreachable!(), _ = deregister_inspector_handler => unreachable!(),
_ = server_handler => {}, _ = server_handler => {},
} }
} }
@@ -392,31 +393,31 @@ async fn pump_websocket_messages(
) { ) {
'pump: loop { 'pump: loop {
tokio::select! { tokio::select! {
Some(msg) = outbound_rx.next() => { Some(msg) = outbound_rx.next() => {
let msg = Frame::text(msg.content.into_bytes().into()); let msg = Frame::text(msg.content.into_bytes().into());
let _ = websocket.write_frame(msg).await; let _ = websocket.write_frame(msg).await;
} }
Ok(msg) = websocket.read_frame() => { Ok(msg) = websocket.read_frame() => {
match msg.opcode { match msg.opcode {
OpCode::Text => { OpCode::Text => {
if let Ok(s) = String::from_utf8(msg.payload.to_vec()) { if let Ok(s) = String::from_utf8(msg.payload.to_vec()) {
let _ = inbound_tx.unbounded_send(s); let _ = inbound_tx.unbounded_send(s);
} }
} }
OpCode::Close => { OpCode::Close => {
// Users don't care if there was an error coming from debugger, // Users don't care if there was an error coming from debugger,
// just about the fact that debugger did disconnect. // just about the fact that debugger did disconnect.
eprintln!("Debugger session ended"); eprintln!("Debugger session ended");
break 'pump; break 'pump;
} }
_ => { _ => {
// Ignore other messages. // Ignore other messages.
} }
}
}
else => {
break 'pump;
} }
}
else => {
break 'pump;
}
} }
} }
} }
@@ -456,14 +457,14 @@ impl InspectorInfo {
let host_listen = format!("{}", self.host); let host_listen = format!("{}", self.host);
let host = host.as_ref().unwrap_or(&host_listen); let host = host.as_ref().unwrap_or(&host_listen);
json!({ json!({
"description": "nix-js", "description": "nix-js",
"devtoolsFrontendUrl": self.get_frontend_url(host), "devtoolsFrontendUrl": self.get_frontend_url(host),
"faviconUrl": "https://deno.land/favicon.ico", "faviconUrl": "https://deno.land/favicon.ico",
"id": self.uuid.to_string(), "id": self.uuid.to_string(),
"title": self.get_title(), "title": self.get_title(),
"type": "node", "type": "node",
"url": self.url.to_string(), "url": self.url.to_string(),
"webSocketDebuggerUrl": self.get_websocket_debugger_url(host), "webSocketDebuggerUrl": self.get_websocket_debugger_url(host),
}) })
} }

View File

@@ -540,6 +540,7 @@ impl NixDaemonClient {
} }
/// Query information about a store path /// Query information about a store path
#[allow(dead_code)]
pub async fn query_path_info(&mut self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> { pub async fn query_path_info(&mut self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> {
let store_path = StorePath::<String>::from_absolute_path(path.as_bytes()) let store_path = StorePath::<String>::from_absolute_path(path.as_bytes())
.map_err(|e| IoError::new(IoErrorKind::InvalidInput, e.to_string()))?; .map_err(|e| IoError::new(IoErrorKind::InvalidInput, e.to_string()))?;
@@ -613,6 +614,7 @@ impl NixDaemonClient {
} }
/// Query which paths are valid /// Query which paths are valid
#[allow(dead_code)]
pub async fn query_valid_paths(&mut self, paths: Vec<String>) -> IoResult<Vec<String>> { pub async fn query_valid_paths(&mut self, paths: Vec<String>) -> IoResult<Vec<String>> {
let store_paths: IoResult<Vec<StorePath<String>>> = paths let store_paths: IoResult<Vec<StorePath<String>>> = paths
.iter() .iter()
@@ -717,6 +719,7 @@ impl NixDaemonConnection {
} }
/// Query information about a store path /// Query information about a store path
#[allow(dead_code)]
pub async fn query_path_info(&self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> { pub async fn query_path_info(&self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> {
let mut client = self.client.lock().await; let mut client = self.client.lock().await;
client.query_path_info(path).await client.query_path_info(path).await
@@ -729,6 +732,7 @@ impl NixDaemonConnection {
} }
/// Query which paths are valid /// Query which paths are valid
#[allow(dead_code)]
pub async fn query_valid_paths(&self, paths: Vec<String>) -> IoResult<Vec<String>> { pub async fn query_valid_paths(&self, paths: Vec<String>) -> IoResult<Vec<String>> {
let mut client = self.client.lock().await; let mut client = self.client.lock().await;
client.query_valid_paths(paths).await client.query_valid_paths(paths).await