rename client and move incomplete projects to research
This commit is contained in:
2
research/repl/.gitignore
vendored
Normal file
2
research/repl/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target
|
||||
temp_db_for_example_worker_default_worker
|
5
research/repl/.rhai_repl_history.txt
Normal file
5
research/repl/.rhai_repl_history.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
#V2
|
||||
.edit
|
||||
quit
|
||||
.edit
|
||||
exit
|
1752
research/repl/Cargo.lock
generated
Normal file
1752
research/repl/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
research/repl/Cargo.toml
Normal file
21
research/repl/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "ui_repl"
|
||||
version = "0.1.0"
|
||||
edition = "2024" # Keep 2024 unless issues arise
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync"] } # Added "time" for potential timeouts, "sync" for worker
|
||||
url = "2" # For parsing Redis URL
|
||||
tracing = "0.1" # For logging
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
log = "0.4" # rhai_dispatcher uses log crate
|
||||
rustyline = { version = "13.0.0", features = ["derive"] } # For enhanced REPL input
|
||||
tempfile = "3.8" # For creating temporary files for editing
|
||||
|
||||
rhai_dispatcher = { path = "../client" }
|
||||
anyhow = "1.0" # For simpler error handling
|
||||
|
||||
rhailib_worker = { path = "../worker", package = "rhailib_worker" }
|
||||
rhailib_engine = { path = "../engine" }
|
||||
heromodels = { path = "../../../db/heromodels", features = ["rhai"] }
|
||||
rhai = { version = "1.18.0" } # Match version used by worker/engine
|
77
research/repl/README.md
Normal file
77
research/repl/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Rhai REPL CLI for Circle WebSocket Servers
|
||||
|
||||
This crate provides a command-line interface (CLI) to interact with Rhai scripts executed on remote Circle WebSocket servers. It includes both an interactive REPL and a non-interactive example.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Circle Orchestrator Running**: Ensure the `circles_orchestrator` is running. This application manages and starts the individual Circle WebSocket servers.
|
||||
To run the orchestrator:
|
||||
```bash
|
||||
cd /path/to/herocode/circles/cmd
|
||||
cargo run
|
||||
```
|
||||
By default, this will start servers based on the `circles.json` configuration (e.g., "Alpha Circle" on `ws://127.0.0.1:8081/ws`).
|
||||
|
||||
2. **Redis Server**: Ensure a Redis server is running and accessible at `redis://127.0.0.1:6379` (this is the default used by the orchestrator and its components).
|
||||
|
||||
## Usage
|
||||
|
||||
Navigate to this crate's directory:
|
||||
```bash
|
||||
cd /path/to/herocode/circles/ui_repl
|
||||
```
|
||||
|
||||
### 1. Interactive REPL
|
||||
|
||||
The main binary of this crate is an interactive REPL.
|
||||
|
||||
**To run with default WebSocket URL (`ws://127.0.0.1:8081/ws`):**
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
|
||||
**To specify a WebSocket URL:**
|
||||
```bash
|
||||
cargo run ws://<your-circle-server-ip>:<port>/ws
|
||||
# Example for "Beta Circle" if configured on port 8082:
|
||||
# cargo run ws://127.0.0.1:8082/ws
|
||||
```
|
||||
|
||||
Once connected, you can:
|
||||
- Type single-line Rhai scripts directly and press Enter.
|
||||
- Use Vi keybindings for editing the current input line (thanks to `rustyline`).
|
||||
- Type `.edit` to open your `$EDITOR` (or `vi` by default) for multi-line script input. Save and close the editor to execute the script.
|
||||
- Type `.run <filepath>` (or `run <filepath>`) to execute a Rhai script from a local file.
|
||||
- Type `exit` or `quit` to close the REPL.
|
||||
|
||||
Command history is saved to `.rhai_repl_history.txt` in the directory where you run the REPL.
|
||||
|
||||
### 2. Non-Interactive Example (`connect_and_play`)
|
||||
|
||||
This example connects to a WebSocket server, sends a predefined Rhai script, prints the response, and then disconnects.
|
||||
|
||||
**To run with default WebSocket URL (`ws://127.0.0.1:8081/ws`):**
|
||||
```bash
|
||||
cargo run --example connect_and_play
|
||||
```
|
||||
|
||||
**To specify a WebSocket URL for the example:**
|
||||
```bash
|
||||
cargo run --example connect_and_play ws://<your-circle-server-ip>:<port>/ws
|
||||
# Example:
|
||||
# cargo run --example connect_and_play ws://127.0.0.1:8082/ws
|
||||
```
|
||||
|
||||
The example script is:
|
||||
```rhai
|
||||
let a = 10;
|
||||
let b = 32;
|
||||
let message = "Hello from example script!";
|
||||
message + " Result: " + (a + b)
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Both the REPL and the example use the `tracing` crate for logging. You can control log levels using the `RUST_LOG` environment variable. For example, to see debug logs from the `circle_client_ws` library:
|
||||
```bash
|
||||
RUST_LOG=info,circle_client_ws=debug cargo run --example connect_and_play
|
53
research/repl/docs/ARCHITECTURE.md
Normal file
53
research/repl/docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Architecture of the `ui_repl` Crate
|
||||
|
||||
The `ui_repl` crate provides an interactive Read-Eval-Print Loop (REPL) interface for the rhailib ecosystem, enabling real-time script development, testing, and execution with integrated worker management.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[REPL Interface] --> B[Script Execution]
|
||||
A --> C[Worker Management]
|
||||
A --> D[Client Integration]
|
||||
|
||||
B --> B1[Local Engine Execution]
|
||||
B --> B2[Remote Worker Execution]
|
||||
B --> B3[Script Editing]
|
||||
|
||||
C --> C1[Worker Lifecycle]
|
||||
C --> C2[Task Distribution]
|
||||
C --> C3[Status Monitoring]
|
||||
|
||||
D --> D1[Redis Client]
|
||||
D --> D2[Task Submission]
|
||||
D --> D3[Result Retrieval]
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Interactive Development
|
||||
- **Enhanced Input**: Rustyline for advanced command-line editing
|
||||
- **Script Editing**: Temporary file editing with external editors
|
||||
- **Syntax Highlighting**: Enhanced script development experience
|
||||
|
||||
### Dual Execution Modes
|
||||
- **Local Execution**: Direct engine execution for development
|
||||
- **Remote Execution**: Worker-based execution for production testing
|
||||
- **Seamless Switching**: Easy mode transitions during development
|
||||
|
||||
### Integrated Worker Management
|
||||
- **Worker Spawning**: Automatic worker process management
|
||||
- **Lifecycle Control**: Start, stop, and restart worker processes
|
||||
- **Status Monitoring**: Real-time worker health and performance
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Rhai Client**: Integration with rhailib client for remote execution
|
||||
- **Rhailib Engine**: Direct engine access for local execution
|
||||
- **Rhailib Worker**: Embedded worker management capabilities
|
||||
- **Enhanced CLI**: Rustyline for superior REPL experience
|
||||
- **Async Runtime**: Tokio for concurrent operations
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
The REPL serves as the primary development interface for rhailib, providing developers with immediate feedback and testing capabilities for Rhai scripts and business logic.
|
198
research/repl/examples/connect_and_play.rs
Normal file
198
research/repl/examples/connect_and_play.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use anyhow::Context;
|
||||
use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherError, RhaiTaskDetails};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use engine::create_heromodels_engine;
|
||||
use heromodels::db::hero::OurDB;
|
||||
use std::path::PathBuf;
|
||||
use worker_lib::spawn_rhai_worker;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive("connect_and_play=info".parse().unwrap())
|
||||
.add_directive("rhai_dispatcher=info".parse().unwrap()),
|
||||
)
|
||||
.init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let redis_url = args.get(1).cloned().unwrap_or_else(|| {
|
||||
let default_url = "redis://127.0.0.1/".to_string();
|
||||
println!("No Redis URL provided. Defaulting to: {}", default_url);
|
||||
default_url
|
||||
});
|
||||
let worker_name = args.get(2).cloned().unwrap_or_else(|| {
|
||||
let default_worker = "default_worker".to_string();
|
||||
println!("No worker name provided. Defaulting to: {}", default_worker);
|
||||
default_worker
|
||||
});
|
||||
|
||||
// Define DB path for the worker
|
||||
let db_path_str = format!("./temp_db_for_example_worker_{}", worker_name);
|
||||
let db_path = PathBuf::from(&db_path_str);
|
||||
|
||||
// Create shutdown channel for the worker
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(1);
|
||||
|
||||
// Spawn a worker in the background
|
||||
let worker_redis_url = redis_url.clone();
|
||||
let worker_circle_name_for_task = worker_name.clone();
|
||||
let db_path_for_task = db_path_str.clone();
|
||||
|
||||
log::info!(
|
||||
"[Main] Spawning worker for circle '{}' with DB path '{}'",
|
||||
worker_circle_name_for_task,
|
||||
db_path_for_task
|
||||
);
|
||||
|
||||
let worker_join_handle = tokio::spawn(async move {
|
||||
log::info!(
|
||||
"[BG Worker] Starting for circle '{}' on Redis '{}'",
|
||||
worker_circle_name_for_task,
|
||||
worker_redis_url
|
||||
);
|
||||
// The `reset: true` in OurDB::new handles pre-cleanup if the directory exists.
|
||||
let db = Arc::new(
|
||||
OurDB::new(&db_path_for_task, true)
|
||||
.expect("Failed to create temp DB for example worker"),
|
||||
);
|
||||
let mut engine = create_heromodels_engine(db);
|
||||
engine.set_max_operations(0);
|
||||
engine.set_max_expr_depths(0, 0);
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::Full);
|
||||
|
||||
if let Err(e) = spawn_rhai_worker(
|
||||
1, // dummy circle_id
|
||||
worker_circle_name_for_task.clone(),
|
||||
engine,
|
||||
worker_redis_url.clone(),
|
||||
shutdown_rx, // Pass the receiver from main
|
||||
false, // preserve_tasks
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::error!(
|
||||
"[BG Worker] Failed to spawn or worker error for circle '{}': {}",
|
||||
worker_circle_name_for_task,
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"[BG Worker] Worker for circle '{}' shut down gracefully.",
|
||||
worker_circle_name_for_task
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Give the worker a moment to start up
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
||||
println!(
|
||||
"Initializing RhaiDispatcher for Redis at {} to target worker '{}'...",
|
||||
redis_url, worker_name
|
||||
);
|
||||
let client = RhaiDispatcher::new(&redis_url)
|
||||
.with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?;
|
||||
println!("RhaiDispatcher initialized.");
|
||||
|
||||
let script = "let a = 10; let b = 32; let message = \"Hello from example script!\"; message + \" Result: \" + (a + b)";
|
||||
println!("\nSending script:\n```rhai\n{}\n```", script);
|
||||
|
||||
let timeout = Duration::from_secs(30);
|
||||
match client
|
||||
.submit_script_and_await_result(&worker_name, script.to_string(), None, timeout)
|
||||
.await
|
||||
{
|
||||
Ok(task_details) => {
|
||||
println!("\nWorker response:");
|
||||
if let Some(ref output) = task_details.output {
|
||||
println!("Output: {}", output);
|
||||
}
|
||||
if let Some(ref error_msg) = task_details.error {
|
||||
eprintln!("Error: {}", error_msg);
|
||||
}
|
||||
if task_details.output.is_none() && task_details.error.is_none() {
|
||||
println!(
|
||||
"Worker finished with no explicit output or error. Status: {}",
|
||||
task_details.status
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
RhaiDispatcherError::Timeout(task_id) => {
|
||||
eprintln!(
|
||||
"\nError: Script execution timed out for task_id: {}.",
|
||||
task_id
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::RedisError(redis_err) => {
|
||||
eprintln!(
|
||||
"\nError: Redis communication failed: {}. Check Redis connection and server status.",
|
||||
redis_err
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::SerializationError(serde_err) => {
|
||||
eprintln!(
|
||||
"\nError: Failed to serialize/deserialize task data: {}.",
|
||||
serde_err
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::TaskNotFound(task_id) => {
|
||||
eprintln!("\nError: Task {} not found after submission.", task_id);
|
||||
} /* All RhaiDispatcherError variants are handled, so _ arm is not strictly needed
|
||||
unless RhaiDispatcherError becomes non-exhaustive in the future. */
|
||||
},
|
||||
}
|
||||
|
||||
println!("\nExample client operations finished. Shutting down worker...");
|
||||
|
||||
// Send shutdown signal to the worker
|
||||
if let Err(e) = shutdown_tx.send(()).await {
|
||||
eprintln!(
|
||||
"[Main] Failed to send shutdown signal to worker: {} (worker might have already exited or an error occurred)",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for the worker to finish
|
||||
log::info!("[Main] Waiting for worker task to join...");
|
||||
if let Err(e) = worker_join_handle.await {
|
||||
eprintln!("[Main] Error waiting for worker task to join: {:?}", e);
|
||||
} else {
|
||||
log::info!("[Main] Worker task joined successfully.");
|
||||
}
|
||||
|
||||
// Clean up the database directory
|
||||
log::info!(
|
||||
"[Main] Cleaning up database directory: {}",
|
||||
db_path.display()
|
||||
);
|
||||
if db_path.exists() {
|
||||
if let Err(e) = std::fs::remove_dir_all(&db_path) {
|
||||
eprintln!(
|
||||
"[Main] Failed to remove database directory '{}': {}",
|
||||
db_path.display(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"[Main] Successfully removed database directory: {}",
|
||||
db_path.display()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::info!(
|
||||
"[Main] Database directory '{}' not found, no cleanup needed.",
|
||||
db_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
println!("Example fully completed and cleaned up.");
|
||||
Ok(())
|
||||
}
|
275
research/repl/src/main.rs
Normal file
275
research/repl/src/main.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use anyhow::Context;
|
||||
use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder, RhaiDispatcherError};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::{Config, DefaultEditor, EditMode};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// Default timeout for script execution
|
||||
const DEFAULT_SCRIPT_TIMEOUT_SECONDS: u64 = 30;
|
||||
|
||||
async fn execute_script(client: &RhaiDispatcher, circle_name: &str, script_content: String) {
|
||||
if script_content.trim().is_empty() {
|
||||
println!("Script is empty, not sending.");
|
||||
return;
|
||||
}
|
||||
println!(
|
||||
"Sending script to worker '{}':\n---\n{}\n---",
|
||||
circle_name, script_content
|
||||
);
|
||||
|
||||
let timeout = Duration::from_secs(DEFAULT_SCRIPT_TIMEOUT_SECONDS);
|
||||
|
||||
match client
|
||||
.new_play_request()
|
||||
.worker_id(circle_name)
|
||||
.script(&script_content)
|
||||
.timeout(timeout)
|
||||
.await_response()
|
||||
.await
|
||||
{
|
||||
Ok(task_details) => {
|
||||
if let Some(output) = &task_details.output {
|
||||
println!("worker: {}", output);
|
||||
}
|
||||
if let Some(error_msg) = &task_details.error {
|
||||
eprintln!("Worker error: {}", error_msg);
|
||||
}
|
||||
if task_details.output.is_none() && task_details.error.is_none() {
|
||||
println!(
|
||||
"Worker finished with no explicit output or error. Status: {}",
|
||||
task_details.status
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
RhaiDispatcherError::Timeout(task_id) => {
|
||||
eprintln!(
|
||||
"Error: Script execution timed out for task_id: {}.",
|
||||
task_id
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::RedisError(redis_err) => {
|
||||
eprintln!(
|
||||
"Error: Redis communication failed: {}. Check Redis connection and server status.",
|
||||
redis_err
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::SerializationError(serde_err) => {
|
||||
eprintln!(
|
||||
"Error: Failed to serialize/deserialize task data: {}.",
|
||||
serde_err
|
||||
);
|
||||
}
|
||||
RhaiDispatcherError::TaskNotFound(task_id) => {
|
||||
eprintln!(
|
||||
"Error: Task {} not found after submission (this should be rare).",
|
||||
task_id
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_repl(redis_url: String, circle_name: String) -> anyhow::Result<()> {
|
||||
println!(
|
||||
"Initializing Rhai REPL for worker '{}' via Redis at {}...",
|
||||
circle_name, redis_url
|
||||
);
|
||||
|
||||
let client = RhaiDispatcherBuilder::new()
|
||||
.redis_url(&redis_url)
|
||||
.caller_id("ui_repl") // Set a caller_id
|
||||
.build()
|
||||
.with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?;
|
||||
|
||||
// No explicit connect() needed for rhai_dispatcher, connection is handled per-operation or pooled.
|
||||
println!(
|
||||
"RhaiDispatcher initialized. Ready to send scripts to worker '{}'.",
|
||||
circle_name
|
||||
);
|
||||
println!(
|
||||
"Type Rhai scripts, '.edit' to use $EDITOR, '.run <path>' to execute a file, or 'exit'/'quit'."
|
||||
);
|
||||
println!("Vi mode enabled for input line.");
|
||||
|
||||
let config = Config::builder()
|
||||
.edit_mode(EditMode::Vi)
|
||||
.auto_add_history(true) // Automatically add to history
|
||||
.build();
|
||||
let mut rl = DefaultEditor::with_config(config)?;
|
||||
|
||||
let history_file = ".rhai_repl_history.txt"; // Simple history file in current dir
|
||||
if rl.load_history(history_file).is_err() {
|
||||
// No history found or error loading, not critical
|
||||
}
|
||||
|
||||
let prompt = format!("rhai ({}) @ {}> ", circle_name, redis_url);
|
||||
|
||||
loop {
|
||||
let readline = rl.readline(&prompt);
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
let input = line.trim();
|
||||
|
||||
if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") {
|
||||
println!("Exiting REPL.");
|
||||
break;
|
||||
} else if input.eq_ignore_ascii_case(".edit") {
|
||||
// Correct way to create a temp file with a suffix
|
||||
let temp_file = TempFileBuilder::new()
|
||||
.prefix("rhai_script_") // Optional: add a prefix
|
||||
.suffix(".rhai")
|
||||
.tempfile_in(".") // Create in current directory for simplicity
|
||||
.with_context(|| "Failed to create temp file")?;
|
||||
|
||||
// You can pre-populate the temp file if needed:
|
||||
// use std::io::Write; // Add this import if using write_all
|
||||
// if let Err(e) = temp_file.as_file().write_all(b"// Start your Rhai script here\n") {
|
||||
// eprintln!("Failed to write initial content to temp file: {}", e);
|
||||
// }
|
||||
|
||||
let temp_path = temp_file.path().to_path_buf();
|
||||
let editor_cmd_str = env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
|
||||
|
||||
let mut editor_parts = editor_cmd_str.split_whitespace();
|
||||
let editor_executable = editor_parts.next().unwrap_or("vi"); // Default to vi if $EDITOR is empty string
|
||||
let editor_args: Vec<&str> = editor_parts.collect();
|
||||
|
||||
println!(
|
||||
"Launching editor: '{}' with args: {:?} for script editing. Save and exit editor to execute.",
|
||||
editor_executable, editor_args
|
||||
);
|
||||
|
||||
let mut command = Command::new(editor_executable);
|
||||
command.args(editor_args); // Add any arguments from $EDITOR (like -w)
|
||||
command.arg(&temp_path); // Add the temp file path as the last argument
|
||||
|
||||
let status = command.status();
|
||||
|
||||
match status {
|
||||
Ok(exit_status) if exit_status.success() => {
|
||||
match fs::read_to_string(&temp_path) {
|
||||
Ok(script_content) => {
|
||||
execute_script(&client, &circle_name, script_content).await;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error reading temp file {:?}: {}", temp_path, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(exit_status) => eprintln!(
|
||||
"Editor exited with status: {}. Script not executed.",
|
||||
exit_status
|
||||
),
|
||||
Err(e) => eprintln!(
|
||||
"Failed to launch editor '{}': {}. Ensure it's in your PATH.",
|
||||
editor_executable, e
|
||||
), // Changed 'editor' to 'editor_executable'
|
||||
}
|
||||
// temp_file is automatically deleted when it goes out of scope
|
||||
} else if input.starts_with(".run ") || input.starts_with("run ") {
|
||||
let parts: Vec<&str> = input.splitn(2, ' ').collect();
|
||||
if parts.len() == 2 {
|
||||
let file_path = parts[1];
|
||||
println!("Attempting to run script from file: {}", file_path);
|
||||
match fs::read_to_string(file_path) {
|
||||
Ok(script_content) => {
|
||||
execute_script(&client, &circle_name, script_content).await;
|
||||
}
|
||||
Err(e) => eprintln!("Error reading file {}: {}", file_path, e),
|
||||
}
|
||||
} else {
|
||||
eprintln!("Usage: .run <filepath>");
|
||||
}
|
||||
} else if !input.is_empty() {
|
||||
execute_script(&client, &circle_name, input.to_string()).await;
|
||||
}
|
||||
// rl.add_history_entry(line.as_str()) is handled by auto_add_history(true)
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
// Ctrl-C
|
||||
println!("Input interrupted. Type 'exit' or 'quit' to close.");
|
||||
continue;
|
||||
}
|
||||
Err(ReadlineError::Eof) => {
|
||||
// Ctrl-D
|
||||
println!("Exiting REPL (EOF).");
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error reading input: {:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rl.save_history(history_file).is_err() {
|
||||
// Failed to save history, not critical
|
||||
}
|
||||
|
||||
// No explicit disconnect for RhaiDispatcher as it manages connections internally.
|
||||
println!("Exited REPL.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive("ui_repl=info".parse()?)
|
||||
.add_directive("rhai_dispatcher=info".parse()?),
|
||||
)
|
||||
.init();
|
||||
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
let redis_url_str = if args.len() > 1 {
|
||||
args[1].clone()
|
||||
} else {
|
||||
let default_url = "redis://127.0.0.1/".to_string();
|
||||
println!("No Redis URL provided. Defaulting to: {}", default_url);
|
||||
default_url
|
||||
};
|
||||
|
||||
let circle_name_str = if args.len() > 2 {
|
||||
args[2].clone()
|
||||
} else {
|
||||
let default_circle = "default_worker".to_string();
|
||||
println!(
|
||||
"No worker/circle name provided. Defaulting to: {}",
|
||||
default_circle
|
||||
);
|
||||
default_circle
|
||||
};
|
||||
|
||||
println!(
|
||||
"Usage: {} [redis_url] [worker_name]",
|
||||
args.get(0).map_or("ui_repl", |s| s.as_str())
|
||||
);
|
||||
println!(
|
||||
"Example: {} redis://127.0.0.1/ my_rhai_worker",
|
||||
args.get(0).map_or("ui_repl", |s| s.as_str())
|
||||
);
|
||||
|
||||
// Basic validation for Redis URL (scheme)
|
||||
// A more robust validation might involve trying to parse it with redis::ConnectionInfo
|
||||
if !redis_url_str.starts_with("redis://") {
|
||||
eprintln!(
|
||||
"Warning: Redis URL '{}' does not start with 'redis://'. Attempting to use it anyway.",
|
||||
redis_url_str
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = run_repl(redis_url_str, circle_name_str).await {
|
||||
eprintln!("REPL error: {:#}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user