credence_lib/render/
renderer.rs1use super::{context::*, preparer::*};
2
3use {
4 ::axum::http::*,
5 bytestring::*,
6 compris::*,
7 kutil_http::*,
8 kutil_std::*,
9 markdown::{mdast::*, *},
10 std::result::Result,
11};
12
13#[derive(Clone, Copy, Debug, Default, Display, FromStr)]
19#[from_str(lowercase)]
20pub enum Renderer {
21 Passthrough,
23
24 #[strings("markdown", "md")]
26 Markdown,
27
28 #[default]
30 GFM,
31}
32
33impl Renderer {
34 pub async fn render(&self, content: &str) -> Result<ByteString, StatusCode> {
36 match self {
37 Self::Passthrough => Ok(content.into()),
38 Self::Markdown => Self::render_markdown(content, Options::default()),
39 Self::GFM => Self::render_markdown(content, Options::gfm()),
40 }
41 }
42
43 pub fn title_from_content(&self, content: &str) -> Result<Option<ByteString>, StatusCode> {
45 match self {
46 Self::Passthrough => Ok(self.title_from_passthrough(content)?.map(|title| title.into())),
47 Self::Markdown => self.title_from_markdown(content, &ParseOptions::default()),
48 Self::GFM => self.title_from_markdown(content, &ParseOptions::gfm()),
49 }
50 }
51
52 pub fn render_markdown(content: &str, mut options: Options) -> Result<ByteString, StatusCode> {
54 options.compile.allow_dangerous_html = true;
55 to_html_with_options(content, &options).map(|html| html.into()).map_err_internal_server("Markdown to HTML")
56 }
57
58 pub fn title_from_passthrough<'content>(
62 &self,
63 content: &'content str,
64 ) -> Result<Option<&'content str>, StatusCode> {
65 if let Some(heading_start) = content.find("<h1>") {
66 let title = &content[heading_start + 4..];
67 if let Some(heading_end) = title.find("</h1>") {
68 let title = &title[..heading_end];
69 return Ok(Some(title));
70 }
71 }
72
73 Ok(None)
74 }
75
76 pub fn title_from_markdown(&self, content: &str, options: &ParseOptions) -> Result<Option<ByteString>, StatusCode> {
80 let node = to_mdast(content, options).map_err_internal_server("parse Markdown")?;
84
85 if let Some(children) = node.children() {
86 for child in children {
87 if let Node::Heading(heading) = child {
88 if let Some(child) = heading.children.get(0) {
89 if let Node::Text(text) = child {
90 return Ok(Some(text.value.clone().into()));
91 }
92 }
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}