smop

Batteries-included scripting utilities for Rust. Write Rust scripts like Python, but with a compiler that won't let them rot.
use smop::prelude::*;
fn main() -> Result<()> {
let users: Vec<User> = fs::read_csv("users.csv")?;
for user in &users {
let data: ApiResponse = http::get_json(&format!("https://api.example.com/{}", user.id))?;
println!("{}: {}", user.name, data.status);
}
success!("Processed {} users", users.len());
Ok(())
}
Features
- Zero ceremony -
use smop::prelude::* and go
- Error handling - anyhow's
Result<T> everywhere, with context
- File I/O - Read/write strings, JSON, CSV, lines
- HTTP - Sync GET/POST with JSON support (no async runtime needed)
- Shell - Cross-platform command execution (Windows + Unix)
- Environment - Typed env vars, dotenv loading
- Terminal UI - Spinners, progress bars, colored output, prompts
Installation
[dependencies]
smop ="0.1"
Or with specific features:
[dependencies]
smop ={ version = "0.1", default-features = false, features = ["http", "csv"] }
Quick Examples
Environment & Config
use smop::prelude::*;
fn main() -> Result<()> {
env::dotenv()?;
let port: u16 = env::var("PORT")?;
let timeout: u32 = env::var_or("TIMEOUT", 30);
env::require_vars(&["API_KEY", "DATABASE_URL"])?;
Ok(())
}
File Operations
use smop::prelude::*;
fn main() -> Result<()> {
let content = fs::read_string("config.txt")?;
fs::write_string("output.txt", "Hello, world!")?;
let config: Config = fs::read_json("config.json")?;
fs::write_json("output.json", &data)?;
let records: Vec<Record> = fs::read_csv("data.csv")?;
fs::write_csv("output.csv", &records)?;
let lines = fs::read_lines("data.txt")?;
fs::append("log.txt", "New entry\n")?;
Ok(())
}
HTTP Requests
use smop::prelude::*;
fn main() -> Result<()> {
let html = http::get("https://example.com")?;
let user: User = http::get_json("https://api.example.com/user/1")?;
let created: User = http::post_json("https://api.example.com/users", &new_user)?;
Ok(())
}
Shell Commands
use smop::prelude::*;
fn main() -> Result<()> {
sh::run("git status")?;
let branch = sh::output("git rev-parse --abbrev-ref HEAD")?;
sh::cmd("cargo")
.args(["build", "--release"])
.dir("./my-project")
.env("RUSTFLAGS", "-C target-cpu=native")
.run()?;
Ok(())
}
Terminal UI
use smop::prelude::*;
fn main() -> Result<()> {
success!("Build complete");
warn!("Deprecated API");
error!("Connection failed");
let spinner = print::spinner("Downloading...");
spinner.finish();
let bar = print::progress(100);
for _ in 0..100 {
bar.inc(1);
}
bar.finish();
let name = print::prompt("What's your name?")?;
let port = print::prompt_default("Port", "8080")?;
if print::confirm("Continue?")? {
}
Ok(())
}
Path Utilities
use smop::prelude::*;
fn main() -> Result<()> {
let home = path::home();
let cwd = path::cwd()?;
let expanded = path::expand("~/Documents/$PROJECT");
Ok(())
}
Feature Flags
| Feature |
Default |
Description |
full |
Yes |
Everything below |
http |
Yes |
HTTP client (ureq) |
cli |
Yes |
CLI parsing (clap derives) |
print |
Yes |
Terminal UI (spinners, progress, prompts) |
csv |
Yes |
CSV read/write |
Minimal build (just core utilities):
smop ={ version = "0.1", default-features = false }
Cargo Script (Future)
With RFC 3424, you'll be able to write single-file scripts:
#!/usr/bin/env cargo
---
[dependencies]
smop ="0.1"
---
use smop::prelude::*;
fn main() -> Result<()> {
let data: Vec<Record> = fs::read_csv("input.csv")?;
success!("Loaded {} records", data.len());
Ok(())
}
Why scriptkit?
|
Python |
Bash |
Rust + scriptkit |
| Type safety |
Runtime errors |
What errors? |
Compile-time |
| Dependencies |
pip chaos |
Pray it's installed |
Cargo.lock |
| IDE support |
Variable |
None |
rust-analyzer |
| Performance |
Slow |
Fast-ish |
Fast |
| Refactoring |
Scary |
Terrifying |
Confident |
Scripts rot. Python scripts fail silently when APIs change. Bash scripts break on edge cases. Rust scripts fail to compile when something's wrong - and that's a feature.
License
MIT OR Apache-2.0