use argh::FromArgs;
use rhai::packages::Package;
use rhai::{Engine, EvalAltResult, Position};
use rhai_rand::RandomPackage;
use rhai_rusp::RuspPackage;
use std::convert::Into;
use std::path::PathBuf;
use std::{fs::File, io::Read, path::Path, process::exit};
fn eprint_error(input: &str, mut err: EvalAltResult) {
fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) {
let line = pos.line().unwrap();
let line_no = format!("{line}: ");
eprintln!("{line_no}{}", lines[line - 1]);
for (i, err_line) in err_msg.to_string().lines().enumerate() {
println!(
"{0:>1$}{err_line}",
if i > 0 { "| " } else { "^ " },
line_no.len() + pos.position().unwrap() + 1,
);
}
eprintln!();
}
let lines: Vec<_> = input.lines().collect();
let pos = err.take_position();
if pos.is_none() {
eprintln!("{err}");
} else {
eprint_line(&lines, pos, &err.to_string());
}
}
#[derive(FromArgs)]
struct Rusp {
#[argh(option, long = "script", short = 's')]
script: Option<String>,
#[argh(switch, long = "comment", short = 'c')]
comment: bool,
#[argh(positional)]
filename: Option<PathBuf>,
}
fn main() {
let args: Rusp = argh::from_env();
let mut engine = Engine::new();
engine.register_static_module("rusp", RuspPackage::new().as_shared_module());
engine.register_static_module("rand", RandomPackage::new().as_shared_module());
engine.set_optimization_level(rhai::OptimizationLevel::Simple);
if let Some(filename) = args.filename {
if args.script.is_some() {
eprintln!("Inline scripting and the use of a file are mutual exclusive");
exit(1);
}
let mut contents = String::new();
let filename = match Path::new(&filename).canonicalize() {
Err(err) => {
eprintln!("Error script file path: {filename:?}\n{err}");
exit(1);
}
Ok(f) => match f.strip_prefix(std::env::current_dir().unwrap().canonicalize().unwrap())
{
Ok(f) => f.into(),
_ => f,
},
};
let mut f = match File::open(&filename) {
Err(err) => {
eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
Ok(f) => f,
};
if let Err(err) = f.read_to_string(&mut contents) {
eprintln!(
"Error reading script file: {}\n{}",
filename.to_string_lossy(),
err
);
exit(1);
}
let contents = if contents.starts_with("#!") {
&contents[contents.find('\n').unwrap_or(0)..]
} else {
&contents[..]
};
if let Err(err) = engine
.compile(contents)
.map_err(Into::into)
.and_then(|mut ast| {
ast.set_source(filename.to_string_lossy().to_string());
engine.run_ast(&ast)
})
{
let filename = filename.to_string_lossy();
eprintln!("{:=<1$}", "", filename.len());
eprintln!("{filename}");
eprintln!("{:=<1$}", "", filename.len());
eprintln!();
eprint_error(contents, *err);
}
} else if let Some(contents) = args.script {
let filename = "<script>";
if let Err(err) = engine
.compile(&contents)
.map_err(Into::into)
.and_then(|mut ast| {
ast.set_source(filename);
engine.run_ast(&ast)
})
{
eprintln!("{:=<1$}", "", filename.len());
eprintln!("{filename}");
eprintln!("{:=<1$}", "", filename.len());
eprintln!();
eprint_error(&contents, *err);
}
} else if args.comment {
let filename = "<comment>";
let mut input = String::new();
std::io::stdin().read_to_string(&mut input).unwrap();
let pos = input.find("/**");
if let Some(pos) = pos {
let mut code = input.clone().split_off(pos + 3);
let pos = code.find("*/");
if let Some(pos) = pos {
_ = code.split_off(pos);
_ = input.split_off(input.find("*/").unwrap() + 2);
println!("{input}");
if let Err(err) = engine
.compile(&code)
.map_err(Into::into)
.and_then(|mut ast| {
ast.set_source(filename);
engine.run_ast(&ast)
})
{
eprintln!("{:=<1$}", "", filename.len());
eprintln!("{filename}");
eprintln!("{:=<1$}", "", filename.len());
eprintln!();
eprint_error(&code, *err);
}
} else {
eprintln!("Couldn't find */ in \"{input}\"");
}
} else {
eprintln!("Couldn't find /** in \"{input}\"");
}
} else {
eprintln!("You will either have to supply a filename, or you can use the --script option");
}
}