rename client and move incomplete projects to research
This commit is contained in:
		
							
								
								
									
										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