From 75ff8c74fe5f0d07787f7d1164142ba3c8cd6fbc Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Thu, 6 Feb 2025 21:41:49 +0100 Subject: [PATCH] Get rid of itertools dependency. Also, write rust code to strings rather than byte vectors. Source code should be utf-8. --- CHANGELOG.md | 1 + Cargo.toml | 1 - src/lib.rs | 80 ++++++++++++++++--------------- src/staticfiles.rs | 51 ++++++++++---------- src/template.rs | 47 +++++++++++-------- src/templateexpression.rs | 99 ++++++++++++++++++++++----------------- 6 files changed, 151 insertions(+), 128 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d38ec6..5bd9e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ project adheres to ## Unreleased * Updated `nom` to 8.0.0, and added `nom-language`. +* Removed `itertools` depenendency (PR #147). * MSRV is now 1.65.0, as required by `nom` 8.0. diff --git a/Cargo.toml b/Cargo.toml index 0751627..4318595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ tide013 = ["http-types"] [dependencies] base64 = "0.22.1" bytecount = "0.6.0" -itertools = "0.14.0" md5 = "0.7" nom = "8.0.0" nom-language = "0.1.0" diff --git a/src/lib.rs b/src/lib.rs index dc9892b..18d3a5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,9 +154,9 @@ mod templateexpression; use parseresult::show_errors; use std::env; use std::error::Error; -use std::fmt::{self, Debug, Display}; +use std::fmt::{self, Debug, Display, Write as _}; use std::fs::{create_dir_all, read_dir, File}; -use std::io::{self, Read, Write}; +use std::io::{self, Read}; use std::path::{Path, PathBuf}; use template::template; @@ -211,7 +211,7 @@ pub use staticfiles::StaticFiles; /// /// [cargo]: https://doc.rust-lang.org/cargo/ pub struct Ructe { - f: Vec, + f: String, outdir: PathBuf, } @@ -241,32 +241,32 @@ impl Ructe { /// /// [cargo]: https://doc.rust-lang.org/cargo/ pub fn new(outdir: PathBuf) -> Result { - let mut f = Vec::with_capacity(512); + let mut f = String::with_capacity(512); let outdir = outdir.join("templates"); create_dir_all(&outdir)?; - f.write_all(b"pub mod templates {\n")?; + f.write_str("pub mod templates {\n")?; write_if_changed( &outdir.join("_utils.rs"), - include_bytes!(concat!( + include_str!(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", + f.write_str( + "#[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!( + include_str!(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", + f.write_str( + "#[doc(hidden)]\nmod _utils_warp03;\n\ + #[doc(inline)]\npub use self::_utils_warp03::*;\n\n", )?; } Ok(Ructe { f, outdir }) @@ -320,7 +320,7 @@ impl Ructe { /// /// [`StaticFile`]: templates::StaticFile pub fn statics(&mut self) -> Result { - self.f.write_all(b"pub mod statics;")?; + self.f.write_str("pub mod statics;")?; StaticFiles::for_template_dir( &self.outdir, &PathBuf::from(get_env("CARGO_MANIFEST_DIR")?), @@ -330,27 +330,24 @@ impl Ructe { impl Drop for Ructe { fn drop(&mut self) { - let _ = self.f.write_all(b"}\n"); + let _ = self.f.write_str("}\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) { +fn write_if_changed(path: &Path, content: &str) -> Result<()> { + use std::fs::{read_to_string, write}; + if let Ok(old) = read_to_string(path) { if old == content { return Ok(()); } } - write(path, content) + write(path, content.as_bytes())?; + Ok(()) } -fn handle_entries( - f: &mut impl Write, - indir: &Path, - outdir: &Path, -) -> Result<()> { +fn handle_entries(f: &mut String, indir: &Path, outdir: &Path) -> Result<()> { println!("cargo:rerun-if-changed={}", indir.display()); for entry in read_dir(indir)? { let entry = entry?; @@ -359,11 +356,11 @@ fn handle_entries( 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(clippy::useless_attribute, unused)]\n\ - use super::{Html,ToHtml};\n", - )?; + let mut modrs = String::with_capacity(512); + modrs.push_str( + "#[allow(clippy::useless_attribute, 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")?; @@ -391,17 +388,13 @@ fn handle_entries( Ok(()) } -fn handle_template( - name: &str, - path: &Path, - outdir: &Path, -) -> io::Result { +fn handle_template(name: &str, path: &Path, outdir: &Path) -> Result { 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(); + let mut data = String::new(); t.write_rust(&mut data, name)?; write_if_changed( &outdir.join(format!("template_{name}.rs")), @@ -429,16 +422,19 @@ pub enum RucteError { Io(io::Error), /// Error resolving a given environment variable. Env(String, env::VarError), - /// Error bundling a sass stylesheet as css. + /// A build-time formatting error in Ructe + Fmt(fmt::Error), #[cfg(feature = "sass")] + /// Error bundling a sass stylesheet as css. 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), + Self::Io(e) => Some(e), + Self::Env(_, e) => Some(e), + Self::Fmt(e) => Some(e), #[cfg(feature = "sass")] RucteError::Sass(e) => Some(e), } @@ -455,6 +451,7 @@ impl Debug for RucteError { match self { RucteError::Io(err) => Display::fmt(err, out), RucteError::Env(var, err) => write!(out, "{var:?}: {err}"), + Self::Fmt(err) => Display::fmt(err, out), #[cfg(feature = "sass")] RucteError::Sass(err) => Debug::fmt(err, out), } @@ -466,6 +463,11 @@ impl From for RucteError { RucteError::Io(e) } } +impl From for RucteError { + fn from(value: fmt::Error) -> Self { + Self::Fmt(value) + } +} #[cfg(feature = "sass")] impl From for RucteError { @@ -475,4 +477,4 @@ impl From for RucteError { } /// A result where the error type is a [`RucteError`]. -pub type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 18eded9..9d0588f 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -1,10 +1,9 @@ use super::Result; -use itertools::Itertools; use std::ascii::escape_default; use std::collections::BTreeMap; -use std::fmt::{self, Display}; +use std::fmt::{self, Display, Write}; use std::fs::{read_dir, File}; -use std::io::{Read, Write}; +use std::io::Read; use std::path::{Path, PathBuf}; /// Handler for static files. @@ -147,7 +146,7 @@ use std::path::{Path, PathBuf}; /// ``` pub struct StaticFiles { /// Rust source file `statics.rs` beeing written. - src: Vec, + src: String, /// Path for writing the file `statics.rs`. src_path: PathBuf, /// Base path for finding static files with relative paths @@ -163,17 +162,17 @@ impl StaticFiles { outdir: &Path, base_path: &Path, ) -> Result { - let mut src = Vec::with_capacity(512); + let mut src = String::with_capacity(512); if cfg!(feature = "mime03") { - src.write_all(b"use mime::Mime;\n\n")?; + src.write_str("use mime::Mime;\n\n")?; } if cfg!(feature = "tide013") { - src.write_all(b"use tide::http::mime::{self, Mime};\n\n")?; + src.write_str("use tide::http::mime::{self, Mime};\n\n")?; } else if cfg!(feature = "http-types") { - src.write_all(b"use http_types::mime::{self, Mime};\n\n")?; + src.write_str("use http_types::mime::{self, Mime};\n\n")?; } - src.write_all( -b"/// A static file has a name (so its url can be recognized) and the + src.write_str( +"/// A static file has a name (so its url can be recognized) and the /// actual file contents. /// /// The name includes a short (48 bits as 8 base64 characters) hash of @@ -185,13 +184,13 @@ pub struct StaticFile { pub name: &'static str, ")?; if cfg!(feature = "mime03") { - src.write_all(b" pub mime: &'static Mime,\n")?; + src.write_str(" pub mime: &'static Mime,\n")?; } if cfg!(feature = "http-types") { - src.write_all(b" pub mime: &'static Mime,\n")?; + src.write_str(" pub mime: &'static Mime,\n")?; } - src.write_all( - b"} + src.write_str( + "} #[allow(dead_code)] impl StaticFile { /// Get a single `StaticFile` by name, if it exists. @@ -520,17 +519,21 @@ impl Drop for StaticFiles { /// Write the ending of the statics source code, declaring the /// `STATICS` variable. fn drop(&mut self) { + fn do_write(s: &mut StaticFiles) -> Result<()> { + write!(s.src, "\npub static STATICS: &[&StaticFile] = &[")?; + let mut q = s.names_r.values(); + if let Some(a) = q.next() { + write!(s.src, "&{a}")?; + } + for a in q { + write!(s.src, ", &{a}")?; + } + writeln!(s.src, "];")?; + super::write_if_changed(&s.src_path, &s.src)?; + Ok(()) + } // Ignore a possible write failure, rather than a panic in drop. - let _ = writeln!( - self.src, - "\npub static STATICS: &[&StaticFile] \ - = &[{}];", - self.names_r - .iter() - .map(|s| format!("&{}", s.1)) - .format(", "), - ); - let _ = super::write_if_changed(&self.src_path, &self.src); + let _ = do_write(self); } } diff --git a/src/template.rs b/src/template.rs index 3063a86..6945d70 100644 --- a/src/template.rs +++ b/src/template.rs @@ -2,7 +2,6 @@ use crate::expression::{input_to_str, rust_name}; use crate::parseresult::PResult; use crate::spacelike::spacelike; use crate::templateexpression::{template_expression, TemplateExpression}; -use itertools::Itertools; use nom::branch::alt; use nom::bytes::complete::is_not; use nom::bytes::complete::tag; @@ -12,7 +11,7 @@ use nom::error::context; use nom::multi::{many0, many_till, separated_list0, separated_list1}; use nom::sequence::{delimited, preceded, terminated}; use nom::Parser as _; -use std::io::{self, Write}; +use std::fmt::Write; #[derive(Debug, PartialEq, Eq)] pub struct Template { @@ -27,9 +26,9 @@ impl Template { &self, out: &mut impl Write, name: &str, - ) -> io::Result<()> { - out.write_all( - b"use std::io::{self, Write};\n\ + ) -> std::fmt::Result { + out.write_str( + "use std::io::{self, Write};\n\ #[allow(clippy::useless_attribute, unused)]\n\ use super::{Html,ToHtml};\n", )?; @@ -40,24 +39,32 @@ impl Template { out, "\n\ #[allow(clippy::used_underscore_binding)]\n\ - pub fn {name}<{ta}{ta_sep}W>(#[allow(unused_mut)] mut _ructe_out_: W{args}) -> io::Result<()>\n\ - where W: Write {{\n\ - {body}\ - Ok(())\n\ - }}", + pub fn {name}<{ta}{ta_sep}W>(\ + \n #[allow(unused_mut)] mut _ructe_out_: W,", name = name, ta = self.type_args, ta_sep = if self.type_args.is_empty() { "" } else { ", " }, - args = - self.args.iter().format_with("", |arg, f| f(&format_args!( - ", {}", - arg.replace( - " Content", - " impl FnOnce(&mut W) -> io::Result<()>" - ) - ))), - body = self.body.iter().map(|b| b.code()).format(""), - ) + )?; + for arg in &self.args { + writeln!( + out, + " {},", + arg.replace( + " Content", + " impl FnOnce(&mut W) -> io::Result<()>" + ) + )?; + } + writeln!( + out, + ") -> io::Result<()>\n\ + where W: Write {{", + )?; + for b in &self.body { + b.write_code(out)?; + } + writeln!(out, "Ok(())\n}}")?; + Ok(()) } } diff --git a/src/templateexpression.rs b/src/templateexpression.rs index a1a2465..6504db5 100644 --- a/src/templateexpression.rs +++ b/src/templateexpression.rs @@ -4,7 +4,6 @@ use crate::expression::{ }; use crate::parseresult::PResult; use crate::spacelike::{comment_tail, spacelike}; -use itertools::Itertools; use nom::branch::alt; use nom::bytes::complete::is_not; use nom::bytes::complete::tag; @@ -14,7 +13,7 @@ use nom::error::context; use nom::multi::{many0, many_till, separated_list0}; use nom::sequence::{delimited, pair, preceded, terminated}; use nom::Parser as _; -use std::fmt::{self, Display}; +use std::fmt::{self, Display, Write}; #[derive(Debug, PartialEq, Eq)] pub enum TemplateExpression { @@ -58,11 +57,13 @@ impl Display for TemplateArgument { TemplateArgument::Body(ref v) if v.is_empty() => { out.write_str("|_| Ok(())") } - TemplateArgument::Body(ref v) => writeln!( - out, - "#[allow(clippy::used_underscore_binding)] |mut _ructe_out_| {{\n{}\nOk(())\n}}", - v.iter().map(|b| b.code()).format(""), - ), + TemplateArgument::Body(ref v) => { + out.write_str("#[allow(clippy::used_underscore_binding)] |mut _ructe_out_| {\n")?; + for b in v { + b.write_code(out)?; + } + out.write_str("Ok(())\n}\n") + } } } } @@ -73,61 +74,71 @@ impl TemplateExpression { text: text.to_string(), } } - pub fn code(&self) -> String { + pub fn write_code(&self, out: &mut impl Write) -> fmt::Result { match *self { - TemplateExpression::Comment => String::new(), + TemplateExpression::Comment => Ok(()), TemplateExpression::Text { ref text } if text.is_ascii() => { - format!("_ructe_out_.write_all(b{text:?})?;\n") + writeln!(out, "_ructe_out_.write_all(b{text:?})?;") } TemplateExpression::Text { ref text } => { - format!("_ructe_out_.write_all({text:?}.as_bytes())?;\n") + writeln!(out, "_ructe_out_.write_all({text:?}.as_bytes())?;") } TemplateExpression::Expression { ref expr } => { - format!("{expr}.to_html(_ructe_out_.by_ref())?;\n") + writeln!(out, "{expr}.to_html(_ructe_out_.by_ref())?;") } TemplateExpression::ForLoop { ref name, ref expr, ref body, - } => format!( - "for {name} in {expr} {{\n{}}}\n", - body.iter().map(|b| b.code()).format(""), - ), + } => { + writeln!(out, "for {name} in {expr} {{")?; + for b in body { + b.write_code(out)?; + } + out.write_str("}\n") + } TemplateExpression::IfBlock { ref expr, ref body, ref else_body, - } => format!( - "if {expr} {{\n{}}}{}\n", - body.iter().map(|b| b.code()).format(""), + } => { + writeln!(out, "if {expr} {{")?; + for b in body { + b.write_code(out)?; + } + out.write_str("}")?; match else_body.as_deref() { - Some([e @ TemplateExpression::IfBlock { .. }]) => - format!(" else {}", e.code()), - - Some(body) => format!( - " else {{\n{}}}", - body.iter().map(|b| b.code()).format(""), - ), - None => String::new(), + Some([e @ TemplateExpression::IfBlock { .. }]) => { + out.write_str(" else ")?; + e.write_code(out) + } + Some(body) => { + out.write_str(" else {\n")?; + for b in body { + b.write_code(out)?; + } + out.write_str("}\n") + } + None => out.write_char('\n'), } - ), - TemplateExpression::MatchBlock { ref expr, ref arms } => format!( - "match {expr} {{{}}}\n", - arms.iter().format_with("", |(expr, body), f| { - f(&format_args!( - "\n {} => {{\n{}}}", - expr, - body.iter().map(|b| b.code()).format(""), - )) - }) - ), + } + TemplateExpression::MatchBlock { ref expr, ref arms } => { + write!(out, "match {expr} {{")?; + for (expr, body) in arms { + write!(out, "\n {expr} => {{")?; + for b in body { + b.write_code(out)?; + } + write!(out, "}}")?; + } + writeln!(out, "\n}}") + } TemplateExpression::CallTemplate { ref name, ref args } => { - format!( - "{name}(_ructe_out_.by_ref(){})?;\n", - args.iter().format_with("", |arg, f| f(&format_args!( - ", {arg}" - ))), - ) + write!(out, "{name}(_ructe_out_.by_ref()",)?; + for arg in args { + write!(out, ", {arg}")?; + } + writeln!(out, ")?;") } } }