major changes
- Added documentation via mdbook - Created basic VS code extension - Implemented if else blocks and changed some syntax - fixed some issues
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
target
|
target
|
||||||
|
node_modules
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -271,6 +271,7 @@ name = "rush"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
"libc",
|
||||||
"predicates 1.0.8",
|
"predicates 1.0.8",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
|
|||||||
674
LICENSE.txt
Normal file
674
LICENSE.txt
Normal file
File diff suppressed because it is too large
Load Diff
93
README.md
93
README.md
@@ -1,6 +1,91 @@
|
|||||||
# rush - A Modern Shell Scripting Language Interpreter
|
# Rush
|
||||||
|
|
||||||
## Overview
|
> ⚠️ **AI-Generated Content Notice**: Much of this project's documentation has been generated with AI and may contain errors or incomplete information. This will get replaced as development continues. **This is experimental software and NOT recommended for production use.**
|
||||||
Rush is a modern shell scripting language interpreter designed to execute scripts written in the `.rsh` format. It supports features such as variable assignment, control flow, and parallel execution, making it a powerful tool for automating tasks and scripting.
|
|
||||||
|
|
||||||
More info coming soon
|
Rush is an experimental shell scripting language interpreter that combines simple syntax with strict validation and built-in concurrency.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Variable interpolation** with validation
|
||||||
|
- **Built-in variables** (`$USER`, `$HOME`, `$IS_ROOT`)
|
||||||
|
- **Control flow** with `if`/`else` and `not`
|
||||||
|
- **For loops** for iteration
|
||||||
|
- **Parallel execution** blocks for concurrent tasks
|
||||||
|
- **Strict parsing** that catches errors before execution
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
**Build:**
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Your first script** (`hello.rsh`):
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
NAME = "World"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run:**
|
||||||
|
```bash
|
||||||
|
./target/debug/rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
📚 **[Read the full documentation](docs/)** (built with mdBook)
|
||||||
|
|
||||||
|
**Build and view docs locally:**
|
||||||
|
```bash
|
||||||
|
mdbook serve docs
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://localhost:3000 in your browser.
|
||||||
|
|
||||||
|
**Or build static HTML:**
|
||||||
|
```bash
|
||||||
|
mdbook build docs
|
||||||
|
# Open docs/book/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Scripts
|
||||||
|
|
||||||
|
The documentation includes comprehensive examples:
|
||||||
|
|
||||||
|
- **Web Deployment** - Multi-server deployment with parallel execution
|
||||||
|
- **System Maintenance** - Permission-aware system tasks
|
||||||
|
- **Data Pipeline** - Parallel batch processing
|
||||||
|
- **CI/CD Pipeline** - Build, test, and deploy automation
|
||||||
|
- **Database Backup** - Backup with integrity verification
|
||||||
|
|
||||||
|
See the [Examples](docs/src/examples/) section in the documentation.
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
**⚠️ Experimental - Not for Production**
|
||||||
|
|
||||||
|
Rush is under active development. The language design and implementation are subject to change. Current capabilities:
|
||||||
|
|
||||||
|
- ✅ Basic scripting features
|
||||||
|
- ✅ Parallel execution
|
||||||
|
- ✅ Variable interpolation
|
||||||
|
- ⚠️ Limited command support
|
||||||
|
- ❌ No package management
|
||||||
|
- ❌ No module system
|
||||||
|
- ❌ Limited error handling
|
||||||
|
|
||||||
|
**Do not use Rush for:**
|
||||||
|
- Production systems
|
||||||
|
- Critical automation
|
||||||
|
- Enterprise deployments
|
||||||
|
- Anything requiring stability
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Found an issue? Have a suggestion? Contributions are welcome! This project is experimental and could benefit from community input.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See LICENSE file for details.
|
||||||
|
|||||||
2
docs/.gitignore
vendored
Normal file
2
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
book
|
||||||
|
docs/book/
|
||||||
14
docs/book.toml
Normal file
14
docs/book.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[book]
|
||||||
|
authors = ["tototomate123"]
|
||||||
|
language = "en"
|
||||||
|
src = "src"
|
||||||
|
title = "Rush Documentation"
|
||||||
|
description = "Documentation for the Rush experimental scripting language"
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
git-repository-url = "https://github.com/yourusername/rush"
|
||||||
|
edit-url-template = "https://github.com/yourusername/rush/edit/main/docs/{path}"
|
||||||
|
|
||||||
|
[output.html.playground]
|
||||||
|
editable = false
|
||||||
|
copyable = true
|
||||||
24
docs/src/SUMMARY.md
Normal file
24
docs/src/SUMMARY.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
[Introduction](./introduction.md)
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
- [Installation](./getting-started/installation.md)
|
||||||
|
- [Quick Start](./getting-started/quick-start.md)
|
||||||
|
|
||||||
|
# Language Guide
|
||||||
|
|
||||||
|
- [Variables](./language/variables.md)
|
||||||
|
- [Control Flow](./language/control-flow.md)
|
||||||
|
- [Loops](./language/loops.md)
|
||||||
|
- [Parallel Execution](./language/parallel.md)
|
||||||
|
- [Built-in Variables](./language/builtins.md)
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
- [Web Deployment](./examples/web-deploy.md)
|
||||||
|
- [System Maintenance](./examples/system-maintenance.md)
|
||||||
|
- [Data Pipeline](./examples/data-pipeline.md)
|
||||||
|
- [CI/CD Pipeline](./examples/ci-pipeline.md)
|
||||||
|
- [Database Backup](./examples/db-backup.md)
|
||||||
65
docs/src/examples/ci-pipeline.md
Normal file
65
docs/src/examples/ci-pipeline.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# CI/CD Pipeline
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Continuous Integration/Deployment pipeline with parallel testing.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/ci_pipeline.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
PROJECT = "my-rust-app"
|
||||||
|
BRANCH = "main"
|
||||||
|
BUILD_ID = "build-12345"
|
||||||
|
|
||||||
|
echo "=== CI/CD Pipeline ==="
|
||||||
|
echo "Project: $PROJECT"
|
||||||
|
|
||||||
|
# Environment validation
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "ERROR: Do not run CI/CD pipeline as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build stages (sequential)
|
||||||
|
for stage in checkout lint test build package {
|
||||||
|
echo "[$BUILD_ID] Stage: $stage"
|
||||||
|
if $stage {
|
||||||
|
echo "[$BUILD_ID] ✓ $stage completed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run tests in parallel
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[unit-tests] Running unit tests..."
|
||||||
|
echo "[unit-tests] 127 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[integration-tests] Running integration tests..."
|
||||||
|
echo "[integration-tests] 45 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[lint-check] Running cargo clippy..."
|
||||||
|
echo "[lint-check] No warnings found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Multi-environment deployment
|
||||||
|
for env in dev staging prod {
|
||||||
|
DEPLOY_URL = "https://$PROJECT.$env.example.com"
|
||||||
|
echo "[$env] Deployment target: $DEPLOY_URL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Security check**: Refuse to run as root
|
||||||
|
- **Sequential build**: Steps that depend on each other
|
||||||
|
- **Parallel tests**: Independent test suites run simultaneously
|
||||||
|
- **Multi-environment deployment**: Same code deployed to different environments
|
||||||
52
docs/src/examples/data-pipeline.md
Normal file
52
docs/src/examples/data-pipeline.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Data Pipeline
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Data processing pipeline showing how to process multiple batches in parallel.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/data_pipeline.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
DATASET = "user_analytics"
|
||||||
|
INPUT_DIR = "$HOME/data/raw"
|
||||||
|
OUTPUT_DIR = "$HOME/data/processed"
|
||||||
|
BATCH_SIZE = "1000"
|
||||||
|
|
||||||
|
echo "Data Processing Pipeline: $DATASET"
|
||||||
|
|
||||||
|
# Pre-processing stages
|
||||||
|
for stage in validate clean normalize {
|
||||||
|
STAGE_UPPER = "$stage"
|
||||||
|
echo " Stage: $STAGE_UPPER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process batches in parallel
|
||||||
|
BATCH_1_IN = "$INPUT_DIR/batch_001.csv"
|
||||||
|
BATCH_1_OUT = "$OUTPUT_DIR/batch_001.json"
|
||||||
|
# ... (define other batches)
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[batch_001] Processing $BATCH_1_IN -> $BATCH_1_OUT"
|
||||||
|
echo "[batch_001] Transformed 1000 records"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[batch_002] Processing $BATCH_2_IN -> $BATCH_2_OUT"
|
||||||
|
echo "[batch_002] Transformed 1000 records"
|
||||||
|
}
|
||||||
|
# ... (more batches)
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All batches processed successfully"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Parallel data processing**: Process multiple batches simultaneously
|
||||||
|
- **Path construction**: Building input/output file paths
|
||||||
|
- **Pipeline stages**: Sequential setup, parallel processing, sequential summary
|
||||||
72
docs/src/examples/db-backup.md
Normal file
72
docs/src/examples/db-backup.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Database Backup
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Database backup automation with validation and integrity checks.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/db_backup.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
DB_NAME = "production_db"
|
||||||
|
DB_HOST = "db.example.com"
|
||||||
|
BACKUP_ROOT = "/backups/databases"
|
||||||
|
BACKUP_PATH = "$BACKUP_ROOT/$DB_NAME"
|
||||||
|
TIMESTAMP = "2024-01-15-143022"
|
||||||
|
BACKUP_FILE = "$BACKUP_PATH/backup-$TIMESTAMP.sql.gz"
|
||||||
|
|
||||||
|
echo "Database Backup Script"
|
||||||
|
|
||||||
|
# Permission check
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Running as $USER (not root)"
|
||||||
|
echo "Ensure $USER has database access"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pre-backup validation
|
||||||
|
for check in connectivity disk_space permissions {
|
||||||
|
echo " Checking $check..."
|
||||||
|
if $check {
|
||||||
|
echo " ✓ $check OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup stages
|
||||||
|
for stage in dump compress encrypt verify {
|
||||||
|
STAGE_FILE = "$BACKUP_PATH/$stage.tmp"
|
||||||
|
echo "[$stage] Processing..."
|
||||||
|
if $stage {
|
||||||
|
echo "[$stage] ✓ Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel integrity verification
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[checksum] Computing SHA256..."
|
||||||
|
echo "[checksum] a1b2c3d4e5f6..."
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[compression] Verifying gzip integrity..."
|
||||||
|
echo "[compression] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[restore-test] Testing restore on sample..."
|
||||||
|
echo "[restore-test] Restore successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Backup completed successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Pre-flight validation**: Check prerequisites before starting
|
||||||
|
- **Multi-stage processing**: Sequential backup pipeline
|
||||||
|
- **Parallel verification**: Simultaneously verify different aspects
|
||||||
|
- **Comprehensive reporting**: Clear status messages throughout
|
||||||
51
docs/src/examples/system-maintenance.md
Normal file
51
docs/src/examples/system-maintenance.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# System Maintenance
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Example system maintenance script demonstrating permission checks, environment variables, and log rotation.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See the full script in `/examples/system_maintenance.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
HOSTNAME = "prod-server-01"
|
||||||
|
MAX_DISK_USAGE = "85"
|
||||||
|
LOG_DIR = "/var/log"
|
||||||
|
BACKUP_DIR = "$HOME/backups"
|
||||||
|
|
||||||
|
echo "System Maintenance Script"
|
||||||
|
echo "Running on: $HOSTNAME"
|
||||||
|
echo "User: $USER"
|
||||||
|
|
||||||
|
# Permission-aware execution
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running with root privileges"
|
||||||
|
|
||||||
|
for check in disk memory services {
|
||||||
|
echo "Checking $check status..."
|
||||||
|
if $check {
|
||||||
|
echo " ✓ $check is healthy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Running as $USER (limited permissions)"
|
||||||
|
echo "Some checks may be skipped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log rotation planning
|
||||||
|
echo "Preparing log rotation in $LOG_DIR"
|
||||||
|
|
||||||
|
for days in 1 7 30 {
|
||||||
|
LOG_ARCHIVE = "$BACKUP_DIR/logs-$days-days-old.tar.gz"
|
||||||
|
echo "Would archive logs older than $days days to $LOG_ARCHIVE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Permission-aware execution**: Different behavior for root vs normal user
|
||||||
|
- **String interpolation**: Building paths with `$HOME`, `$USER`
|
||||||
|
- **Loops for maintenance tasks**: Iterating over checks and retention periods
|
||||||
184
docs/src/examples/web-deploy.md
Normal file
184
docs/src/examples/web-deploy.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# Web Deployment
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
This example demonstrates a web application deployment pipeline using Rush's parallel execution capabilities.
|
||||||
|
|
||||||
|
## Full Script
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Web deployment pipeline example
|
||||||
|
# Shows conditional logic, loops, and parallel execution
|
||||||
|
|
||||||
|
APP_NAME = "my-app"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$APP_NAME"
|
||||||
|
BUILD_DIR = "./dist"
|
||||||
|
|
||||||
|
echo "Starting deployment for $APP_NAME v$VERSION to $ENV"
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root. Some operations may fail."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build steps
|
||||||
|
STEPS = "lint test build"
|
||||||
|
for step in lint test build {
|
||||||
|
echo "[$step] Running..."
|
||||||
|
|
||||||
|
if $step {
|
||||||
|
# Simulate the build step
|
||||||
|
echo "[$step] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy in parallel
|
||||||
|
echo "Deploying to multiple servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Server 1: Syncing files to web-1.$ENV.example.com"
|
||||||
|
echo "Server 1: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 2: Syncing files to web-2.$ENV.example.com"
|
||||||
|
echo "Server 2: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 3: Syncing files to web-3.$ENV.example.com"
|
||||||
|
echo "Server 3: Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All servers updated with $APP_NAME v$VERSION"
|
||||||
|
|
||||||
|
# Post-deployment checks
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
FULL_HOST = "$server.$ENV.example.com"
|
||||||
|
echo "Health check: $FULL_HOST - OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Deployment complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features Demonstrated
|
||||||
|
|
||||||
|
### 1. Configuration Variables
|
||||||
|
|
||||||
|
The script starts by defining configuration:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP_NAME = "my-app"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$APP_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
These variables are used throughout the script, making it easy to adapt for different applications.
|
||||||
|
|
||||||
|
### 2. Permission Check
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root. Some operations may fail."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The script warns if not running as root, but continues anyway (since deployment might work without root depending on file permissions).
|
||||||
|
|
||||||
|
### 3. Sequential Build Steps
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for step in lint test build {
|
||||||
|
echo "[$step] Running..."
|
||||||
|
|
||||||
|
if $step {
|
||||||
|
echo "[$step] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build steps run sequentially since each step depends on the previous one completing successfully.
|
||||||
|
|
||||||
|
### 4. Parallel Server Deployment
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Server 1: Syncing files to web-1.$ENV.example.com"
|
||||||
|
echo "Server 1: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 2: Syncing files to web-2.$ENV.example.com"
|
||||||
|
echo "Server 2: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 3: Syncing files to web-3.$ENV.example.com"
|
||||||
|
echo "Server 3: Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Servers are updated in parallel since they're independent. This saves significant time vs deploying sequentially.
|
||||||
|
|
||||||
|
### 5. Post-Deployment Verification
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
FULL_HOST = "$server.$ENV.example.com"
|
||||||
|
echo "Health check: $FULL_HOST - OK"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After deployment, verify each server is healthy.
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Starting deployment for my-app v1.2.3 to staging
|
||||||
|
Warning: Not running as root. Some operations may fail.
|
||||||
|
[lint] Running...
|
||||||
|
[lint] Complete
|
||||||
|
[test] Running...
|
||||||
|
[test] Complete
|
||||||
|
[build] Running...
|
||||||
|
[build] Complete
|
||||||
|
Deploying to multiple servers...
|
||||||
|
Server 1: Syncing files to web-1.staging.example.com
|
||||||
|
Server 2: Syncing files to web-2.staging.example.com
|
||||||
|
Server 3: Syncing files to web-3.staging.example.com
|
||||||
|
Server 1: Deployment complete
|
||||||
|
Server 2: Deployment complete
|
||||||
|
Server 3: Deployment complete
|
||||||
|
All servers updated with my-app v1.2.3
|
||||||
|
Health check: web-1.staging.example.com - OK
|
||||||
|
Health check: web-2.staging.example.com - OK
|
||||||
|
Health check: web-3.staging.example.com - OK
|
||||||
|
Deployment complete!
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Output from parallel tasks may be interleaved.
|
||||||
|
|
||||||
|
## Adapting for Your Use
|
||||||
|
|
||||||
|
To use this for real deployments:
|
||||||
|
|
||||||
|
1. **Change configuration variables** to match your app
|
||||||
|
2. **Replace echo with actual commands** (rsync, scp, etc.)
|
||||||
|
3. **Add error handling** (check command exit codes)
|
||||||
|
4. **Customize server list** for your infrastructure
|
||||||
|
5. **Add real health checks** (curl, wget, etc.)
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Parallel Execution](../language/parallel.md) - Learn more about parallel blocks
|
||||||
|
- [Loops](../language/loops.md) - Sequential iteration patterns
|
||||||
|
- [Variables](../language/variables.md) - Working with configuration
|
||||||
72
docs/src/getting-started/installation.md
Normal file
72
docs/src/getting-started/installation.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Rush is written in Rust, so you'll need:
|
||||||
|
- Rust toolchain (rustc, cargo)
|
||||||
|
- Git (to clone the repository)
|
||||||
|
|
||||||
|
## Installing Rust
|
||||||
|
|
||||||
|
If you don't have Rust installed, get it from [rustup.rs](https://rustup.rs/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building Rush
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/rush.git
|
||||||
|
cd rush
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the project:
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The binary will be available at `./target/release/rush`
|
||||||
|
|
||||||
|
## Optional: Install System-Wide
|
||||||
|
|
||||||
|
To make Rush available system-wide:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs Rush to `~/.cargo/bin/rush` (make sure `~/.cargo/bin` is in your PATH).
|
||||||
|
|
||||||
|
## Verifying Installation
|
||||||
|
|
||||||
|
Test your installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./target/release/rush --version
|
||||||
|
# or if installed system-wide:
|
||||||
|
rush --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a test script `hello.rsh`:
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
NAME = "World"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Make it executable and run it:
|
||||||
|
```bash
|
||||||
|
chmod +x hello.rsh
|
||||||
|
./target/release/rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see: `Hello, World!`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that Rush is installed, proceed to the [Quick Start](./quick-start.md) guide.
|
||||||
172
docs/src/getting-started/quick-start.md
Normal file
172
docs/src/getting-started/quick-start.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Quick Start
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
This guide will walk you through creating your first Rush script.
|
||||||
|
|
||||||
|
## Your First Script
|
||||||
|
|
||||||
|
Create a file called `hello.rsh`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Variables are assigned with =
|
||||||
|
NAME = "Rush"
|
||||||
|
echo "Hello from $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
```bash
|
||||||
|
rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Hello from Rush!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables and Interpolation
|
||||||
|
|
||||||
|
Variables are automatically interpolated in strings:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
FULL_NAME = "$PROJECT-v$VERSION"
|
||||||
|
|
||||||
|
echo "Building $FULL_NAME"
|
||||||
|
# Output: Building my-app-v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Built-in Variables
|
||||||
|
|
||||||
|
Rush provides several built-in variables:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Current user: $USER"
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running as normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Logic
|
||||||
|
|
||||||
|
Use `if`/`else` for branching:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running with elevated privileges"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loops
|
||||||
|
|
||||||
|
Iterate over items with `for`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Loop over space-separated items
|
||||||
|
for name in Alice Bob Charlie {
|
||||||
|
echo "Hello, $name!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use variables in loops
|
||||||
|
SERVERS = "web-1 web-2 web-3"
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parallel Execution
|
||||||
|
|
||||||
|
Run tasks concurrently:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Task 1: Starting..."
|
||||||
|
echo "Task 1: Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 2: Starting..."
|
||||||
|
echo "Task 2: Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 3: Starting..."
|
||||||
|
echo "Task 3: Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All tasks finished"
|
||||||
|
```
|
||||||
|
|
||||||
|
The output from parallel blocks may be interleaved since they run concurrently.
|
||||||
|
|
||||||
|
## A Complete Example
|
||||||
|
|
||||||
|
Here's a more complete script showing multiple features:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT = "web-app"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$PROJECT"
|
||||||
|
|
||||||
|
echo "Deployment Script for $PROJECT"
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Pre-flight checks
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not root. Some operations may fail."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build steps
|
||||||
|
echo "Running build steps:"
|
||||||
|
for step in lint test build {
|
||||||
|
echo " - $step"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy to multiple servers in parallel
|
||||||
|
echo ""
|
||||||
|
echo "Deploying to $ENV servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[server-1] Deploying $PROJECT"
|
||||||
|
echo "[server-1] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[server-2] Deploying $PROJECT"
|
||||||
|
echo "[server-2] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Deployment complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Learn more about Rush's features:
|
||||||
|
- [Variables](../language/variables.md) - Deep dive into variable handling
|
||||||
|
- [Control Flow](../language/control-flow.md) - Conditionals and branching
|
||||||
|
- [Loops](../language/loops.md) - Iteration patterns
|
||||||
|
- [Parallel Execution](../language/parallel.md) - Concurrency in Rush
|
||||||
85
docs/src/introduction.md
Normal file
85
docs/src/introduction.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Rush Documentation
|
||||||
|
|
||||||
|
Welcome to the Rush programming language documentation!
|
||||||
|
|
||||||
|
> ⚠️ **AI-Generated Documentation Notice**
|
||||||
|
>
|
||||||
|
> This documentation has been primarily generated by AI and may contain errors, inconsistencies, or incomplete information. Rush is an experimental project and is **not recommended for production use** at this time.
|
||||||
|
>
|
||||||
|
> If you find issues or have suggestions, please contribute to improving this documentation.
|
||||||
|
|
||||||
|
## What is Rush?
|
||||||
|
|
||||||
|
Rush is an experimental shell scripting language that combines:
|
||||||
|
|
||||||
|
- **Simple syntax** inspired by modern scripting languages
|
||||||
|
- **Strict validation** to catch errors at parse time
|
||||||
|
- **Built-in concurrency** with parallel execution blocks
|
||||||
|
- **Type-aware variables** with automatic interpolation
|
||||||
|
- **Control flow** familiar to programmers (if/else, for loops)
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Variable Interpolation
|
||||||
|
Variables are automatically expanded in strings:
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
DIR = "$HOME/projects/$PROJECT"
|
||||||
|
echo $DIR # Outputs: /home/user/projects/my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Execution
|
||||||
|
Run tasks concurrently with ease:
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run { echo "Task 1" }
|
||||||
|
run { echo "Task 2" }
|
||||||
|
run { echo "Task 3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strict Validation
|
||||||
|
Rush validates your scripts before execution:
|
||||||
|
- Variables must be defined before use
|
||||||
|
- Syntax errors are caught immediately
|
||||||
|
- Clear error messages guide you to fixes
|
||||||
|
|
||||||
|
### Built-in Variables
|
||||||
|
Access system information easily:
|
||||||
|
- `$USER` - Current username
|
||||||
|
- `$HOME` - Home directory
|
||||||
|
- `$IS_ROOT` - Whether running as root
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Rush is in **early experimental development**. The language design and implementation are subject to change. Current capabilities include:
|
||||||
|
|
||||||
|
- ✅ Variable assignment and interpolation
|
||||||
|
- ✅ Conditional execution (if/else)
|
||||||
|
- ✅ For loops
|
||||||
|
- ✅ Parallel execution blocks
|
||||||
|
- ✅ Basic built-in commands (echo, exit, require_root)
|
||||||
|
- ⚠️ Limited external command support
|
||||||
|
- ❌ No package management
|
||||||
|
- ❌ No module system
|
||||||
|
- ❌ Limited error handling
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Rush is designed for:
|
||||||
|
- **Learning** parallel programming concepts
|
||||||
|
- **Experimenting** with shell script alternatives
|
||||||
|
- **Prototyping** automation workflows
|
||||||
|
- **Exploring** strict validation in scripting languages
|
||||||
|
|
||||||
|
**Not recommended for:**
|
||||||
|
- Production systems
|
||||||
|
- Critical automation
|
||||||
|
- Enterprise deployments
|
||||||
|
- Anything requiring stability
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Installation](./getting-started/installation.md) - Get Rush set up
|
||||||
|
- [Quick Start](./getting-started/quick-start.md) - Your first Rush script
|
||||||
|
- [Language Guide](./language/variables.md) - Learn Rush syntax
|
||||||
282
docs/src/language/builtins.md
Normal file
282
docs/src/language/builtins.md
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Built-in Variables
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush provides several built-in variables that give you access to system information.
|
||||||
|
|
||||||
|
## Available Built-ins
|
||||||
|
|
||||||
|
### `$USER`
|
||||||
|
|
||||||
|
The current username.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Running as: $USER"
|
||||||
|
# Output: Running as: alice
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Logging who ran a script
|
||||||
|
- User-specific configuration
|
||||||
|
- Checking if running as a specific user
|
||||||
|
|
||||||
|
```rush
|
||||||
|
EXPECTED_USER = "deploy"
|
||||||
|
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Script executed by: $USER"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `$HOME`
|
||||||
|
|
||||||
|
The current user's home directory.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
# Output: Home directory: /home/alice
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Building paths to user files
|
||||||
|
- Configuration file locations
|
||||||
|
- User-specific data directories
|
||||||
|
|
||||||
|
```rush
|
||||||
|
CONFIG_DIR = "$HOME/.config/myapp"
|
||||||
|
DATA_DIR = "$HOME/.local/share/myapp"
|
||||||
|
CACHE_DIR = "$HOME/.cache/myapp"
|
||||||
|
|
||||||
|
echo "Configuration: $CONFIG_DIR"
|
||||||
|
echo "Data: $DATA_DIR"
|
||||||
|
echo "Cache: $CACHE_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `$IS_ROOT`
|
||||||
|
|
||||||
|
Boolean indicating if the script is running as root (UID 0).
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running with root privileges"
|
||||||
|
} else {
|
||||||
|
echo "Running as normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Permission validation
|
||||||
|
- Security checks
|
||||||
|
- Conditional behavior based on privileges
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Require root for system operations
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "ERROR: This script must be run as root"
|
||||||
|
echo "Please run with: sudo rush script.rsh"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Proceeding with system modifications..."
|
||||||
|
```
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Warn but don't fail
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "WARNING: Not running as root"
|
||||||
|
echo "Some operations may fail"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Continuing anyway..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### User-Specific Paths
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "myapp"
|
||||||
|
CONFIG_FILE = "$HOME/.config/$PROJECT/settings.conf"
|
||||||
|
LOG_FILE = "$HOME/.local/share/$PROJECT/app.log"
|
||||||
|
|
||||||
|
echo "Config: $CONFIG_FILE"
|
||||||
|
echo "Logs: $LOG_FILE"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Validate we have the right permissions
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "This script requires root privileges"
|
||||||
|
echo "Current user: $USER"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Running as root - proceeding with installation"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Information
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "=== Environment Information ==="
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Home: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Privileges: root"
|
||||||
|
} else {
|
||||||
|
echo "Privileges: normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Behavior
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Different behavior for root vs normal user
|
||||||
|
if $IS_ROOT {
|
||||||
|
INSTALL_DIR = "/opt/myapp"
|
||||||
|
CONFIG_DIR = "/etc/myapp"
|
||||||
|
} else {
|
||||||
|
INSTALL_DIR = "$HOME/.local/share/myapp"
|
||||||
|
CONFIG_DIR = "$HOME/.config/myapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Installing to: $INSTALL_DIR"
|
||||||
|
echo "Config at: $CONFIG_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Built-in Commands
|
||||||
|
|
||||||
|
In addition to variables, Rush provides built-in commands:
|
||||||
|
|
||||||
|
### `echo`
|
||||||
|
|
||||||
|
Print output to stdout.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Hello, World!"
|
||||||
|
echo "Multiple arguments" "are joined" "with spaces"
|
||||||
|
|
||||||
|
NAME = "Alice"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `exit`
|
||||||
|
|
||||||
|
Exit the script with a status code.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Exit with success
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
# Exit with error
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Exit with custom code
|
||||||
|
CODE = "42"
|
||||||
|
exit $CODE
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common pattern:**
|
||||||
|
```rush
|
||||||
|
if not $REQUIRED_VAR {
|
||||||
|
echo "ERROR: REQUIRED_VAR must be set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Continuing..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### `require_root`
|
||||||
|
|
||||||
|
Built-in helper that ensures the script is running as root, exiting if not.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# This will exit if not root
|
||||||
|
require_root
|
||||||
|
|
||||||
|
echo "This only runs as root"
|
||||||
|
```
|
||||||
|
|
||||||
|
Equivalent to:
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "This script must be run as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Currently, Rush built-in variables are limited to `$USER`, `$HOME`, and `$IS_ROOT`.
|
||||||
|
|
||||||
|
**Not currently supported:**
|
||||||
|
- ❌ `$PATH` or other environment variables
|
||||||
|
- ❌ `$PWD` (current working directory)
|
||||||
|
- ❌ `$SHELL` (user's shell)
|
||||||
|
- ❌ Custom environment variable access (like `$MY_VAR` from the environment)
|
||||||
|
|
||||||
|
### Workaround
|
||||||
|
|
||||||
|
For now, if you need other environment variables, you would need to run external commands to get them (if supported).
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always check built-ins exist** - Validate `$USER` and `$HOME` are set
|
||||||
|
2. **Validate permissions early** - Check `$IS_ROOT` at script start
|
||||||
|
3. **Provide clear error messages** - Tell users what went wrong
|
||||||
|
4. **Document requirements** - Make permission requirements clear in comments
|
||||||
|
5. **Use meaningful exit codes** - Exit with non-zero on errors
|
||||||
|
|
||||||
|
### Example Script Template
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Script: system-setup.rsh
|
||||||
|
# Description: Configure system settings
|
||||||
|
# Requirements: Must run as root
|
||||||
|
|
||||||
|
# Validate environment
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $HOME {
|
||||||
|
echo "ERROR: HOME not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check permissions
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "ERROR: This script must be run as root"
|
||||||
|
echo "Current user: $USER"
|
||||||
|
echo "Please run: sudo rush system-setup.rsh"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script logic
|
||||||
|
echo "Configuring system as $USER (root)"
|
||||||
|
# ... rest of script ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential additions:
|
||||||
|
|
||||||
|
- Access to all environment variables
|
||||||
|
- `$PWD` for current directory
|
||||||
|
- `$HOSTNAME` for system name
|
||||||
|
- `$SHELL` for user's shell
|
||||||
|
- Function to read environment: `env("VAR_NAME")`
|
||||||
|
- Process ID: `$PID`
|
||||||
|
- Parent process ID: `$PPID`
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
214
docs/src/language/control-flow.md
Normal file
214
docs/src/language/control-flow.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Control Flow
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush provides conditional execution through `if`/`else` statements.
|
||||||
|
|
||||||
|
## Basic If Statements
|
||||||
|
|
||||||
|
The simplest form checks if a variable is defined and non-empty:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
USER_NAME = "Alice"
|
||||||
|
|
||||||
|
if $USER_NAME {
|
||||||
|
echo "Hello, $USER_NAME!"
|
||||||
|
}
|
||||||
|
# Output: Hello, Alice!
|
||||||
|
```
|
||||||
|
|
||||||
|
## If-Else
|
||||||
|
|
||||||
|
Add an `else` branch for alternative execution:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
} else {
|
||||||
|
echo "No environment set"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Negation with `not`
|
||||||
|
|
||||||
|
Use `not` to check if a variable is empty or undefined:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Not running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running as root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Condition Evaluation
|
||||||
|
|
||||||
|
In Rush, conditions are evaluated as follows:
|
||||||
|
|
||||||
|
- **Truthy:** Variable exists and has a non-empty value
|
||||||
|
- **Falsy:** Variable is empty or undefined
|
||||||
|
|
||||||
|
```rush
|
||||||
|
EMPTY = ""
|
||||||
|
FILLED = "data"
|
||||||
|
|
||||||
|
if $FILLED {
|
||||||
|
echo "This runs" # ✅ Runs
|
||||||
|
}
|
||||||
|
|
||||||
|
if $EMPTY {
|
||||||
|
echo "This doesn't run" # ❌ Skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $EMPTY {
|
||||||
|
echo "Empty variable detected" # ✅ Runs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Permission Checks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: This script should be run as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Proceeding with root privileges..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
REQUIRED_VAR = "" # Simulating an undefined/empty variable
|
||||||
|
|
||||||
|
if not $REQUIRED_VAR {
|
||||||
|
echo "ERROR: REQUIRED_VAR must be set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Configuration valid"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature Flags
|
||||||
|
|
||||||
|
```rush
|
||||||
|
DEBUG_MODE = "true"
|
||||||
|
|
||||||
|
if $DEBUG_MODE {
|
||||||
|
echo "Debug mode enabled"
|
||||||
|
echo "Verbose logging active"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Step Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
CONFIG_FILE = "/etc/myapp/config.conf"
|
||||||
|
DATA_DIR = "/var/lib/myapp"
|
||||||
|
|
||||||
|
if $CONFIG_FILE {
|
||||||
|
echo "Config: $CONFIG_FILE"
|
||||||
|
|
||||||
|
if $DATA_DIR {
|
||||||
|
echo "Data directory: $DATA_DIR"
|
||||||
|
echo "All paths configured"
|
||||||
|
} else {
|
||||||
|
echo "ERROR: Data directory not set"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "ERROR: Config file not set"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nested Conditions
|
||||||
|
|
||||||
|
You can nest `if` statements:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
BACKUP_ENABLED = "yes"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
|
||||||
|
if $BACKUP_ENABLED {
|
||||||
|
echo "Backups are enabled"
|
||||||
|
} else {
|
||||||
|
echo "Warning: Backups disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush conditionals:
|
||||||
|
|
||||||
|
- ❌ No `elif` or `else if` (use nested `if` instead)
|
||||||
|
- ❌ No comparison operators (`==`, `!=`, `<`, `>`)
|
||||||
|
- ❌ No logical operators (`&&`, `||`) beyond `not`
|
||||||
|
- ❌ No string matching or regex
|
||||||
|
- ❌ No numeric comparisons
|
||||||
|
- ❌ Can only test variable truthiness
|
||||||
|
|
||||||
|
### Workaround Example
|
||||||
|
|
||||||
|
Since there's no `elif`, use nested conditions:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
MODE = "production"
|
||||||
|
|
||||||
|
if $MODE {
|
||||||
|
# Check what MODE actually is would require comparison operators
|
||||||
|
# For now, any non-empty value is truthy
|
||||||
|
echo "Mode is set to: $MODE"
|
||||||
|
} else {
|
||||||
|
echo "Mode not set, using default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep conditions simple** - Rush conditionals are basic, so keep logic straightforward
|
||||||
|
2. **Use descriptive variable names** - Make intent clear since you can't use complex expressions
|
||||||
|
3. **Validate early** - Check required conditions at the start of your script
|
||||||
|
4. **Provide clear messages** - Echo helpful information in each branch
|
||||||
|
5. **Use exit codes** - Exit with non-zero status on errors
|
||||||
|
|
||||||
|
### Example: Script Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Validate environment
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $HOME {
|
||||||
|
echo "ERROR: HOME not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proceed with script
|
||||||
|
echo "Environment validated for $USER"
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
|
||||||
|
# Execute main logic here...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential future enhancements to control flow:
|
||||||
|
|
||||||
|
- `elif` for multi-way branching
|
||||||
|
- Comparison operators for string/numeric comparison
|
||||||
|
- Logical operators for combining conditions
|
||||||
|
- Pattern matching
|
||||||
|
- Case/switch statements
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
264
docs/src/language/loops.md
Normal file
264
docs/src/language/loops.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Loops
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush supports iteration through `for` loops.
|
||||||
|
|
||||||
|
## Basic For Loop
|
||||||
|
|
||||||
|
Loop over a space-separated list of items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for name in Alice Bob Charlie {
|
||||||
|
echo "Hello, $name!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Hello, Alice!
|
||||||
|
Hello, Bob!
|
||||||
|
Hello, Charlie!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loop Variable
|
||||||
|
|
||||||
|
The loop variable takes on each value in sequence:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for number in 1 2 3 4 5 {
|
||||||
|
echo "Number: $number"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Number: 1
|
||||||
|
Number: 2
|
||||||
|
Number: 3
|
||||||
|
Number: 4
|
||||||
|
Number: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Iterating Over Configuration
|
||||||
|
|
||||||
|
Common pattern for processing multiple items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
SERVERS = "web-1 web-2 web-3"
|
||||||
|
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
# Deployment commands here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Variables in Loops
|
||||||
|
|
||||||
|
You can use variables within the loop body:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
BASE_URL = "https://api.example.com"
|
||||||
|
|
||||||
|
for endpoint in users posts comments {
|
||||||
|
URL = "$BASE_URL/$endpoint"
|
||||||
|
echo "Checking $URL in $ENV"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nested Loops
|
||||||
|
|
||||||
|
Loops can be nested for multi-dimensional iteration:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for env in dev staging prod {
|
||||||
|
echo "Environment: $env"
|
||||||
|
|
||||||
|
for server in web-1 web-2 {
|
||||||
|
HOSTNAME = "$server.$env.example.com"
|
||||||
|
echo " - $HOSTNAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Environment: dev
|
||||||
|
- web-1.dev.example.com
|
||||||
|
- web-2.dev.example.com
|
||||||
|
Environment: staging
|
||||||
|
- web-1.staging.example.com
|
||||||
|
- web-2.staging.example.com
|
||||||
|
Environment: prod
|
||||||
|
- web-1.prod.example.com
|
||||||
|
- web-2.prod.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditionals in Loops
|
||||||
|
|
||||||
|
Combine loops with `if` statements:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in file1 file2 file3 {
|
||||||
|
if $item {
|
||||||
|
echo "Processing $item"
|
||||||
|
# Process the file
|
||||||
|
} else {
|
||||||
|
echo "Skipping empty item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Processing Files
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for file in config.yml database.yml secrets.yml {
|
||||||
|
FILE_PATH = "/etc/app/$file"
|
||||||
|
echo "Loading $FILE_PATH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Stage Pipeline
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Build Pipeline"
|
||||||
|
|
||||||
|
for stage in fetch compile test package deploy {
|
||||||
|
echo "[$stage] Starting..."
|
||||||
|
# Stage-specific logic here
|
||||||
|
echo "[$stage] Complete"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Configuration
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP = "myapp"
|
||||||
|
|
||||||
|
for region in us-east us-west eu-west {
|
||||||
|
SERVER = "$APP-$region"
|
||||||
|
echo "Configuring $SERVER"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleanup Tasks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
TEMP_DIR = "/tmp/build"
|
||||||
|
|
||||||
|
for artifact in logs cache temp build {
|
||||||
|
ARTIFACT_PATH = "$TEMP_DIR/$artifact"
|
||||||
|
echo "Removing $ARTIFACT_PATH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loop Variable Scope
|
||||||
|
|
||||||
|
Loop variables persist after the loop ends with their last value:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in a b c {
|
||||||
|
echo "Current: $item"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Last item was: $item"
|
||||||
|
# Output: Last item was: c
|
||||||
|
```
|
||||||
|
|
||||||
|
This may change in future versions to limit scope to the loop body.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush loops:
|
||||||
|
|
||||||
|
- ❌ Only `for` loops (no `while` or `until`)
|
||||||
|
- ❌ No `break` or `continue` statements
|
||||||
|
- ❌ No range syntax (like `1..10`)
|
||||||
|
- ❌ Cannot iterate over arrays (Rush has no arrays)
|
||||||
|
- ❌ Cannot iterate over command output (like `for i in $(ls)`)
|
||||||
|
- ❌ No C-style `for(i=0; i<10; i++)` syntax
|
||||||
|
- ❌ Loop items must be explicitly listed
|
||||||
|
|
||||||
|
### Workarounds
|
||||||
|
|
||||||
|
Since you can't iterate over command output, you need to explicitly list items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ Not supported (yet):
|
||||||
|
# for file in $(ls *.txt) { ... }
|
||||||
|
|
||||||
|
# ✅ Instead, list explicitly:
|
||||||
|
for file in file1.txt file2.txt file3.txt {
|
||||||
|
echo "Processing $file"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use descriptive loop variables** - `for server in ...` is clearer than `for i in ...`
|
||||||
|
2. **Keep loop bodies simple** - Complex logic should be broken down
|
||||||
|
3. **Document what you're iterating** - Add comments for clarity
|
||||||
|
4. **Consider parallel execution** - If iterations are independent, use `parallel` blocks
|
||||||
|
|
||||||
|
### Good Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Deploy to multiple environments
|
||||||
|
echo "Starting deployment"
|
||||||
|
|
||||||
|
for env in dev staging production {
|
||||||
|
echo ""
|
||||||
|
echo "=== Deploying to $env ==="
|
||||||
|
|
||||||
|
DEPLOY_URL = "https://deploy.$env.example.com"
|
||||||
|
echo "Target: $DEPLOY_URL"
|
||||||
|
|
||||||
|
# Deployment steps
|
||||||
|
for step in backup deploy verify {
|
||||||
|
echo " [$step] Running..."
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== $env deployment complete ==="
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "All deployments finished"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining with Parallel Execution
|
||||||
|
|
||||||
|
For independent iterations, consider using parallel blocks instead:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Sequential (slow)
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel (fast)
|
||||||
|
parallel {
|
||||||
|
run { echo "Deploying to web-1" }
|
||||||
|
run { echo "Deploying to web-2" }
|
||||||
|
run { echo "Deploying to web-3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Parallel Execution](./parallel.md) for more details.
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential enhancements:
|
||||||
|
|
||||||
|
- `while` and `until` loops
|
||||||
|
- `break` and `continue` statements
|
||||||
|
- Range syntax: `for i in 1..10`
|
||||||
|
- Iterating over command output
|
||||||
|
- Array iteration
|
||||||
|
- Step values: `for i in 1..10 step 2`
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
331
docs/src/language/parallel.md
Normal file
331
docs/src/language/parallel.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# Parallel Execution
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
One of Rush's key features is built-in support for parallel execution through `parallel` blocks.
|
||||||
|
|
||||||
|
## Basic Parallel Block
|
||||||
|
|
||||||
|
Run multiple tasks concurrently:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Task 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The tasks run simultaneously, so output may be interleaved:
|
||||||
|
```
|
||||||
|
Task 1
|
||||||
|
Task 3
|
||||||
|
Task 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Parallel Execution
|
||||||
|
|
||||||
|
Use parallel blocks when:
|
||||||
|
|
||||||
|
- ✅ Tasks are **independent** (don't depend on each other)
|
||||||
|
- ✅ Tasks are **I/O bound** (network, disk operations)
|
||||||
|
- ✅ You want to **save time** by running concurrently
|
||||||
|
- ✅ Order of completion doesn't matter
|
||||||
|
|
||||||
|
Don't use parallel blocks when:
|
||||||
|
|
||||||
|
- ❌ Tasks must run in **specific order**
|
||||||
|
- ❌ Tasks **share state** or modify the same resources
|
||||||
|
- ❌ Tasks are **CPU intensive** and would compete for resources
|
||||||
|
- ❌ You need **deterministic output** order
|
||||||
|
|
||||||
|
## Practical Examples
|
||||||
|
|
||||||
|
### Deploying to Multiple Servers
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP = "my-service"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
|
||||||
|
echo "Deploying $APP v$VERSION to all servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[web-1] Starting deployment"
|
||||||
|
echo "[web-1] Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[web-2] Starting deployment"
|
||||||
|
echo "[web-2] Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[web-3] Starting deployment"
|
||||||
|
echo "[web-3] Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All servers updated!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Multiple Tests
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Running test suite in parallel..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[unit-tests] Running..."
|
||||||
|
echo "[unit-tests] 127 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[integration-tests] Running..."
|
||||||
|
echo "[integration-tests] 45 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[lint] Running..."
|
||||||
|
echo "[lint] No issues found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All tests complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Processing Batches
|
||||||
|
|
||||||
|
```rush
|
||||||
|
DATA_DIR = "$HOME/data"
|
||||||
|
|
||||||
|
echo "Processing data batches..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_001.csv"
|
||||||
|
echo "[batch-001] Processing $INPUT"
|
||||||
|
echo "[batch-001] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_002.csv"
|
||||||
|
echo "[batch-002] Processing $INPUT"
|
||||||
|
echo "[batch-002] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_003.csv"
|
||||||
|
echo "[batch-003] Processing $INPUT"
|
||||||
|
echo "[batch-003] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All batches processed!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Checking service health..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[database] Checking connection..."
|
||||||
|
echo "[database] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[cache] Checking Redis..."
|
||||||
|
echo "[cache] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[api] Checking endpoint..."
|
||||||
|
echo "[api] OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All services healthy"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable Access
|
||||||
|
|
||||||
|
Variables defined before the `parallel` block are accessible inside:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Deploying $PROJECT to server-1 ($ENV)"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Deploying $PROJECT to server-2 ($ENV)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of parallel execution in Rush:
|
||||||
|
|
||||||
|
- ❌ **Cannot define variables** inside `run` blocks
|
||||||
|
- ❌ **No synchronization** primitives (no locks, semaphores)
|
||||||
|
- ❌ **No return values** from parallel blocks
|
||||||
|
- ❌ **No error propagation** (if one task fails, others continue)
|
||||||
|
- ❌ **Fixed worker thread count** (currently hardcoded to 4)
|
||||||
|
- ❌ **No task dependencies** (can't say "run A, then B and C in parallel")
|
||||||
|
- ❌ **Output interleaving** (output from tasks mixed randomly)
|
||||||
|
|
||||||
|
### Variable Limitation Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ This doesn't work:
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
MY_VAR = "value" # ERROR: Can't define variables in run blocks
|
||||||
|
echo $MY_VAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ✅ Define variables before the parallel block:
|
||||||
|
MY_VAR = "value"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo $MY_VAR # OK: Can read existing variables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Thread Pool
|
||||||
|
|
||||||
|
Rush currently uses a fixed pool of 4 worker threads. This means:
|
||||||
|
|
||||||
|
- If you have 4 tasks, they all run simultaneously
|
||||||
|
- If you have 10 tasks, 4 run at a time, then the next 4, then the final 2
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# All run simultaneously (4 tasks, 4 threads)
|
||||||
|
parallel {
|
||||||
|
run { echo "A" }
|
||||||
|
run { echo "B" }
|
||||||
|
run { echo "C" }
|
||||||
|
run { echo "D" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs in batches (8 tasks, 4 threads)
|
||||||
|
parallel {
|
||||||
|
run { echo "1" } # First batch
|
||||||
|
run { echo "2" } # First batch
|
||||||
|
run { echo "3" } # First batch
|
||||||
|
run { echo "4" } # First batch
|
||||||
|
run { echo "5" } # Second batch (waits)
|
||||||
|
run { echo "6" } # Second batch (waits)
|
||||||
|
run { echo "7" } # Second batch (waits)
|
||||||
|
run { echo "8" } # Second batch (waits)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### I/O vs CPU Bound
|
||||||
|
|
||||||
|
Parallel execution works best for I/O-bound tasks:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ✅ Good use case (I/O bound)
|
||||||
|
parallel {
|
||||||
|
run { echo "Downloading file 1..." }
|
||||||
|
run { echo "Downloading file 2..." }
|
||||||
|
run { echo "Downloading file 3..." }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ⚠️ May not help much (CPU bound)
|
||||||
|
parallel {
|
||||||
|
run { echo "Computing hash 1..." }
|
||||||
|
run { echo "Computing hash 2..." }
|
||||||
|
run { echo "Computing hash 3..." }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining Parallel with Loops
|
||||||
|
|
||||||
|
You can't directly parallelize a loop, but you can manually write parallel tasks:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ Can't do this (loop over parallel tasks):
|
||||||
|
# for server in web-1 web-2 web-3 {
|
||||||
|
# run { echo "Deploy to $server" }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# ✅ Instead, write out each task:
|
||||||
|
parallel {
|
||||||
|
run { echo "Deploy to web-1" }
|
||||||
|
run { echo "Deploy to web-2" }
|
||||||
|
run { echo "Deploy to web-3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep run blocks simple** - Complex logic is harder to debug when parallel
|
||||||
|
2. **Prefix output** - Use `[task-name]` prefixes to identify which task is logging
|
||||||
|
3. **Make tasks independent** - Don't rely on order of execution
|
||||||
|
4. **Use for I/O operations** - Network requests, file operations, database queries
|
||||||
|
5. **Avoid shared state** - Don't modify the same resources from multiple tasks
|
||||||
|
|
||||||
|
### Good Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
BUILD_ID = "12345"
|
||||||
|
REGISTRY = "registry.example.com"
|
||||||
|
|
||||||
|
echo "Starting parallel build verification"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[checksums] Computing artifact checksums..."
|
||||||
|
echo "[checksums] SHA256: abc123def456"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[docker] Verifying container image..."
|
||||||
|
echo "[docker] Image $REGISTRY/app:$BUILD_ID verified"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[tests] Running smoke tests..."
|
||||||
|
echo "[tests] All smoke tests passed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Verification complete for build $BUILD_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential enhancements:
|
||||||
|
|
||||||
|
- Configurable worker thread count
|
||||||
|
- Error handling and propagation
|
||||||
|
- Waiting for specific tasks
|
||||||
|
- Task dependencies (DAG execution)
|
||||||
|
- Return values from parallel tasks
|
||||||
|
- Synchronized output (prevent interleaving)
|
||||||
|
- Progress indicators
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
188
docs/src/language/variables.md
Normal file
188
docs/src/language/variables.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Variables
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Variables in Rush are simple to use and automatically interpolated in strings.
|
||||||
|
|
||||||
|
## Declaration and Assignment
|
||||||
|
|
||||||
|
Variables are declared and assigned using the `=` operator:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
NAME = "Alice"
|
||||||
|
AGE = "30"
|
||||||
|
CITY = "San Francisco"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Currently, all values are treated as strings in Rush.
|
||||||
|
|
||||||
|
## Variable Naming Rules
|
||||||
|
|
||||||
|
Variable names:
|
||||||
|
- Must start with a letter or underscore
|
||||||
|
- Can contain letters, numbers, and underscores
|
||||||
|
- Are case-sensitive
|
||||||
|
- Should be in UPPER_CASE by convention (though not required)
|
||||||
|
|
||||||
|
```rush
|
||||||
|
valid_name = "yes"
|
||||||
|
ALSO_VALID = "yes"
|
||||||
|
name123 = "yes"
|
||||||
|
_private = "yes"
|
||||||
|
|
||||||
|
# Invalid (will cause parse errors):
|
||||||
|
# 123invalid = "no" # Can't start with number
|
||||||
|
# my-var = "no" # Hyphens not allowed
|
||||||
|
```
|
||||||
|
|
||||||
|
## String Interpolation
|
||||||
|
|
||||||
|
Variables are automatically interpolated when prefixed with `$`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
USER_NAME = "Alice"
|
||||||
|
GREETING = "Hello, $USER_NAME!"
|
||||||
|
echo $GREETING
|
||||||
|
# Output: Hello, Alice!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested Interpolation
|
||||||
|
|
||||||
|
Variables can reference other variables:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
VERSION = "2.1.0"
|
||||||
|
ARTIFACT = "$PROJECT-$VERSION.tar.gz"
|
||||||
|
|
||||||
|
echo $ARTIFACT
|
||||||
|
# Output: my-app-2.1.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path Construction
|
||||||
|
|
||||||
|
Common pattern for building paths:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
BASE_DIR = "$HOME/projects"
|
||||||
|
PROJECT_NAME = "rush"
|
||||||
|
PROJECT_DIR = "$BASE_DIR/$PROJECT_NAME"
|
||||||
|
|
||||||
|
echo $PROJECT_DIR
|
||||||
|
# Output: /home/user/projects/rush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation and Errors
|
||||||
|
|
||||||
|
Rush validates variable usage at parse time:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ ERROR: Variable used before definition
|
||||||
|
echo $UNDEFINED_VAR
|
||||||
|
MESSAGE = "Hello"
|
||||||
|
|
||||||
|
# ✅ CORRECT: Define before use
|
||||||
|
MESSAGE = "Hello"
|
||||||
|
echo $MESSAGE
|
||||||
|
```
|
||||||
|
|
||||||
|
This strict validation helps catch typos and undefined variable references early.
|
||||||
|
|
||||||
|
## Variable Scope
|
||||||
|
|
||||||
|
### Global Scope
|
||||||
|
|
||||||
|
Variables defined at the top level are available throughout the script:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
GLOBAL_VAR = "accessible everywhere"
|
||||||
|
|
||||||
|
if $GLOBAL_VAR {
|
||||||
|
echo $GLOBAL_VAR # ✅ Works
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in a b c {
|
||||||
|
echo $GLOBAL_VAR # ✅ Works
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loop Variables
|
||||||
|
|
||||||
|
Loop variables are scoped to the loop body:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in x y z {
|
||||||
|
# 'item' is available here
|
||||||
|
echo "Processing $item"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 'item' retains its last value after the loop
|
||||||
|
echo "Last item was: $item"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Currently, Rush has simple scoping rules. Variables defined in loops persist after the loop exits with their last assigned value.
|
||||||
|
|
||||||
|
## Built-in Variables
|
||||||
|
|
||||||
|
Rush provides several built-in variables (see [Built-in Variables](./builtins.md) for details):
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Home: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running as root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Escaping Variables
|
||||||
|
|
||||||
|
Currently, Rush does not support escaping `$` to print it literally. If you need a literal `$`, you may need to work around this limitation.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# This will attempt variable interpolation:
|
||||||
|
echo "Price: $100" # Looks for variable named "100"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Define variables at the top** of your script for clarity
|
||||||
|
2. **Use descriptive names** that indicate purpose
|
||||||
|
3. **Group related variables** together
|
||||||
|
4. **Use UPPER_CASE** for constants/configuration
|
||||||
|
5. **Validate inputs** early in your script
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
APP_NAME = "my-service"
|
||||||
|
APP_VERSION = "1.0.0"
|
||||||
|
DEPLOY_ENV = "production"
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
BASE_DIR = "/opt/apps"
|
||||||
|
APP_DIR = "$BASE_DIR/$APP_NAME"
|
||||||
|
LOG_DIR = "/var/log/$APP_NAME"
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
ARTIFACT = "$APP_NAME-$APP_VERSION.tar.gz"
|
||||||
|
ARTIFACT_URL = "https://releases.example.com/$ARTIFACT"
|
||||||
|
|
||||||
|
echo "Deploying $ARTIFACT to $DEPLOY_ENV"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush variables:
|
||||||
|
|
||||||
|
- ❌ No arrays or lists
|
||||||
|
- ❌ No numeric operations
|
||||||
|
- ❌ No string manipulation functions
|
||||||
|
- ❌ No variable expansion modifiers (like `${var:-default}`)
|
||||||
|
- ❌ All values are strings
|
||||||
|
- ❌ No explicit quoting mechanism
|
||||||
|
|
||||||
|
These limitations may be addressed in future versions of Rush.
|
||||||
@@ -16,28 +16,22 @@ impl<'a> Executor<'a> {
|
|||||||
match node {
|
match node {
|
||||||
AstNode::VariableAssignment { name, value } => {
|
AstNode::VariableAssignment { name, value } => {
|
||||||
if let AstNode::Literal(val) = *value {
|
if let AstNode::Literal(val) = *value {
|
||||||
self.env.set_variable(name, val);
|
let substituted = self.substitute_variables(&val);
|
||||||
|
self.env.set_variable(name, substituted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Command { name, args } => {
|
AstNode::Command { name, args } => {
|
||||||
// convert args to strings
|
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if its a variable reference
|
Some(self.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
self.env.get_variable(&s[1..]).map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// built in commands :D
|
|
||||||
//todo: move and add more builtins
|
//todo: move and add more builtins
|
||||||
if name == "echo" {
|
if name == "echo" {
|
||||||
builtins::echo(arg_strings);
|
builtins::echo(arg_strings);
|
||||||
@@ -50,7 +44,6 @@ impl<'a> Executor<'a> {
|
|||||||
} else if name == "require_root" {
|
} else if name == "require_root" {
|
||||||
builtins::require_root();
|
builtins::require_root();
|
||||||
} else {
|
} else {
|
||||||
// execute external cmd
|
|
||||||
let cmd = Command {
|
let cmd = Command {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
args: arg_strings,
|
args: arg_strings,
|
||||||
@@ -59,34 +52,32 @@ impl<'a> Executor<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::ControlFlow {
|
AstNode::ControlFlow {
|
||||||
condition: _,
|
condition,
|
||||||
then_branch,
|
then_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
} => {
|
} => {
|
||||||
println!("TODO: Execute if statement");
|
let condition_result = self.evaluate_condition(&condition);
|
||||||
|
|
||||||
|
if condition_result {
|
||||||
for node in then_branch {
|
for node in then_branch {
|
||||||
self.execute_node(node);
|
self.execute_node(node);
|
||||||
}
|
}
|
||||||
if let Some(else_nodes) = else_branch {
|
} else if let Some(else_nodes) = else_branch {
|
||||||
for node in else_nodes {
|
for node in else_nodes {
|
||||||
self.execute_node(node);
|
self.execute_node(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::For { var, items, body } => {
|
AstNode::For { var, items, body } => {
|
||||||
// execute for loop by iterating over items
|
|
||||||
for item in items {
|
for item in items {
|
||||||
// set loop variable
|
|
||||||
self.env.set_variable(var.clone(), item);
|
self.env.set_variable(var.clone(), item);
|
||||||
|
|
||||||
// Execute body
|
|
||||||
for node in &body {
|
for node in &body {
|
||||||
self.execute_node(node.clone());
|
self.execute_node(node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Parallel { blocks } => {
|
AstNode::Parallel { blocks } => {
|
||||||
// create multithreaded runtime
|
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(4) //todo: make configurable or dynamic
|
.worker_threads(4) //todo: make configurable or dynamic
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -96,24 +87,16 @@ impl<'a> Executor<'a> {
|
|||||||
runtime.block_on(async {
|
runtime.block_on(async {
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
// clone the environment for this block to access variables
|
|
||||||
let env = self.env.clone();
|
let env = self.env.clone();
|
||||||
|
|
||||||
// Spawn each block as a separate task (can run on different threads)
|
|
||||||
let handle = tokio::task::spawn_blocking(move || {
|
let handle = tokio::task::spawn_blocking(move || {
|
||||||
for node in block {
|
for node in block {
|
||||||
// Execute node
|
|
||||||
if let AstNode::Command { name, args } = node {
|
if let AstNode::Command { name, args } = node {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if its a variable reference
|
Some(env.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
env.get_variable(&s[1..]).map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -123,7 +106,6 @@ impl<'a> Executor<'a> {
|
|||||||
if name == "echo" {
|
if name == "echo" {
|
||||||
println!("{}", arg_strings.join(" "));
|
println!("{}", arg_strings.join(" "));
|
||||||
} else {
|
} else {
|
||||||
// execute external cmd
|
|
||||||
match std::process::Command::new(&name)
|
match std::process::Command::new(&name)
|
||||||
.args(&arg_strings)
|
.args(&arg_strings)
|
||||||
.output()
|
.output()
|
||||||
@@ -153,16 +135,14 @@ impl<'a> Executor<'a> {
|
|||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for all
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let _ = handle.await;
|
let _ = handle.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AstNode::Workers { count, body } => {
|
AstNode::Workers { count, body } => {
|
||||||
// execute with limited concurrency using a semaphore and multi-threaded runtime
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(count.max(2)) // use at least 2 threads, or the worker count
|
.worker_threads(count.max(2))
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -171,7 +151,6 @@ impl<'a> Executor<'a> {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
// clone environment for workers
|
|
||||||
let env = self.env.clone();
|
let env = self.env.clone();
|
||||||
|
|
||||||
let semaphore = Arc::new(Semaphore::new(count));
|
let semaphore = Arc::new(Semaphore::new(count));
|
||||||
@@ -181,31 +160,19 @@ impl<'a> Executor<'a> {
|
|||||||
let sem = semaphore.clone();
|
let sem = semaphore.clone();
|
||||||
let env_clone = env.clone();
|
let env_clone = env.clone();
|
||||||
|
|
||||||
// clone the node for the async task
|
|
||||||
let node_clone = node.clone();
|
let node_clone = node.clone();
|
||||||
|
|
||||||
// Use spawn_blocking for CPU bound work to run on thread pool
|
|
||||||
let handle = tokio::task::spawn_blocking(move || {
|
let handle = tokio::task::spawn_blocking(move || {
|
||||||
// Acquire semaphore permit (limits concurrency)
|
|
||||||
// Note: We need to do this in a blocking context
|
|
||||||
let rt_inner = tokio::runtime::Handle::current();
|
let rt_inner = tokio::runtime::Handle::current();
|
||||||
let _permit = rt_inner.block_on(async { sem.acquire().await.unwrap() });
|
let _permit = rt_inner.block_on(async { sem.acquire().await.unwrap() });
|
||||||
|
|
||||||
// execute the node based on its type
|
|
||||||
match node_clone {
|
match node_clone {
|
||||||
AstNode::Command { name, args } => {
|
AstNode::Command { name, args } => {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if it's a variable reference
|
Some(env_clone.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
env_clone
|
|
||||||
.get_variable(&s[1..])
|
|
||||||
.map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -244,30 +211,20 @@ impl<'a> Executor<'a> {
|
|||||||
items,
|
items,
|
||||||
body: for_body,
|
body: for_body,
|
||||||
} => {
|
} => {
|
||||||
// execute for loop
|
|
||||||
for item in items {
|
for item in items {
|
||||||
for for_node in &for_body {
|
for for_node in &for_body {
|
||||||
// simple execution for commands in for loops
|
match for_node {
|
||||||
if let AstNode::Command { name, args } = for_node {
|
AstNode::Command { name, args } => {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
|
let mut temp_env = env_clone.clone();
|
||||||
if s.starts_with('$') {
|
temp_env.set_variable(
|
||||||
let var_name = &s[1..];
|
var.clone(),
|
||||||
if var_name == var {
|
item.clone(),
|
||||||
// loop var
|
);
|
||||||
Some(item.clone())
|
Some(temp_env.substitute_variables(s))
|
||||||
} else {
|
|
||||||
// Environment var
|
|
||||||
env_clone
|
|
||||||
.get_variable(var_name)
|
|
||||||
.map(|v| v.clone())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(s.clone())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -278,27 +235,120 @@ impl<'a> Executor<'a> {
|
|||||||
println!("{}", arg_strings.join(" "));
|
println!("{}", arg_strings.join(" "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AstNode::Parallel { blocks } => {
|
||||||
|
for block in blocks {
|
||||||
|
for cmd_node in block {
|
||||||
|
if let AstNode::Command { name, args } = cmd_node {
|
||||||
|
let arg_strings: Vec<String> = args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| {
|
||||||
|
if let AstNode::Literal(s) = arg {
|
||||||
|
let mut temp_env = env_clone.clone();
|
||||||
|
temp_env.set_variable(
|
||||||
|
var.clone(),
|
||||||
|
item.clone(),
|
||||||
|
);
|
||||||
|
Some(temp_env.substitute_variables(s))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if name == "echo" {
|
||||||
|
println!("{}", arg_strings.join(" "));
|
||||||
|
} else {
|
||||||
|
match std::process::Command::new(name)
|
||||||
|
.args(&arg_strings)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
eprint!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to execute '{}': {}", name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AstNode::Parallel { blocks } => {
|
||||||
|
for block in blocks {
|
||||||
|
for cmd_node in block {
|
||||||
|
if let AstNode::Command { name, args } = cmd_node {
|
||||||
|
let arg_strings: Vec<String> = args
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|arg| {
|
||||||
|
if let AstNode::Literal(s) = arg {
|
||||||
|
Some(env_clone.substitute_variables(&s))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if name == "echo" {
|
||||||
|
println!("{}", arg_strings.join(" "));
|
||||||
|
} else {
|
||||||
|
match std::process::Command::new(&name)
|
||||||
|
.args(&arg_strings)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
eprint!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to execute '{}': {}", name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
//todo: other node types not yet supported in workers
|
//todo: other node types not yet supported in workers
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for all tasks to complete
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let _ = handle.await;
|
let _ = handle.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AstNode::Literal(_) => {
|
AstNode::Literal(_) => {}
|
||||||
// literals dont execute on their own
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,9 +363,7 @@ impl<'a> Executor<'a> {
|
|||||||
var,
|
var,
|
||||||
items.len()
|
items.len()
|
||||||
);
|
);
|
||||||
for _stmt in body {
|
for _stmt in body {}
|
||||||
// execute each statement in the loop body
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Statement::If {
|
Statement::If {
|
||||||
condition,
|
condition,
|
||||||
@@ -324,13 +372,9 @@ impl<'a> Executor<'a> {
|
|||||||
} => {
|
} => {
|
||||||
// todo: Implement if statement execution
|
// todo: Implement if statement execution
|
||||||
println!("todo: Execute if with condition '{}'", condition);
|
println!("todo: Execute if with condition '{}'", condition);
|
||||||
for _stmt in then_branch {
|
for _stmt in then_branch {}
|
||||||
// Execute then branch
|
|
||||||
}
|
|
||||||
if let Some(else_stmts) = else_branch {
|
if let Some(else_stmts) = else_branch {
|
||||||
for _stmt in else_stmts {
|
for _stmt in else_stmts {}
|
||||||
// Execute else branch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Parallel { blocks } => {
|
Statement::Parallel { blocks } => {
|
||||||
@@ -364,4 +408,49 @@ impl<'a> Executor<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evaluate_condition(&self, condition: &AstNode) -> bool {
|
||||||
|
match condition {
|
||||||
|
AstNode::Literal(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
|
||||||
|
let (negated, condition_str) = if s.starts_with("not ") {
|
||||||
|
(true, s[4..].trim())
|
||||||
|
} else {
|
||||||
|
(false, s)
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if condition_str.starts_with('$') {
|
||||||
|
let var_name = &condition_str[1..];
|
||||||
|
if let Some(value) = self.env.get_variable(var_name) {
|
||||||
|
self.is_truthy(value)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.is_truthy(condition_str)
|
||||||
|
};
|
||||||
|
|
||||||
|
if negated {
|
||||||
|
!result
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_truthy(&self, value: &str) -> bool {
|
||||||
|
// Empty string, "0", "false", "no" are falsy
|
||||||
|
// Everything else is truthy
|
||||||
|
!value.is_empty()
|
||||||
|
&& value != "0"
|
||||||
|
&& value.to_lowercase() != "false"
|
||||||
|
&& value.to_lowercase() != "no"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substitute_variables(&self, s: &str) -> String {
|
||||||
|
self.env.substitute_variables(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ use crate::parser::Parser;
|
|||||||
use crate::runtime::environment::Environment;
|
use crate::runtime::environment::Environment;
|
||||||
use executor::Executor;
|
use executor::Executor;
|
||||||
|
|
||||||
/// Main entry point for executing rush scripts
|
|
||||||
pub fn execute(content: &str) {
|
pub fn execute(content: &str) {
|
||||||
// create a new parser
|
|
||||||
let parser = Parser::new();
|
let parser = Parser::new();
|
||||||
|
|
||||||
// parse the script content
|
|
||||||
let program = match parser.parse(content) {
|
let program = match parser.parse(content) {
|
||||||
Ok(prog) => prog,
|
Ok(prog) => prog,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -19,13 +16,10 @@ pub fn execute(content: &str) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// create a new environment
|
|
||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
|
|
||||||
// create an executor
|
|
||||||
let mut executor = Executor::new(&mut env);
|
let mut executor = Executor::new(&mut env);
|
||||||
|
|
||||||
// execute each statement in the program
|
|
||||||
for statement in program.statements {
|
for statement in program.statements {
|
||||||
executor.execute_node(statement);
|
executor.execute_node(statement);
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -8,7 +8,16 @@ fn main() {
|
|||||||
|
|
||||||
let script_path = &args[1];
|
let script_path = &args[1];
|
||||||
|
|
||||||
let content = std::fs::read_to_string(script_path).expect("Failed to read the script file");
|
let content = std::fs::read_to_string(script_path);
|
||||||
|
if content.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Error reading file {}: {}",
|
||||||
|
script_path,
|
||||||
|
content.err().unwrap()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let content = content.unwrap();
|
||||||
|
|
||||||
let stripped_content = if content.starts_with("#!") {
|
let stripped_content = if content.starts_with("#!") {
|
||||||
content.lines().skip(1).collect::<Vec<&str>>().join("\n")
|
content.lines().skip(1).collect::<Vec<&str>>().join("\n")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user