heart_patched_maud/
lib.rs1#![no_std]
2
3#![doc(html_root_url = "https://docs.rs/maud/0.26.0")]
11
12extern crate alloc;
13
14use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc};
15use core::fmt::{self, Arguments, Display, Write};
16
17pub use maud_macros::html;
18
19mod escape;
20
21pub struct Escaper<'a>(&'a mut String);
45
46impl<'a> Escaper<'a> {
47 pub fn new(buffer: &'a mut String) -> Escaper<'a> {
49 Escaper(buffer)
50 }
51}
52
53impl fmt::Write for Escaper<'_> {
54 fn write_str(&mut self, s: &str) -> fmt::Result {
55 escape::escape_to_string(s, self.0);
56 Ok(())
57 }
58}
59
60pub trait Render {
90 fn render(&self) -> Markup {
92 let mut buffer = String::new();
93 self.render_to(&mut buffer);
94 PreEscaped(buffer)
95 }
96
97 fn render_to(&self, buffer: &mut String) {
107 buffer.push_str(&self.render().into_string());
108 }
109}
110
111impl Render for str {
112 fn render_to(&self, w: &mut String) {
113 escape::escape_to_string(self, w);
114 }
115}
116
117impl Render for String {
118 fn render_to(&self, w: &mut String) {
119 str::render_to(self, w);
120 }
121}
122
123impl Render for Cow<'_, str> {
124 fn render_to(&self, w: &mut String) {
125 str::render_to(self, w);
126 }
127}
128
129impl Render for Arguments<'_> {
130 fn render_to(&self, w: &mut String) {
131 let _ = Escaper::new(w).write_fmt(*self);
132 }
133}
134
135impl<T: Render + ?Sized> Render for &T {
136 fn render_to(&self, w: &mut String) {
137 T::render_to(self, w);
138 }
139}
140
141impl<T: Render + ?Sized> Render for &mut T {
142 fn render_to(&self, w: &mut String) {
143 T::render_to(self, w);
144 }
145}
146
147impl<T: Render + ?Sized> Render for Box<T> {
148 fn render_to(&self, w: &mut String) {
149 T::render_to(self, w);
150 }
151}
152
153impl<T: Render + ?Sized> Render for Arc<T> {
154 fn render_to(&self, w: &mut String) {
155 T::render_to(self, w);
156 }
157}
158
159macro_rules! impl_render_with_display {
160 ($($ty:ty)*) => {
161 $(
162 impl Render for $ty {
163 fn render_to(&self, w: &mut String) {
164 format_args!("{self}", self = self).render_to(w);
166 }
167 }
168 )*
169 };
170}
171
172impl_render_with_display! {
173 char f32 f64
174}
175
176macro_rules! impl_render_with_itoa {
177 ($($ty:ty)*) => {
178 $(
179 impl Render for $ty {
180 fn render_to(&self, w: &mut String) {
181 w.push_str(itoa::Buffer::new().format(*self));
182 }
183 }
184 )*
185 };
186}
187
188impl_render_with_itoa! {
189 i8 i16 i32 i64 i128 isize
190 u8 u16 u32 u64 u128 usize
191}
192
193pub fn display(value: impl Display) -> impl Render {
211 struct DisplayWrapper<T>(T);
212
213 impl<T: Display> Render for DisplayWrapper<T> {
214 fn render_to(&self, w: &mut String) {
215 format_args!("{0}", self.0).render_to(w);
216 }
217 }
218
219 DisplayWrapper(value)
220}
221
222#[derive(Debug, Clone, Copy)]
224pub struct PreEscaped<T>(pub T);
225
226impl<T: AsRef<str>> Render for PreEscaped<T> {
227 fn render_to(&self, w: &mut String) {
228 w.push_str(self.0.as_ref());
229 }
230}
231
232pub type Markup = PreEscaped<String>;
236
237impl<T: Into<String>> PreEscaped<T> {
238 pub fn into_string(self) -> String {
240 self.0.into()
241 }
242}
243
244impl<T: Into<String>> From<PreEscaped<T>> for String {
245 fn from(value: PreEscaped<T>) -> String {
246 value.into_string()
247 }
248}
249
250impl<T: Default> Default for PreEscaped<T> {
251 fn default() -> Self {
252 Self(Default::default())
253 }
254}
255
256pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
279
280#[cfg(feature = "rocket")]
281mod rocket_support {
282 extern crate std;
283
284 use crate::PreEscaped;
285 use alloc::string::String;
286 use rocket::{
287 http::ContentType,
288 request::Request,
289 response::{Responder, Response},
290 };
291 use std::io::Cursor;
292
293 impl Responder<'_, 'static> for PreEscaped<String> {
294 fn respond_to(self, _: &Request) -> rocket::response::Result<'static> {
295 Response::build()
296 .header(ContentType::HTML)
297 .sized_body(self.0.len(), Cursor::new(self.0))
298 .ok()
299 }
300 }
301}
302
303#[cfg(feature = "actix-web")]
304mod actix_support {
305 use core::{
306 pin::Pin,
307 task::{Context, Poll},
308 };
309
310 use crate::PreEscaped;
311 use actix_web_dep::{
312 body::{BodySize, MessageBody},
313 http::header,
314 web::Bytes,
315 HttpRequest, HttpResponse, Responder,
316 };
317 use alloc::string::String;
318
319 impl MessageBody for PreEscaped<String> {
320 type Error = <String as MessageBody>::Error;
321
322 fn size(&self) -> BodySize {
323 self.0.size()
324 }
325
326 fn poll_next(
327 mut self: Pin<&mut Self>,
328 cx: &mut Context<'_>,
329 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
330 Pin::new(&mut self.0).poll_next(cx)
331 }
332 }
333
334 impl Responder for PreEscaped<String> {
335 type Body = String;
336
337 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
338 HttpResponse::Ok()
339 .content_type(header::ContentType::html())
340 .message_body(self.0)
341 .unwrap()
342 }
343 }
344}
345
346#[cfg(feature = "tide")]
347mod tide_support {
348 use crate::PreEscaped;
349 use alloc::string::String;
350 use tide::{http::mime, Response, StatusCode};
351
352 impl From<PreEscaped<String>> for Response {
353 fn from(markup: PreEscaped<String>) -> Response {
354 Response::builder(StatusCode::Ok)
355 .body(markup.into_string())
356 .content_type(mime::HTML)
357 .build()
358 }
359 }
360}
361
362#[cfg(feature = "axum")]
363mod axum_support {
364 use crate::PreEscaped;
365 use alloc::string::String;
366 use axum_core::response::{IntoResponse, Response};
367 use http::{header, HeaderMap, HeaderValue};
368
369 impl IntoResponse for PreEscaped<String> {
370 fn into_response(self) -> Response {
371 let mut headers = HeaderMap::new();
372 headers.insert(
373 header::CONTENT_TYPE,
374 HeaderValue::from_static("text/html; charset=utf-8"),
375 );
376 (headers, self.0).into_response()
377 }
378 }
379}
380
381#[cfg(feature = "warp")]
382mod warp_support {
383 use crate::PreEscaped;
384 use alloc::string::String;
385 use warp::reply::{self, Reply, Response};
386
387 impl Reply for PreEscaped<String> {
388 fn into_response(self) -> Response {
389 reply::html(self.into_string()).into_response()
390 }
391 }
392}
393
394#[cfg(feature = "submillisecond")]
395mod submillisecond_support {
396 use crate::PreEscaped;
397 use alloc::string::String;
398 use submillisecond::{
399 http::{header, HeaderMap, HeaderValue},
400 response::{IntoResponse, Response},
401 };
402
403 impl IntoResponse for PreEscaped<String> {
404 fn into_response(self) -> Response {
405 let mut headers = HeaderMap::new();
406 headers.insert(
407 header::CONTENT_TYPE,
408 HeaderValue::from_static("text/html; charset=utf-8"),
409 );
410 (headers, self.0).into_response()
411 }
412 }
413}
414
415#[doc(hidden)]
416pub mod macro_private {
417 use crate::{display, Render};
418 use alloc::string::String;
419 use core::fmt::Display;
420
421 #[doc(hidden)]
422 #[macro_export]
423 macro_rules! render_to {
424 ($x:expr, $buffer:expr) => {{
425 use $crate::macro_private::*;
426 match ChooseRenderOrDisplay($x) {
427 x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
428 }
429 }};
430 }
431
432 pub use render_to;
433
434 pub struct ChooseRenderOrDisplay<T>(pub T);
435
436 pub struct ViaRenderTag;
437 pub struct ViaDisplayTag;
438
439 pub trait ViaRender {
440 fn implements_render_or_display(&self) -> ViaRenderTag {
441 ViaRenderTag
442 }
443 }
444 pub trait ViaDisplay {
445 fn implements_render_or_display(&self) -> ViaDisplayTag {
446 ViaDisplayTag
447 }
448 }
449
450 impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
451 impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
452
453 impl ViaRenderTag {
454 pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
455 value.render_to(buffer);
456 }
457 }
458
459 impl ViaDisplayTag {
460 pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
461 display(value).render_to(buffer);
462 }
463 }
464}