1#![no_std]
2
3#![doc(html_root_url = "https://docs.rs/maud/0.26.0")]
11
12extern crate alloc;
13
14pub use wini_maud_macros::html;
15use {
16 alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc},
17 core::{
18 fmt::{self, Arguments, Display, Write},
19 ops::Deref,
20 },
21 hashbrown::HashSet,
22};
23
24mod escape;
25
26pub struct Escaper<'a>(&'a mut String);
50
51impl<'a> Escaper<'a> {
52 pub fn new(buffer: &'a mut String) -> Escaper<'a> {
54 Escaper(buffer)
55 }
56}
57
58impl fmt::Write for Escaper<'_> {
59 fn write_str(&mut self, s: &str) -> fmt::Result {
60 escape::escape_to_string(s, self.0);
61 Ok(())
62 }
63}
64
65pub trait Render {
95 fn render(&self) -> Markup {
97 let mut buffer = String::new();
98 self.render_to(&mut buffer);
99 Markup {
100 content: PreEscaped(buffer),
101 linked_files: HashSet::new(),
102 }
103 }
104
105 fn render_to(&self, buffer: &mut String) {
115 buffer.push_str(&self.render().content.into_string());
116 }
117}
118
119impl Render for str {
120 fn render_to(&self, w: &mut String) {
121 escape::escape_to_string(self, w);
122 }
123}
124
125impl Render for String {
126 fn render_to(&self, w: &mut String) {
127 str::render_to(self, w);
128 }
129}
130
131impl Render for Cow<'_, str> {
132 fn render_to(&self, w: &mut String) {
133 str::render_to(self, w);
134 }
135}
136
137impl Render for Arguments<'_> {
138 fn render_to(&self, w: &mut String) {
139 let _ = Escaper::new(w).write_fmt(*self);
140 }
141}
142
143impl<T: Render + ?Sized> Render for &T {
144 fn render_to(&self, w: &mut String) {
145 T::render_to(self, w);
146 }
147}
148
149impl<T: Render + ?Sized> Render for &mut T {
150 fn render_to(&self, w: &mut String) {
151 T::render_to(self, w);
152 }
153}
154
155impl<T: Render + ?Sized> Render for Box<T> {
156 fn render_to(&self, w: &mut String) {
157 T::render_to(self, w);
158 }
159}
160
161impl<T: Render + ?Sized> Render for Arc<T> {
162 fn render_to(&self, w: &mut String) {
163 T::render_to(self, w);
164 }
165}
166
167macro_rules! impl_render_with_display {
168 ($($ty:ty)*) => {
169 $(
170 impl Render for $ty {
171 fn render_to(&self, w: &mut String) {
172 format_args!("{self}", self = self).render_to(w);
174 }
175 }
176 )*
177 };
178}
179
180impl_render_with_display! {
181 char f32 f64
182}
183
184macro_rules! impl_render_with_itoa {
185 ($($ty:ty)*) => {
186 $(
187 impl Render for $ty {
188 fn render_to(&self, w: &mut String) {
189 w.push_str(itoa::Buffer::new().format(*self));
190 }
191 }
192 )*
193 };
194}
195
196impl_render_with_itoa! {
197 i8 i16 i32 i64 i128 isize
198 u8 u16 u32 u64 u128 usize
199}
200
201pub fn display(value: impl Display) -> impl Render {
219 struct DisplayWrapper<T>(T);
220
221 impl<T: Display> Render for DisplayWrapper<T> {
222 fn render_to(&self, w: &mut String) {
223 format_args!("{0}", self.0).render_to(w);
224 }
225 }
226
227 DisplayWrapper(value)
228}
229
230#[derive(Debug, Clone, Copy)]
232pub struct PreEscaped<T>(pub T);
233
234impl<T: AsRef<str>> Render for PreEscaped<T> {
235 fn render_to(&self, w: &mut String) {
236 w.push_str(self.0.as_ref());
237 }
238}
239
240#[derive(Debug, Default, Clone)]
244pub struct Markup {
245 pub content: PreEscaped<String>,
246 pub linked_files: HashSet<String>,
247}
248impl Markup {
249 pub fn into_string(self) -> String {
251 self.content.into()
252 }
253}
254
255impl Render for Markup {
256 fn render_to(&self, w: &mut String) {
257 w.push_str(&self.content.0)
258 }
259}
260
261impl From<Markup> for PreEscaped<String> {
262 fn from(val: Markup) -> Self {
263 val.content
264 }
265}
266
267
268impl Deref for Markup {
269 type Target = PreEscaped<String>;
270
271 fn deref(&self) -> &Self::Target {
272 &self.content
273 }
274}
275
276impl<T: Into<String>> PreEscaped<T> {
277 pub fn into_string(self) -> String {
279 self.0.into()
280 }
281}
282
283impl<T: Into<String>> From<PreEscaped<T>> for String {
284 fn from(value: PreEscaped<T>) -> String {
285 value.into_string()
286 }
287}
288
289impl<T: Default> Default for PreEscaped<T> {
290 fn default() -> Self {
291 Self(Default::default())
292 }
293}
294
295pub const DOCTYPE: PreEscaped<&'static str> = PreEscaped("<!DOCTYPE html>");
318
319#[cfg(feature = "rocket")]
320mod rocket_support {
321 extern crate std;
322
323 use {
324 crate::PreEscaped,
325 alloc::string::String,
326 rocket::{
327 http::ContentType,
328 request::Request,
329 response::{Responder, Response},
330 },
331 std::io::Cursor,
332 };
333
334 impl Responder<'_, 'static> for PreEscaped<String> {
335 fn respond_to(self, _: &Request) -> rocket::response::Result<'static> {
336 Response::build()
337 .header(ContentType::HTML)
338 .sized_body(self.0.len(), Cursor::new(self.0))
339 .ok()
340 }
341 }
342}
343
344#[cfg(feature = "actix-web")]
345mod actix_support {
346 use {
347 crate::PreEscaped,
348 actix_web_dep::{
349 HttpRequest,
350 HttpResponse,
351 Responder,
352 body::{BodySize, MessageBody},
353 http::header,
354 web::Bytes,
355 },
356 alloc::string::String,
357 core::{
358 pin::Pin,
359 task::{Context, Poll},
360 },
361 };
362
363 impl MessageBody for PreEscaped<String> {
364 type Error = <String as MessageBody>::Error;
365
366 fn size(&self) -> BodySize {
367 self.0.size()
368 }
369
370 fn poll_next(
371 mut self: Pin<&mut Self>,
372 cx: &mut Context<'_>,
373 ) -> Poll<Option<Result<Bytes, Self::Error>>> {
374 Pin::new(&mut self.0).poll_next(cx)
375 }
376 }
377
378 impl Responder for PreEscaped<String> {
379 type Body = String;
380
381 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
382 HttpResponse::Ok()
383 .content_type(header::ContentType::html())
384 .message_body(self.0)
385 .unwrap()
386 }
387 }
388}
389
390#[cfg(feature = "tide")]
391mod tide_support {
392 use {
393 crate::PreEscaped,
394 alloc::string::String,
395 tide::{Response, StatusCode, http::mime},
396 };
397
398 impl From<PreEscaped<String>> for Response {
399 fn from(markup: PreEscaped<String>) -> Response {
400 Response::builder(StatusCode::Ok)
401 .body(markup.into_string())
402 .content_type(mime::HTML)
403 .build()
404 }
405 }
406}
407
408#[cfg(feature = "axum")]
409mod axum_support {
410 use {
411 crate::Markup,
412 axum_core::response::{IntoResponse, Response},
413 http::{HeaderMap, HeaderValue, header},
414 };
415
416 impl IntoResponse for Markup {
417 fn into_response(self) -> Response {
418 let mut headers = HeaderMap::new();
419 headers.insert(
420 header::CONTENT_TYPE,
421 HeaderValue::from_static("text/html; charset=utf-8"),
422 );
423 (headers, self.content.0).into_response()
424 }
425 }
426}
427
428#[cfg(feature = "warp")]
429mod warp_support {
430 use {
431 crate::PreEscaped,
432 alloc::string::String,
433 warp::reply::{self, Reply, Response},
434 };
435
436 impl Reply for PreEscaped<String> {
437 fn into_response(self) -> Response {
438 reply::html(self.into_string()).into_response()
439 }
440 }
441}
442
443#[cfg(feature = "submillisecond")]
444mod submillisecond_support {
445 use {
446 crate::PreEscaped,
447 alloc::string::String,
448 submillisecond::{
449 http::{HeaderMap, HeaderValue, header},
450 response::{IntoResponse, Response},
451 },
452 };
453
454 impl IntoResponse for PreEscaped<String> {
455 fn into_response(self) -> Response {
456 let mut headers = HeaderMap::new();
457 headers.insert(
458 header::CONTENT_TYPE,
459 HeaderValue::from_static("text/html; charset=utf-8"),
460 );
461 (headers, self.0).into_response()
462 }
463 }
464}
465
466
467
468#[doc(hidden)]
469pub mod macro_private {
470 pub use hashbrown::HashSet;
471 use {
472 crate::{Render, display},
473 alloc::string::String,
474 core::fmt::Display,
475 };
476
477 #[doc(hidden)]
478 #[macro_export]
479 macro_rules! render_to {
480 ($x:expr, $buffer:expr) => {{
481 use $crate::macro_private::*;
482 match ChooseRenderOrDisplay($x) {
483 x => (&&x).implements_render_or_display().render_to(x.0, $buffer),
484 }
485 }};
486 }
487
488 pub use render_to;
489
490 pub struct ChooseRenderOrDisplay<T>(pub T);
491
492 pub struct ViaRenderTag;
493 pub struct ViaDisplayTag;
494
495 pub trait ViaRender {
496 fn implements_render_or_display(&self) -> ViaRenderTag {
497 ViaRenderTag
498 }
499 }
500 pub trait ViaDisplay {
501 fn implements_render_or_display(&self) -> ViaDisplayTag {
502 ViaDisplayTag
503 }
504 }
505
506 impl<T: Render> ViaRender for &ChooseRenderOrDisplay<T> {}
507 impl<T: Display> ViaDisplay for ChooseRenderOrDisplay<T> {}
508
509 impl ViaRenderTag {
510 pub fn render_to<T: Render + ?Sized>(self, value: &T, buffer: &mut String) {
511 value.render_to(buffer);
512 }
513 }
514
515 impl ViaDisplayTag {
516 pub fn render_to<T: Display + ?Sized>(self, value: &T, buffer: &mut String) {
517 display(value).render_to(buffer);
518 }
519 }
520}