use serde::Serialize;
use std::env;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
pub const AUTO_GENERATED: &str = "// AUTO-GENERATED BY RPLC";
pub mod config;
use config::Config;
pub fn generate(config_file: &str) -> Result<(), Box<dyn Error>> {
Builder::new(config_file).generate()
}
pub struct Builder<'a> {
config_file: &'a str,
context: tera::Context,
}
impl<'a> Builder<'a> {
pub fn new(config_file: &'a str) -> Self {
Self {
config_file,
context: tera::Context::new(),
}
}
pub fn insert<T: Serialize + ?Sized, S: Into<String>>(&mut self, variable: S, value: &T) {
self.context.insert(variable, value);
}
pub fn generate(&self) -> Result<(), Box<dyn Error>> {
let config = Config::load(self.config_file, &self.context)?;
prepare(&config)?;
fs::create_dir_all("src/plc")?;
config.generate_io("src/plc/io.rs")?;
config.generate_context("src/plc/context.rs")?;
Ok(())
}
}
fn prepare(config: &Config) -> Result<(), Box<dyn Error>> {
fs::create_dir_all("src/plc")?;
let mut plc_mod = codegen::Scope::new();
plc_mod.raw(AUTO_GENERATED);
plc_mod.raw("use ::std::time::Duration;");
plc_mod.raw("pub mod context;");
plc_mod.raw("pub mod io;");
for c in &["VERSION", "NAME", "DESCRIPTION"] {
plc_mod.raw(format!(
"pub const {c}: &str = \"{}\";",
env::var(format!("CARGO_PKG_{c}"))?
));
}
plc_mod.raw(format!(
"pub const STACK_SIZE: Option<usize> = {};",
if let Some(ss) = config.core.stack_size {
format!("Some({})", ss * 1000)
} else {
"None".to_owned()
}
));
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
plc_mod.raw(format!(
"#[allow(clippy::unreadable_literal)] pub const STOP_TIMEOUT: Duration = Duration::from_nanos({});",
(config.core.stop_timeout.trunc() as u64) * 1_000_000_000
));
#[cfg(feature = "eva")]
plc_mod.raw(format!(
"pub const EAPI_ACTION_POOL_SIZE: usize = {};",
config.eapi.action_pool_size
));
#[cfg(not(feature = "eva"))]
plc_mod.raw("pub const EAPI_ACTION_POOL_SIZE: usize = 0;");
write("src/plc/mod.rs", plc_mod.to_string())?;
Ok(())
}
fn format_code(code: String) -> Result<String, Box<dyn Error>> {
let mut child = Command::new("rustfmt")
.arg("--edition=2021")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let mut stdin = child
.stdin
.take()
.ok_or_else(|| eva_common::Error::io("unable to take stdin"))?;
std::thread::spawn(move || {
stdin.write_all(code.as_bytes()).unwrap();
stdin.flush().unwrap();
});
let output = child.wait_with_output()?;
if !output.status.success() {
return Err(eva_common::Error::failed(format!(
"process exit code: {:?}",
output.status.code()
))
.into());
}
let formatted_code = std::str::from_utf8(&output.stdout)?;
Ok(formatted_code.to_owned())
}
fn write<T, D>(target: T, data: D) -> Result<(), Box<dyn Error>>
where
T: AsRef<Path>,
D: fmt::Display,
{
let mut code = data.to_string();
match format_code(code.clone()) {
Ok(c) => code = c,
Err(e) => println!("cargo:warning=unable to format code with rustfmt: {}", e),
}
if let Ok(x) = fs::read_to_string(&target) {
if code == x {
return Ok(());
}
}
fs::write(target, code)?;
Ok(())
}