use regex::Regex;
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug)]
pub struct ParsedData {
rollup_name: String,
stf_function: String,
}
pub fn importparser(destination_file_path: &PathBuf) -> io::Result<()> {
let current_dir = env::current_dir()?;
let content = fs::read_to_string(&destination_file_path)?;
let re_usage =
Regex::new(r"\bethers(?:\.([a-zA-Z0-9_]+))?\.[a-zA-Z0-9_]+\b").unwrap();
let mut ethers_members = HashMap::new();
let new_content =
re_usage.replace_all(&content, |caps: ®ex::Captures| {
let member = caps[0].to_string();
let split: Vec<&str> = member.split('.').collect();
let (import_path, import_member) = match split.as_slice() {
[_, subpath, name] => {
(format!("ethers/{}", subpath), name.to_string())
}
[_, name] => ("ethers".to_string(), name.to_string()),
_ => unreachable!(),
};
ethers_members
.entry(import_member.clone())
.or_insert_with(|| import_path);
import_member
});
let mut import_lines = String::new();
for (member, path) in ðers_members {
import_lines
.push_str(&format!("import {{{}}} from \"{}\";\n", member, path));
}
if !import_lines.is_empty() {
import_lines.push('\n');
}
let final_content = format!("{}{}", import_lines, new_content);
let mut output_file = fs::File::create(&destination_file_path)?;
output_file.write_all(final_content.as_bytes())?;
Ok(())
}
pub fn configparser() -> io::Result<()> {
let current_dir = env::current_dir()?;
let destination_file_path = current_dir.join(format!("stackr.config.ts"));
let content = fs::read_to_string(&destination_file_path)?;
let json_value: Value = serde_json::from_str(&content)?;
let js_object_literal = format!(
"const stackrConfig: StackrConfig = {};",
json_to_js_object_literal(&json_value)
);
let mut output_file = fs::File::create(&destination_file_path)?;
output_file.write_all(js_object_literal.as_bytes())?;
Ok(())
}
pub fn remove_unwanted_lines(file_path: &PathBuf) -> io::Result<()> {
println!("Sanitizing files ...");
let data = fs::read_to_string(&file_path)?;
let console_log_regex = Regex::new(r"^\s*console\.log\(.*\);\s*$")
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let import_ethers_regex =
Regex::new(r#"import\s+\{\s*ethers\s*\}\s+from\s+['"]ethers['"];"#)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let cleaned_data = data
.lines()
.filter(|line| {
(!console_log_regex.is_match(line)
|| !import_ethers_regex.is_match(line))
})
.collect::<Vec<&str>>()
.join("\n");
let mut file = fs::File::create(&file_path)?;
file.write_all(cleaned_data.as_bytes())?;
println!("Sanitization complete");
Ok(())
}
pub fn append_js_functions_to_file<P: AsRef<Path>>(
file_path: P,
) -> std::io::Result<()> {
let js_code = r#"
// Execution step
// Read inputs from stdin
const stfInputs = readInput();
var newState = stfInputs.currentState;
//this sets all product descriptions to a max length of 10 characters
stfInputs.actions.forEach((action) => {
newState = counterSTF.apply(action, newState);
});
// Write the result to stdout
writeOutput(newState);
function readInput() {
const chunkSize = 1024;
const inputChunks = [];
let totalBytes = 0;
// Read all the available bytes
while (1) {
const buffer = new Uint8Array(chunkSize);
// Stdin file descriptor
const fd = 0;
const bytesRead = Javy.IO.readSync(fd, buffer);
totalBytes += bytesRead;
if (bytesRead === 0) {
break;
}
inputChunks.push(buffer.subarray(0, bytesRead));
}
// Assemble input into a single Uint8Array
const { finalBuffer } = inputChunks.reduce((context, chunk) => {
context.finalBuffer.set(chunk, context.bufferOffset);
context.bufferOffset += chunk.length;
return context;
}, { bufferOffset: 0, finalBuffer: new Uint8Array(totalBytes) });
return JSON.parse(new TextDecoder().decode(finalBuffer));
}
// Write output to stdout
function writeOutput(output) {
const encodedOutput = new TextEncoder().encode(JSON.stringify(output));
const buffer = new Uint8Array(encodedOutput);
// Stdout file descriptor
const fd = 1;
Javy.IO.writeSync(fd, buffer);
}
"#;
let current_dir = env::current_dir()?;
let _ = Command::new("mkdir")
.arg("build")
.current_dir(¤t_dir)
.output();
let build_file_path = current_dir.join("build/state.ts");
let _ = std::fs::copy(&file_path, &build_file_path);
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open(&build_file_path)?;
writeln!(file, "{}", js_code)?;
Ok(())
}
fn json_to_js_object_literal(value: &Value) -> String {
match value {
Value::Object(map) => map_to_js_object_literal(map),
_ => value.to_string(),
}
}
fn map_to_js_object_literal(map: &Map<String, Value>) -> String {
let mut object_literal = String::from("{");
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
object_literal.push_str(", ");
}
object_literal.push_str(&format!(
"{}: {}",
k,
json_to_js_object_literal(v)
));
}
object_literal.push('}');
object_literal
}
pub fn read_first_account<P: AsRef<Path>>(
file_path: P,
) -> Result<String, Box<dyn Error>> {
let data = fs::read_to_string(&file_path)?;
let re = Regex::new(r#"privateKey:\s*\"([^\"]+)\""#)?;
if let Some(caps) = re.captures(&data) {
if let Some(account) = caps.get(1) {
return Ok(account.as_str().to_string());
}
}
Err("No account found".into())
}
pub fn read_vulcan_rpc<P: AsRef<Path>>(
file_path: P,
) -> Result<String, Box<dyn Error>> {
let data = fs::read_to_string(&file_path)?;
let re = Regex::new(r#"vulcanRPC:\s*\"([^"]+)\""#)?;
if let Some(caps) = re.captures(&data) {
if let Some(account) = caps.get(1) {
return Ok(account.as_str().to_string());
}
}
Err("No vulcan RPC found".into())
}
pub fn read_l1_rpc<P: AsRef<Path>>(
file_path: P,
) -> Result<String, Box<dyn Error>> {
let data = fs::read_to_string(&file_path)?;
let re = Regex::new(r#"L1RPC:\s*\"([^"]+)\""#)?;
if let Some(caps) = re.captures(&data) {
if let Some(account) = caps.get(1) {
return Ok(account.as_str().to_string());
}
}
Err("No L1 RPC found".into())
}
pub fn generate_import_statement(
file_path: &PathBuf,
) -> Result<String, Box<dyn Error>> {
let content = fs::read_to_string(file_path)?;
let re =
Regex::new(r"export (?:type|class) (\w+)|export const (\w+)").unwrap();
let mut exports = vec![];
for caps in re.captures_iter(&content) {
if let Some(export_name) = caps.get(1).or_else(|| caps.get(2)) {
exports.push(export_name.as_str().to_string());
}
}
let import_statement = if !exports.is_empty() {
format!("import {{ {} }} from \"./state\";", exports.join(", "))
} else {
String::new()
};
Ok(import_statement)
}
pub fn create_js_function(
import_statement: &str,
) -> Result<String, Box<dyn Error>> {
let current_dir = env::current_dir()?;
let state_file_dir = current_dir.join("src/state.ts");
let result = parse_typescript_file(&state_file_dir)?;
let rollup_name = &result.rollup_name;
let stf_name = &result.stf_function;
let first_char = rollup_name
.chars()
.next()
.unwrap()
.to_lowercase()
.to_string();
let lowercase_rollup_name = first_char + &rollup_name[1..] + "Object";
Ok(format!(
r#"const run = (prevState: StateVariable, actions: any[]) => {{
const {} = new {}(prevState);
const fsm = new StateMachine({{
state: {},
stf: {},
}});
actions.forEach((action: any) => {{
fsm.apply(action);
}});
return fsm.state.getState();
}};"#,
lowercase_rollup_name, rollup_name, lowercase_rollup_name, stf_name
))
}
pub fn parse_typescript_file(
file_path: &PathBuf,
) -> Result<ParsedData, Box<dyn Error>> {
let content = fs::read_to_string(file_path)?;
let rollup_regex = Regex::new(r"export class (\w+) extends RollupState<")?;
let rollup_name = rollup_regex
.captures(&content)
.and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
.ok_or("RollupState class not found")?;
let stf_regex = Regex::new(r"export const (\w+): STF<")?;
let stf_function = stf_regex
.captures(&content)
.and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
.ok_or("STF function not found")?;
Ok(ParsedData {
rollup_name,
stf_function,
})
}