#![forbid(unsafe_code, missing_docs)]
#![allow(clippy::manual_strip)]
pub mod Template_syntax;
mod expression;
mod parseresult;
mod spacelike;
mod staticfiles;
mod template;
mod templateexpression;
use parseresult::show_errors;
use std::env;
use std::error::Error;
use std::fmt::{self, Debug, Display};
use std::fs::{create_dir_all, read_dir, File};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use template::template;
pub use staticfiles::StaticFiles;
pub struct Ructe {
f: Vec<u8>,
outdir: PathBuf,
}
impl Ructe {
pub fn from_env() -> Result<Ructe> {
Ructe::new(PathBuf::from(get_env("OUT_DIR")?))
}
pub fn new(outdir: PathBuf) -> Result<Ructe> {
let mut f = Vec::with_capacity(512);
let outdir = outdir.join("templates");
create_dir_all(&outdir)?;
f.write_all(b"pub mod templates {\n")?;
write_if_changed(
&outdir.join("_utils.rs"),
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/templates/utils.rs"
)),
)?;
f.write_all(
b"#[doc(hidden)]\nmod _utils;\n\
#[doc(inline)]\npub use self::_utils::*;\n\n",
)?;
if cfg!(feature = "warp03") {
write_if_changed(
&outdir.join("_utils_warp03.rs"),
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/templates/utils_warp03.rs"
)),
)?;
f.write_all(
b"#[doc(hidden)]\nmod _utils_warp03;\n\
#[doc(inline)]\npub use self::_utils_warp03::*;\n\n",
)?;
}
Ok(Ructe { f, outdir })
}
pub fn compile_templates<P>(&mut self, indir: P) -> Result<()>
where
P: AsRef<Path>,
{
handle_entries(&mut self.f, indir.as_ref(), &self.outdir)
}
pub fn statics(&mut self) -> Result<StaticFiles> {
self.f.write_all(b"pub mod statics;")?;
StaticFiles::for_template_dir(
&self.outdir,
&PathBuf::from(get_env("CARGO_MANIFEST_DIR")?),
)
}
}
impl Drop for Ructe {
fn drop(&mut self) {
let _ = self.f.write_all(b"}\n");
let _ =
write_if_changed(&self.outdir.join("../templates.rs"), &self.f);
}
}
fn write_if_changed(path: &Path, content: &[u8]) -> io::Result<()> {
use std::fs::{read, write};
if let Ok(old) = read(path) {
if old == content {
return Ok(());
}
}
write(path, content)
}
fn handle_entries(
f: &mut impl Write,
indir: &Path,
outdir: &Path,
) -> Result<()> {
println!("cargo:rerun-if-changed={}", indir.display());
for entry in read_dir(indir)? {
let entry = entry?;
let path = entry.path();
if entry.file_type()?.is_dir() {
if let Some(filename) = entry.file_name().to_str() {
let outdir = outdir.join(filename);
create_dir_all(&outdir)?;
let mut modrs = Vec::with_capacity(512);
modrs.write_all(
b"#[allow(renamed_and_removed_lints)]\n\
#[cfg_attr(feature=\"cargo-clippy\", \
allow(useless_attribute))]\n\
#[allow(unused)]\n\
use super::{Html,ToHtml};\n",
)?;
handle_entries(&mut modrs, &path, &outdir)?;
write_if_changed(&outdir.join("mod.rs"), &modrs)?;
writeln!(f, "pub mod {filename};\n")?;
}
} else if let Some(filename) = entry.file_name().to_str() {
for suffix in &[".rs.html", ".rs.svg", ".rs.xml"] {
if filename.ends_with(suffix) {
println!("cargo:rerun-if-changed={}", path.display());
let prename = &filename[..filename.len() - suffix.len()];
let name =
format!("{prename}_{}", &suffix[".rs.".len()..]);
if handle_template(&name, &path, outdir)? {
writeln!(
f,
"#[doc(hidden)]\n\
mod template_{name};\n\
#[doc(inline)]\n\
pub use self::template_{name}::{name};\n",
)?;
}
}
}
}
}
Ok(())
}
fn handle_template(
name: &str,
path: &Path,
outdir: &Path,
) -> io::Result<bool> {
let mut input = File::open(path)?;
let mut buf = Vec::new();
input.read_to_end(&mut buf)?;
match template(&buf) {
Ok((_, t)) => {
let mut data = Vec::new();
t.write_rust(&mut data, name)?;
write_if_changed(
&outdir.join(format!("template_{name}.rs")),
&data,
)?;
Ok(true)
}
Err(error) => {
println!("cargo:warning=Template parse error in {path:?}:");
show_errors(&mut io::stdout(), &buf, &error, "cargo:warning=");
Ok(false)
}
}
}
pub mod templates;
fn get_env(name: &str) -> Result<String> {
env::var(name).map_err(|e| RucteError::Env(name.into(), e))
}
pub enum RucteError {
Io(io::Error),
Env(String, env::VarError),
#[cfg(feature = "sass")]
Sass(rsass::Error),
}
impl Error for RucteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self {
RucteError::Io(e) => Some(e),
RucteError::Env(_, e) => Some(e),
#[cfg(feature = "sass")]
RucteError::Sass(e) => Some(e),
}
}
}
impl Display for RucteError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "Error: {self:?}")
}
}
impl Debug for RucteError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
match self {
RucteError::Io(err) => Display::fmt(err, out),
RucteError::Env(var, err) => write!(out, "{var:?}: {err}"),
#[cfg(feature = "sass")]
RucteError::Sass(err) => Debug::fmt(err, out),
}
}
}
impl From<io::Error> for RucteError {
fn from(e: io::Error) -> RucteError {
RucteError::Io(e)
}
}
#[cfg(feature = "sass")]
impl From<rsass::Error> for RucteError {
fn from(e: rsass::Error) -> RucteError {
RucteError::Sass(e)
}
}
pub type Result<T> = std::result::Result<T, RucteError>;