archive old code
This commit is contained in:
parent
b20140785e
commit
db5b9a0a42
1320
devtools/reloadd/Cargo.lock
generated
1320
devtools/reloadd/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "reloadd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-tungstenite = "0.21"
|
||||
notify = "6"
|
||||
futures-util = "0.3"
|
@ -1,124 +0,0 @@
|
||||
# Reloadd
|
||||
|
||||
A powerful development tool for automatically restarting your application and reloading your browser when files change.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔄 **File Watching**: Monitor multiple directories for changes
|
||||
- 🚀 **Auto-Restart**: Automatically restart your application when files change
|
||||
- 🌐 **Browser Reload**: Automatically reload connected browsers
|
||||
- 🔌 **WebSocket Integration**: Uses WebSockets for instant browser reloading
|
||||
- 📊 **Sequential Commands**: Run multiple commands in sequence
|
||||
- 🔧 **Configurable Ports**: Customize web server and WebSocket ports
|
||||
- 🛠️ **Robust Error Handling**: Clear error messages and graceful recovery
|
||||
|
||||
## Installation
|
||||
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/yourusername/reloadd.git
|
||||
cd reloadd
|
||||
|
||||
# Build and install
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
reloadd --watch src --watch templates -- run --example server
|
||||
```
|
||||
|
||||
### With Sequential Commands
|
||||
|
||||
```bash
|
||||
reloadd --watch src --run "cargo build" --run "cargo test" --run "cargo run --example server"
|
||||
```
|
||||
|
||||
Commands will run in the order specified. All commands except the last one will run to completion. The last command is treated as the long-running server process.
|
||||
|
||||
### With Custom Port
|
||||
|
||||
```bash
|
||||
reloadd --watch src --port 3000 --run "cargo run --example server"
|
||||
```
|
||||
|
||||
### In a Shell Script
|
||||
|
||||
Create a `develop.sh` script for your project:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Start dev server with file watching and browser reload
|
||||
reloadd \
|
||||
--watch src \
|
||||
--watch templates \
|
||||
--port 8080 \
|
||||
--run "cargo run --example server"
|
||||
```
|
||||
|
||||
Make it executable and run it:
|
||||
|
||||
```bash
|
||||
chmod +x develop.sh
|
||||
./develop.sh
|
||||
```
|
||||
|
||||
## Command Line Options
|
||||
|
||||
```
|
||||
Usage: reloadd [OPTIONS] --watch <PATH>... [-- <COMMAND>...]
|
||||
|
||||
Arguments:
|
||||
[COMMAND]... Command to run on change (legacy format)
|
||||
|
||||
Options:
|
||||
-w, --watch <PATH>... Paths to watch (like src/, templates/, scripts/)
|
||||
-p, --port <PORT> Port for the web server [default: 8080]
|
||||
-r, --run <COMMAND>... Multiple commands to run
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## LiveReload Integration
|
||||
|
||||
When you start the tool, it will output a script tag that you can add to your HTML files:
|
||||
|
||||
```html
|
||||
<script>
|
||||
const ws = new WebSocket("ws://localhost:35729");
|
||||
ws.onmessage = (msg) => {
|
||||
if (msg.data === "reload") location.reload();
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
Add this script to your HTML templates to enable automatic browser reloading.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reloadd watches specified directories for file changes
|
||||
2. When a change is detected, it runs your commands in sequence
|
||||
3. Build commands (all except the last) run to completion before proceeding
|
||||
4. The last command (typically a server) runs and stays active
|
||||
5. After a brief delay, it sends a reload signal to connected browsers
|
||||
6. Browsers with the LiveReload script will automatically refresh
|
||||
|
||||
## Error Handling
|
||||
|
||||
Reloadd includes robust error handling:
|
||||
|
||||
- Validates watch paths before starting
|
||||
- Checks for port availability
|
||||
- Provides clear error messages
|
||||
- Gracefully exits with error codes when critical errors occur
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
@ -1,97 +0,0 @@
|
||||
use clap::{Parser};
|
||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct Args {
|
||||
/// Paths to watch (like src/, templates/, scripts/)
|
||||
#[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
|
||||
watch: Vec<String>,
|
||||
|
||||
/// Command to run on change (like: -- run --example server)
|
||||
#[arg(last = true)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
let (tx, _) = broadcast::channel::<()>(10);
|
||||
let tx_ws = tx.clone();
|
||||
let server_process = Arc::new(Mutex::new(None::<Child>));
|
||||
|
||||
// Start WebSocket reload server
|
||||
tokio::spawn(start_websocket_server(tx_ws.clone()));
|
||||
|
||||
// Start watching files and restarting command
|
||||
let server_process_clone = Arc::clone(&server_process);
|
||||
let watch_paths = args.watch.clone();
|
||||
let cmd_args = args.command.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut watcher = recommended_watcher(move |res| {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
|
||||
println!("📦 Change detected, restarting...");
|
||||
|
||||
// Kill previous process
|
||||
if let Some(mut child) = server_process_clone.lock().unwrap().take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
|
||||
// Run new process
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(&cmd_args);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
let child = cmd.spawn().expect("Failed to spawn");
|
||||
|
||||
*server_process_clone.lock().unwrap() = Some(child);
|
||||
|
||||
// Notify browser
|
||||
let _ = tx.send(());
|
||||
}
|
||||
}
|
||||
}).expect("watcher failed");
|
||||
|
||||
// Add watches
|
||||
for path in &watch_paths {
|
||||
watcher.watch(path, RecursiveMode::Recursive).unwrap();
|
||||
}
|
||||
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
|
||||
println!("🔁 Watching paths: {:?}", args.watch);
|
||||
println!("🌐 Connect browser to ws://localhost:35729");
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_websocket_server(tx: broadcast::Sender<()>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:35729").await.unwrap();
|
||||
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
let tx = tx.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let ws_stream = accept_async(stream).await.unwrap();
|
||||
let (mut write, _) = ws_stream.split();
|
||||
|
||||
while rx.recv().await.is_ok() {
|
||||
let _ = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,333 +0,0 @@
|
||||
use clap::{Parser};
|
||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
struct Args {
|
||||
/// Paths to watch (like src/, templates/, scripts/)
|
||||
#[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
|
||||
watch: Vec<String>,
|
||||
|
||||
/// Port for the web server (default: 8080)
|
||||
#[arg(short, long, default_value = "8080")]
|
||||
port: u16,
|
||||
|
||||
/// Multiple commands to run (format: --run "cargo build" --run "cargo test")
|
||||
#[arg(short, long, value_name = "COMMAND", num_args = 1..)]
|
||||
run: Vec<String>,
|
||||
|
||||
/// Command to run on change (like: -- run --example server)
|
||||
/// This is kept for backward compatibility
|
||||
#[arg(last = true)]
|
||||
command: Vec<String>,
|
||||
}
|
||||
|
||||
// Run commands in sequence, with the last one potentially being a long-running server
|
||||
async fn run_commands_in_sequence(commands: &[Vec<String>], server_process: Arc<Mutex<Option<Child>>>) -> bool {
|
||||
// Run all commands in sequence
|
||||
for (i, command) in commands.iter().enumerate() {
|
||||
let is_last = i == commands.len() - 1;
|
||||
let program = if command[0] == "cargo" || command[0].ends_with("/cargo") {
|
||||
"cargo".to_string()
|
||||
} else {
|
||||
command[0].clone()
|
||||
};
|
||||
|
||||
let args = if command[0] == "cargo" || command[0].ends_with("/cargo") {
|
||||
command.iter().skip(1).cloned().collect::<Vec<String>>()
|
||||
} else {
|
||||
command.iter().skip(1).cloned().collect::<Vec<String>>()
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(&program);
|
||||
cmd.args(&args);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
if is_last {
|
||||
// Last command might be a long-running server
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
*server_process.lock().unwrap() = Some(child);
|
||||
println!("🚀 Started server process: {} {}", program, args.join(" "));
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to start server process: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For non-last commands, wait for them to complete
|
||||
println!("🔄 Running build step: {} {}", program, args.join(" "));
|
||||
match cmd.output() {
|
||||
Ok(output) => {
|
||||
if !output.status.success() {
|
||||
eprintln!("❌ Command failed: {} {}", program, args.join(" "));
|
||||
return false;
|
||||
}
|
||||
println!("✅ Command completed successfully");
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to execute command: {}", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Check if a port is already in use
|
||||
fn is_port_in_use(port: u16) -> bool {
|
||||
StdTcpListener::bind(format!("127.0.0.1:{}", port)).is_err()
|
||||
}
|
||||
|
||||
// Find an available port starting from the given port
|
||||
fn find_available_port(start_port: u16) -> Option<u16> {
|
||||
let mut port = start_port;
|
||||
// Try up to 10 ports (start_port through start_port+9)
|
||||
for _ in 0..10 {
|
||||
if !is_port_in_use(port) {
|
||||
return Some(port);
|
||||
}
|
||||
port += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Generate LiveReload script with the given WebSocket port
|
||||
fn generate_livereload_script(ws_port: u16) -> String {
|
||||
format!(
|
||||
r#"<script>
|
||||
const ws = new WebSocket("ws://localhost:{}");
|
||||
ws.onmessage = (msg) => {{
|
||||
if (msg.data === "reload") location.reload();
|
||||
}};
|
||||
</script>"#,
|
||||
ws_port
|
||||
)
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// Handle both the new --run and legacy command format
|
||||
let commands = if !args.run.is_empty() {
|
||||
// New format: each --run is a separate command
|
||||
args.run.iter().map(|cmd| {
|
||||
// Split the command string into arguments
|
||||
cmd.split_whitespace().map(String::from).collect::<Vec<String>>()
|
||||
}).collect::<Vec<Vec<String>>>()
|
||||
} else if !args.command.is_empty() {
|
||||
// Legacy format: single command from the trailing arguments
|
||||
println!("Command: {:?}", args.command);
|
||||
vec![args.command.clone()]
|
||||
} else {
|
||||
// No commands provided
|
||||
eprintln!("❌ Error: No commands provided. Use --run or trailing arguments.");
|
||||
std::process::exit(1);
|
||||
};
|
||||
|
||||
// Check if server port is already in use
|
||||
let server_port = args.port;
|
||||
if is_port_in_use(server_port) {
|
||||
eprintln!("❌ Error: Port {} is already in use. Stop any running instances before starting a new one.", server_port);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Find an available WebSocket port
|
||||
let ws_port = match find_available_port(35729) {
|
||||
Some(port) => port,
|
||||
None => {
|
||||
eprintln!("❌ Error: Could not find an available WebSocket port. Please free up some ports and try again.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let (tx, _) = broadcast::channel::<()>(10);
|
||||
let tx_ws = tx.clone();
|
||||
let server_process = Arc::new(Mutex::new(None::<Child>));
|
||||
|
||||
// Validate paths before starting the watcher
|
||||
let mut invalid_paths = Vec::new();
|
||||
for path in &args.watch {
|
||||
if !std::path::Path::new(path).exists() {
|
||||
invalid_paths.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_paths.is_empty() {
|
||||
eprintln!("❌ Error: The following watch paths do not exist:");
|
||||
for path in invalid_paths {
|
||||
eprintln!(" - {}", path);
|
||||
}
|
||||
eprintln!("Please provide valid paths to watch.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Start WebSocket reload server
|
||||
match start_websocket_server(tx_ws.clone(), ws_port).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to start WebSocket server: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Output the LiveReload script for users to add to their projects
|
||||
println!("📋 Add this script to your HTML for live reloading:");
|
||||
println!("{}", generate_livereload_script(ws_port));
|
||||
|
||||
// 🚀 Run all commands in sequence
|
||||
if !run_commands_in_sequence(&commands, Arc::clone(&server_process)).await {
|
||||
eprintln!("❌ Command execution failed.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
println!("🔁 Watching paths: {:?}", args.watch);
|
||||
println!("🌐 Connect browser to ws://localhost:{}", ws_port);
|
||||
println!("🖥️ Web server running on http://localhost:{}", server_port);
|
||||
|
||||
// Create a runtime handle for the watcher to use
|
||||
let rt_handle = tokio::runtime::Handle::current();
|
||||
|
||||
// Clone necessary values before moving into the watcher thread
|
||||
let watch_paths = args.watch.clone();
|
||||
let commands_clone = commands.clone();
|
||||
let server_process_clone = Arc::clone(&server_process);
|
||||
let tx_clone = tx.clone();
|
||||
|
||||
// Create a dedicated thread for the file watcher
|
||||
let watcher_thread = std::thread::spawn(move || {
|
||||
// Create a new watcher
|
||||
let mut watcher = match recommended_watcher(move |res: notify::Result<notify::Event>| {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
|
||||
println!("📦 Change detected, restarting...");
|
||||
|
||||
// Kill previous process
|
||||
if let Some(mut child) = server_process_clone.lock().unwrap().take() {
|
||||
let _ = child.kill();
|
||||
}
|
||||
|
||||
// Run new process (only the primary command gets restarted)
|
||||
if let Some(first_command) = commands.first() {
|
||||
let program = if first_command[0] == "cargo" || first_command[0].ends_with("/cargo") {
|
||||
"cargo".to_string()
|
||||
} else {
|
||||
first_command[0].clone()
|
||||
};
|
||||
|
||||
let args = if first_command[0] == "cargo" || first_command[0].ends_with("/cargo") {
|
||||
first_command.iter().skip(1).cloned().collect::<Vec<String>>()
|
||||
} else {
|
||||
first_command.iter().skip(1).cloned().collect::<Vec<String>>()
|
||||
};
|
||||
|
||||
let mut cmd = Command::new(&program);
|
||||
cmd.args(&args);
|
||||
cmd.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
|
||||
match cmd.spawn() {
|
||||
Ok(child) => {
|
||||
*server_process_clone.lock().unwrap() = Some(child);
|
||||
|
||||
// Immediately send reload signal without waiting for server
|
||||
let tx = tx_clone.clone();
|
||||
rt_handle.spawn(async move {
|
||||
// Small delay to give the server a moment to start
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
// Notify browser to reload
|
||||
let _ = tx.send(());
|
||||
});
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to spawn process: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Err(e) = res {
|
||||
eprintln!("❌ Watch error: {}", e);
|
||||
}
|
||||
}) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to create watcher: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
// Add watches
|
||||
for path in &watch_paths {
|
||||
match watcher.watch(path.as_ref(), RecursiveMode::Recursive) {
|
||||
Ok(_) => println!("👁️ Watching path: {}", path),
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to watch path '{}': {}", path, e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the thread alive
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(3600));
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the watcher thread to finish (it shouldn't unless there's an error)
|
||||
match watcher_thread.join() {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Watcher thread panicked: {:?}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_websocket_server(tx: broadcast::Sender<()>, ws_port: u16) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let addr = format!("127.0.0.1:{}", ws_port);
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
println!("WebSocket server started on ws://localhost:{}", ws_port);
|
||||
|
||||
// Spawn a task to handle WebSocket connections
|
||||
tokio::spawn(async move {
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
println!("New WebSocket connection from: {}", addr);
|
||||
let tx = tx.clone();
|
||||
let mut rx = tx.subscribe();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match accept_async(stream).await {
|
||||
Ok(ws_stream) => {
|
||||
let (mut write, _) = ws_stream.split();
|
||||
|
||||
while rx.recv().await.is_ok() {
|
||||
if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await {
|
||||
eprintln!("❌ Error sending reload message to client {}: {}", addr, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("❌ Failed to accept WebSocket connection from {}: {}", addr, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
54
rhai_autobind_macros/Cargo.lock
generated
54
rhai_autobind_macros/Cargo.lock
generated
@ -1,54 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "rhai_autobind_macros"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
heck = "0.4"
|
@ -1,33 +0,0 @@
|
||||
// calculator.rhai
|
||||
// This script demonstrates using the Calculator struct from Rust in Rhai
|
||||
|
||||
// Create a new calculator
|
||||
let calc = new_calculator();
|
||||
println("Created a new calculator with value: " + calc.value);
|
||||
|
||||
// Perform some calculations
|
||||
calc.add(5);
|
||||
println("After adding 5: " + calc.value);
|
||||
|
||||
calc.multiply(2);
|
||||
println("After multiplying by 2: " + calc.value);
|
||||
|
||||
calc.subtract(3);
|
||||
println("After subtracting 3: " + calc.value);
|
||||
|
||||
calc.divide(2);
|
||||
println("After dividing by 2: " + calc.value);
|
||||
|
||||
// Set value directly
|
||||
calc.value = 100;
|
||||
println("After setting value to 100: " + calc.value);
|
||||
|
||||
// Clear the calculator
|
||||
calc.clear();
|
||||
println("After clearing: " + calc.value);
|
||||
|
||||
// Chain operations
|
||||
let result = calc.add(10).multiply(2).subtract(5).divide(3);
|
||||
println("Result of chained operations: " + result);
|
||||
|
||||
println("Final calculator value: " + calc.value);
|
@ -1,90 +0,0 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai_autobind_macros::rhai_autobind;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
// Define a simple Calculator struct with the rhai_autobind attribute
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, rhai::CustomType)]
|
||||
#[rhai_autobind]
|
||||
pub struct Calculator {
|
||||
pub value: f64,
|
||||
}
|
||||
|
||||
impl Calculator {
|
||||
// Constructor
|
||||
pub fn new() -> Self {
|
||||
Calculator { value: 0.0 }
|
||||
}
|
||||
|
||||
// Add method
|
||||
pub fn add(&mut self, x: f64) -> f64 {
|
||||
self.value += x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Subtract method
|
||||
pub fn subtract(&mut self, x: f64) -> f64 {
|
||||
self.value -= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Multiply method
|
||||
pub fn multiply(&mut self, x: f64) -> f64 {
|
||||
self.value *= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Divide method
|
||||
pub fn divide(&mut self, x: f64) -> f64 {
|
||||
if x == 0.0 {
|
||||
println!("Error: Division by zero!");
|
||||
return self.value;
|
||||
}
|
||||
self.value /= x;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Clear method
|
||||
pub fn clear(&mut self) -> f64 {
|
||||
self.value = 0.0;
|
||||
self.value
|
||||
}
|
||||
|
||||
// Get value method
|
||||
pub fn get_value(&self) -> f64 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
println!("Rhai Calculator Example");
|
||||
println!("======================");
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the Calculator type with the engine using the generated function
|
||||
Calculator::register_rhai_bindings_for_calculator(&mut engine);
|
||||
|
||||
// Register print function for output
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
|
||||
// Create a calculator instance to demonstrate it works
|
||||
let calc = Calculator::new();
|
||||
println!("Initial value: {}", calc.value);
|
||||
|
||||
// Load and run the Rhai script
|
||||
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("examples")
|
||||
.join("rhai_autobind_example")
|
||||
.join("calculator.rhai");
|
||||
|
||||
println!("Loading Rhai script from: {}", script_path.display());
|
||||
|
||||
match engine.eval_file::<()>(script_path) {
|
||||
Ok(_) => println!("Script executed successfully"),
|
||||
Err(e) => eprintln!("Error executing script: {}\nAt: {:?}", e, e.position()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{quote, format_ident};
|
||||
use syn::{parse_macro_input, DeriveInput, Ident, Type, Fields, Visibility, ItemStruct, LitStr};
|
||||
|
||||
/// A procedural macro to automatically generate Rhai bindings for a struct.
|
||||
///
|
||||
/// This macro will generate an `impl` block for the annotated struct containing
|
||||
/// a static method, typically named `register_rhai_bindings_for_<struct_name_lowercase>`,
|
||||
/// which registers the struct, its fields (as getters), its methods, and common
|
||||
/// database operations with a Rhai `Engine`.
|
||||
///
|
||||
/// # Example Usage
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // Assuming MyDb is your database connection type
|
||||
/// // and it's wrapped in an Arc for sharing with Rhai.
|
||||
/// use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
/// use rhai_autobind_macros::rhai_model_export;
|
||||
///
|
||||
/// // Define your database type (placeholder)
|
||||
/// struct MyDb;
|
||||
/// impl MyDb {
|
||||
/// fn new() -> Self { MyDb }
|
||||
/// }
|
||||
///
|
||||
/// #[rhai_model_export(db_type = "Arc<MyDb>")]
|
||||
/// pub struct User {
|
||||
/// pub id: i64,
|
||||
/// pub name: String,
|
||||
/// }
|
||||
///
|
||||
/// impl User {
|
||||
/// // A constructor or builder Rhai can use
|
||||
/// pub fn new(id: i64, name: String) -> Self {
|
||||
/// Self { id, name }
|
||||
/// }
|
||||
///
|
||||
/// // An example method
|
||||
/// pub fn greet(&self) -> String {
|
||||
/// format!("Hello, {}!", self.name)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let db_instance = Arc::new(MyDb::new());
|
||||
///
|
||||
/// // The generated function is called here
|
||||
/// User::register_rhai_bindings_for_user(&mut engine, db_instance);
|
||||
///
|
||||
/// // Now you can use User in Rhai scripts
|
||||
/// // let user = User::new(1, "Test User");
|
||||
/// // print(user.name);
|
||||
/// // print(user.greet());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Macro Attributes
|
||||
///
|
||||
/// - `db_type = "path::to::YourDbType"`: **Required**. Specifies the fully qualified
|
||||
/// Rust type of your database connection/handler that will be passed to the
|
||||
/// generated registration function and used for database operations.
|
||||
/// The type should be enclosed in string quotes, e.g., `"Arc<crate::db::MyDatabase>"`.
|
||||
///
|
||||
/// - `collection_name = "your_collection_name"`: **Optional**. Specifies the name of the
|
||||
/// database collection associated with this model. If not provided, it defaults
|
||||
/// to the struct name converted to snake_case and pluralized (e.g., `User` -> `"users"`).
|
||||
/// Enclose in string quotes, e.g., `"user_profiles"`.
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MacroArgs {
|
||||
db_type: syn::Type,
|
||||
collection_name: Option<String>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for MacroArgs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut db_type_str: Option<LitStr> = None;
|
||||
let mut collection_name_str: Option<LitStr> = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let ident: Ident = input.parse()?;
|
||||
let _eq_token: syn::token::Eq = input.parse()?;
|
||||
if ident == "db_type" {
|
||||
db_type_str = Some(input.parse()?);
|
||||
}
|
||||
else if ident == "collection_name" {
|
||||
collection_name_str = Some(input.parse()?);
|
||||
}
|
||||
else {
|
||||
return Err(syn::Error::new(ident.span(), "Unknown attribute argument"));
|
||||
}
|
||||
if !input.is_empty() {
|
||||
let _comma: syn::token::Comma = input.parse()?;
|
||||
}
|
||||
}
|
||||
|
||||
let db_type_str = db_type_str.ok_or_else(|| syn::Error::new(input.span(), "Missing required attribute `db_type`"))?;
|
||||
let db_type: syn::Type = syn::parse_str(&db_type_str.value())?;
|
||||
let collection_name = collection_name_str.map(|s| s.value());
|
||||
|
||||
Ok(MacroArgs { db_type, collection_name })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn rhai_model_export(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let macro_args = parse_macro_input!(attr as MacroArgs);
|
||||
let input_struct = parse_macro_input!(item as ItemStruct);
|
||||
|
||||
let struct_name = &input_struct.ident;
|
||||
let struct_name_str = struct_name.to_string();
|
||||
let struct_name_lowercase = struct_name_str.to_lowercase();
|
||||
|
||||
let db_type = macro_args.db_type;
|
||||
let collection_name = macro_args.collection_name.unwrap_or_else(|| {
|
||||
// Basic pluralization, could use `heck` or `inflector` for better results
|
||||
if struct_name_lowercase.ends_with('y') {
|
||||
format!("{}ies", &struct_name_lowercase[..struct_name_lowercase.len()-1])
|
||||
} else if struct_name_lowercase.ends_with('s') {
|
||||
format!("{}es", struct_name_lowercase)
|
||||
} else {
|
||||
format!("{}s", struct_name_lowercase)
|
||||
}
|
||||
});
|
||||
|
||||
let generated_registration_fn_name = format_ident!("register_rhai_bindings_for_{}", struct_name_lowercase);
|
||||
|
||||
// Placeholder for generated getters and method registrations
|
||||
let mut field_getters = Vec::new();
|
||||
if let Fields::Named(fields) = &input_struct.fields {
|
||||
for field in fields.named.iter() {
|
||||
if let (Some(field_name), Visibility::Public(_)) = (&field.ident, &field.vis) {
|
||||
let field_name_str = field_name.to_string();
|
||||
let getter_fn = quote! {
|
||||
engine.register_get(#field_name_str, |s: &mut #struct_name| s.#field_name.clone());
|
||||
// TODO: Handle non-cloneable types or provide options
|
||||
};
|
||||
field_getters.push(getter_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Parse methods from the struct's impl blocks (this is complex)
|
||||
// TODO: Generate wrappers for DB operations using rhai_wrapper macros
|
||||
|
||||
let output = quote! {
|
||||
#input_struct // Re-emit the original struct definition
|
||||
|
||||
impl #struct_name {
|
||||
pub fn #generated_registration_fn_name(
|
||||
engine: &mut rhai::Engine,
|
||||
db: #db_type
|
||||
) {
|
||||
// Bring rhai_wrapper into scope for generated DB functions later
|
||||
// Users might need to ensure rhai_wrapper is a dependency of their crate.
|
||||
// use rhai_wrapper::*;
|
||||
|
||||
engine.build_type::<#struct_name>();
|
||||
|
||||
// Register getters
|
||||
#(#field_getters)*
|
||||
|
||||
// Placeholder for constructor registration
|
||||
// Example: engine.register_fn("new_user", User::new);
|
||||
|
||||
// Placeholder for method registration
|
||||
// Example: engine.register_fn("greet", User::greet);
|
||||
|
||||
// Placeholder for DB function registrations
|
||||
// e.g., get_by_id, get_all, save, delete
|
||||
// let get_by_id_fn_name = format!("get_{}_by_id", #struct_name_lowercase);
|
||||
// engine.register_fn(&get_by_id_fn_name, ...wrap_option_method_result!(...));
|
||||
|
||||
println!("Registered {} with Rhai (collection: '{}') using DB type {}",
|
||||
#struct_name_str,
|
||||
#collection_name,
|
||||
stringify!(#db_type)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(output)
|
||||
}
|
1563
server/Cargo.lock
generated
1563
server/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
http = "0.2"
|
||||
httparse = "1"
|
||||
rhai = { version = "1.21", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tera = "1.0"
|
||||
once_cell = "1"
|
||||
rhai_tera = { path = "../rhai_tera" }
|
||||
calendar = { path = "../components/calendar" }
|
@ -1,51 +0,0 @@
|
||||
# Calendar Server Example
|
||||
|
||||
A simple Rust web server using Hyper that exposes an `/all_calendars` endpoint.
|
||||
|
||||
## Running the server
|
||||
|
||||
```bash
|
||||
# Navigate to the examples directory
|
||||
cd /path/to/examples
|
||||
|
||||
# Build and run the server
|
||||
cargo run
|
||||
```
|
||||
|
||||
Once the server is running, you can access the endpoint at:
|
||||
- http://127.0.0.1:8080/all_calendars
|
||||
|
||||
## Features
|
||||
- Simple HTTP server using Hyper
|
||||
- Single endpoint that returns "Hello World"
|
||||
|
||||
|
||||
Sure thing! Here’s the Markdown version you can copy-paste directly into your README.md:
|
||||
|
||||
## 🔁 Live Reload (Hot Reload for Development)
|
||||
|
||||
To automatically recompile and restart your example server on file changes (e.g. Rust code, templates, Rhai scripts), you can use [`cargo-watch`](https://github.com/watchexec/cargo-watch):
|
||||
|
||||
### ✅ Step 1: Install `cargo-watch`
|
||||
|
||||
```bash
|
||||
cargo install cargo-watch
|
||||
```
|
||||
|
||||
### ✅ Step 2: Run the server with live reload
|
||||
|
||||
cargo watch -x 'run --example server'
|
||||
|
||||
This will:
|
||||
• Watch for file changes in your project
|
||||
• Rebuild and re-run examples/server.rs whenever you make a change
|
||||
|
||||
### 🧠 Bonus: Watch additional folders
|
||||
|
||||
To also reload when .tera templates or .rhai scripts change:
|
||||
|
||||
cargo watch -w src -w examples -w src/templates -w src/scripts -x 'run --example server'
|
||||
|
||||
### 💡 Optional: Clear terminal on each reload
|
||||
|
||||
cargo watch -c -x 'run --example server'
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# 🚀 Start dev server with file watching and browser reload
|
||||
reloadd \
|
||||
--watch src \
|
||||
--watch src/templates \
|
||||
--watch examples \
|
||||
--port 8080 \
|
||||
--run "cargo build" \
|
||||
--run "cargo run --example server"
|
@ -1,80 +0,0 @@
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::StatusCode;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
extern crate calendar;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||
|
||||
let make_svc = make_service_fn(|_conn| async {
|
||||
Ok::<_, Infallible>(service_fn(|req| async {
|
||||
// Remove the /calendar prefix and pass the modified request to the calendar handler
|
||||
if req.uri().path().starts_with("/calendar/") {
|
||||
calendar::handle_request(remove_path_prefix(req, "/calendar")).await
|
||||
} else {
|
||||
let mut resp = Response::new(Body::from("Not Found"));
|
||||
*resp.status_mut() = StatusCode::NOT_FOUND;
|
||||
Ok(resp)
|
||||
}
|
||||
}))
|
||||
});
|
||||
|
||||
println!("Proxy server running at http://{}", addr);
|
||||
|
||||
if let Err(e) = Server::bind(&addr).serve(make_svc).await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a specified prefix from the request's URI path
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `req` - The original request
|
||||
/// * `prefix` - The prefix to remove from the path (e.g., "/calendar")
|
||||
///
|
||||
/// # Returns
|
||||
/// A new request with the modified path
|
||||
fn remove_path_prefix(req: Request<Body>, prefix: &str) -> Request<Body> {
|
||||
let (parts, body) = req.into_parts();
|
||||
let mut new_parts = parts;
|
||||
|
||||
// Get the original path for prefix calculation
|
||||
let original_path = new_parts.uri.path();
|
||||
|
||||
// Calculate the prefix length (including the trailing slash if present)
|
||||
let prefix_len = if original_path.starts_with(&format!("{}/", prefix)) {
|
||||
prefix.len() + 1
|
||||
} else {
|
||||
prefix.len()
|
||||
};
|
||||
|
||||
// Build a new URI with the prefix removed
|
||||
let mut uri_builder = hyper::Uri::builder()
|
||||
.scheme(new_parts.uri.scheme_str().unwrap_or("http"));
|
||||
|
||||
// Only set authority if it exists
|
||||
if let Some(auth) = new_parts.uri.authority() {
|
||||
uri_builder = uri_builder.authority(auth.clone());
|
||||
}
|
||||
|
||||
// Create path and query string
|
||||
let mut path_query = original_path[prefix_len..].to_string();
|
||||
|
||||
// Add query parameters if they exist
|
||||
if let Some(q) = new_parts.uri.query() {
|
||||
path_query.push('?');
|
||||
path_query.push_str(q);
|
||||
}
|
||||
|
||||
let new_uri = uri_builder
|
||||
.path_and_query(path_query)
|
||||
.build()
|
||||
.unwrap_or(new_parts.uri.clone());
|
||||
|
||||
new_parts.uri = new_uri;
|
||||
Request::from_parts(new_parts, body)
|
||||
}
|
Reference in New Issue
Block a user