credence_lib/render/
renderer.rs1use super::{context::*, preparer::*};
2
3use {
4 ::axum::http::*,
5 compris::*,
6 kutil::{
7 http::*,
8 std::{immutable::*, *},
9 },
10 markdown::{mdast::*, *},
11 std::result::Result,
12};
13
14#[derive(Clone, Copy, Debug, Default, Display, FromStr, Eq, Hash, PartialEq)]
20#[from_str(lowercase)]
21pub enum Renderer {
22 Passthrough,
24
25 #[strings("markdown", "md")]
27 Markdown,
28
29 #[default]
31 GFM,
32}
33
34impl Renderer {
35 pub async fn render(&self, content: &str) -> Result<ByteString, StatusCode> {
37 match self {
38 Self::Passthrough => Ok(content.into()),
39 Self::Markdown => Self::render_markdown(content, Default::default()),
40 Self::GFM => Self::render_markdown(content, Options::gfm()),
41 }
42 }
43
44 pub fn title_from_content(&self, content: &str) -> Result<Option<ByteString>, StatusCode> {
46 match self {
47 Self::Passthrough => Ok(self.title_from_passthrough(content)?.map(|title| title.into())),
48 Self::Markdown => self.title_from_markdown(content, &Default::default()),
49 Self::GFM => self.title_from_markdown(content, &ParseOptions::gfm()),
50 }
51 }
52
53 pub fn render_markdown(content: &str, mut options: Options) -> Result<ByteString, StatusCode> {
55 options.compile.allow_dangerous_html = true;
56 to_html_with_options(content, &options).map(|html| html.into()).map_err_internal_server("Markdown to HTML")
57 }
58
59 pub fn title_from_passthrough<'content>(
63 &self,
64 content: &'content str,
65 ) -> Result<Option<&'content str>, StatusCode> {
66 if let Some(heading_start) = content.find("<h1>") {
67 let title = &content[heading_start + 4..];
68 if let Some(heading_end) = title.find("</h1>") {
69 let title = &title[..heading_end];
70 return Ok(Some(title));
71 }
72 }
73
74 Ok(None)
75 }
76
77 pub fn title_from_markdown(&self, content: &str, options: &ParseOptions) -> Result<Option<ByteString>, StatusCode> {
81 let node = to_mdast(content, options).map_err_internal_server("parse Markdown")?;
85
86 if let Some(children) = node.children() {
87 for child in children {
88 if let Node::Heading(heading) = child
89 && let Some(child) = heading.children.get(0)
90 && let Node::Text(text) = child
91 {
92 return Ok(Some(text.value.clone().into()));
93 }
94 }
95 }
96
97 Ok(None)
98 }
99}
100
101impl_resolve_from_str!(Renderer);
102
103impl RenderPreparer for Renderer {
104 async fn prepare<'own>(&self, context: &mut RenderContext<'own>) -> Result<(), StatusCode> {
105 if !context.variables.contains_key("content") {
106 if let Some(content) = context.rendered_page.content.as_ref() {
107 let content = self.render(content).await?;
108 context.variables.insert("content".into(), content.into());
109 }
110 }
111
112 Ok(())
113 }
114}