Basic functionality works.
TODO: * Allow calling scripts from other directories. * Don't use OS-dependent path processing.
This commit is contained in:
commit
a13cd20685
|
@ -0,0 +1,2 @@
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "rush"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Sage Vaillancourt <sagev9000@tutanota.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/rush
|
||||||
|
|
||||||
|
let x = %cat example.rsh%;
|
||||||
|
|
||||||
|
if x.contains("example") {
|
||||||
|
%echo It\\'s a script!%
|
||||||
|
}
|
||||||
|
|
||||||
|
if %echo some words%.contains("me wo") {
|
||||||
|
println!("But it's also Rust!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mem = %free -h%.lines().nth(1).unwrap()
|
||||||
|
.split_whitespace().nth(3).unwrap().to_owned();
|
||||||
|
|
||||||
|
// for i in 0..10000 {
|
||||||
|
// println!("x contains %% example? [{}]", x.contains("example"));
|
||||||
|
// }
|
||||||
|
|
||||||
|
println!("There are {} of memory free", &mem);
|
||||||
|
%echo hello%
|
|
@ -0,0 +1,181 @@
|
||||||
|
#![feature(option_result_contains)]
|
||||||
|
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::time::{Instant, SystemTime};
|
||||||
|
|
||||||
|
use std::iter;
|
||||||
|
use std::str::Chars;
|
||||||
|
|
||||||
|
use std::str;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
|
enum PrintType {
|
||||||
|
Out, Err, All
|
||||||
|
}
|
||||||
|
|
||||||
|
fn com(print: &PrintType, command_string: &str) -> String {
|
||||||
|
let command_output = Command::new("bash").arg("-c")
|
||||||
|
.arg(command_string)
|
||||||
|
.output().unwrap();
|
||||||
|
|
||||||
|
let to_string = |bytes| str::from_utf8(bytes).unwrap().to_string();
|
||||||
|
let out = to_string(&command_output.stdout);
|
||||||
|
let err = to_string(&command_output.stderr);
|
||||||
|
|
||||||
|
match print {
|
||||||
|
PrintType::Out => { print!("{}", out); out },
|
||||||
|
PrintType::Err => { print!("{}", err); err },
|
||||||
|
PrintType::All => {
|
||||||
|
print!("{}", out);
|
||||||
|
if err.trim().is_empty() {
|
||||||
|
print!("{}", err);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn earlier_than(earlier: SystemTime, later: SystemTime) -> bool {
|
||||||
|
earlier.duration_since(later).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn needs_compilation(script_name: &str) -> bool {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let script_attr = fs::metadata(&script_name);
|
||||||
|
let compiled_attr = fs::metadata(&output_filename(script_name));
|
||||||
|
if let (Ok(sa), Ok(ca)) = (script_attr, compiled_attr) {
|
||||||
|
let sm = sa.modified();
|
||||||
|
let cm = ca.modified();
|
||||||
|
if let (Ok(script_mod_time), Ok(compiled_mod_time)) = (sm, cm) {
|
||||||
|
if earlier_than(compiled_mod_time, script_mod_time) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn output_filename(script_name: &str) -> String {
|
||||||
|
format!(".{}.compiled", script_name.replace("./", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_compiled(script_name: &str) {
|
||||||
|
com(&PrintType::All, &output_filename(script_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bash_command(
|
||||||
|
iter: &mut iter::Peekable<Chars>,
|
||||||
|
code: &mut String,
|
||||||
|
is_expr: bool
|
||||||
|
) -> Result<()> {
|
||||||
|
code.push_str(&format!("com({}, \"", !is_expr));
|
||||||
|
loop {
|
||||||
|
if let Some(c) = iter.next() {
|
||||||
|
if c == '%' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
code.push(c);
|
||||||
|
} else {
|
||||||
|
bail!("Unexpected end-of-file while parsing bash command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.push_str("\")");
|
||||||
|
let peek = iter.peek();
|
||||||
|
if peek.is_none() || peek.contains(&&' ') || peek.contains(&&'\n') {
|
||||||
|
code.push(';');
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_code(script_name: &str) -> Result<String> {
|
||||||
|
let script = fs::read_to_string(&script_name)?;
|
||||||
|
|
||||||
|
let mut code = "
|
||||||
|
use std::str;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn com(print: bool, comcom: &str) -> String {
|
||||||
|
let s = str::from_utf8(&Command::new(\"bash\").arg(\"-c\")
|
||||||
|
.arg(comcom).output().unwrap().stdout).unwrap().to_string();
|
||||||
|
if print {
|
||||||
|
print!(\"{}\", s)
|
||||||
|
};
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
".to_string();
|
||||||
|
|
||||||
|
let mut is_expr = false;
|
||||||
|
|
||||||
|
let mut iter = script.chars().peekable();
|
||||||
|
if script.starts_with("#!/") {
|
||||||
|
while let Some(c) = iter.next() {
|
||||||
|
if c == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(c) = iter.next() {
|
||||||
|
if c == '%' {
|
||||||
|
// Double percents count as a single literal percent
|
||||||
|
if iter.peek().contains(&&'%') {
|
||||||
|
code.push('%');
|
||||||
|
iter.next();
|
||||||
|
} else {
|
||||||
|
parse_bash_command(&mut iter, &mut code, is_expr)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code.push(c);
|
||||||
|
if (c == 'i' && iter.peek().contains(&&'f'))
|
||||||
|
|| (c == '=' && !iter.peek().contains(&&'=')) {
|
||||||
|
is_expr = true;
|
||||||
|
} else if (c == '{' || c == ';') && is_expr {
|
||||||
|
is_expr = false;
|
||||||
|
} else if c == '\n' && iter.peek().is_some() {
|
||||||
|
code.push_str(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.push('}');
|
||||||
|
return Ok(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let now = Instant::now();
|
||||||
|
if let Some(script_name) = env::args().nth(1) {
|
||||||
|
if !needs_compilation(&script_name) {
|
||||||
|
run_compiled(&script_name);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let code = collect_code(&script_name)?;
|
||||||
|
let code_filename = format!("/tmp/{}-rush.rs",
|
||||||
|
script_name.replace("./", "").replace(".", "-"));
|
||||||
|
let mut file = File::create(&code_filename)?;
|
||||||
|
file.write_all(code.as_bytes())?;
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
println!("[Start Rust]");
|
||||||
|
println!("{}", code);
|
||||||
|
println!("[End Rust]");
|
||||||
|
}
|
||||||
|
|
||||||
|
let compile_command = format!(
|
||||||
|
"rustc {} -o {}",
|
||||||
|
code_filename,
|
||||||
|
output_filename(&script_name)
|
||||||
|
);
|
||||||
|
com(&PrintType::Err, &compile_command);
|
||||||
|
println!("[32m[Compiled in {}ms][0m\n", now.elapsed().as_millis());
|
||||||
|
run_compiled(&script_name);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue