Skip to main content

max / makenotwork

4.0 KB · 116 lines History Blame Raw
1 //! Configurable markdown-to-HTML rendering with sanitization presets.
2 //!
3 //! Provides four rendering presets for different trust levels:
4 //! - **Permissive** -- full GFM (tables, footnotes, images, raw HTML). For trusted content.
5 //! - **Standard** -- GFM without images. For app text fields.
6 //! - **Strict** -- no images, no raw HTML, dangerous scheme filtering, nofollow. For UGC.
7 //! - **Sanitize-only** -- ammonia cleaning without markdown parsing. For external HTML.
8 //!
9 //! Optional features add document loading, TOML frontmatter, @mention resolution,
10 //! and quote attribution post-processing.
11
12 #[cfg(any(feature = "mentions", feature = "assumptions", test))]
13 mod code_spans;
14 mod escape;
15 mod render;
16 mod sanitize;
17 mod text;
18 mod toc;
19
20 #[cfg(feature = "assumptions")]
21 mod assumptions;
22 #[cfg(feature = "assumptions")]
23 mod filters;
24 #[cfg(feature = "directives")]
25 mod directives;
26 #[cfg(feature = "doc-loader")]
27 mod doc_loader;
28 #[cfg(feature = "frontmatter")]
29 mod frontmatter;
30 #[cfg(feature = "mentions")]
31 mod mentions;
32 #[cfg(feature = "quotes")]
33 mod quotes;
34 #[cfg(feature = "media-urls")]
35 mod media_urls;
36
37 // Re-export core types
38 pub use render::{RenderResult, Renderer};
39 pub use sanitize::SanitizePreset;
40 pub use text::{extract_title, reading_time_minutes, strip_first_heading, word_count};
41 pub use toc::{TocEntry, extract_toc, render_toc_html};
42
43 // Re-export feature-gated types
44 #[cfg(feature = "assumptions")]
45 pub use assumptions::{Assumptions, AssumptionsError, LookupValue};
46 #[cfg(feature = "assumptions")]
47 pub use filters::{Filter, FilterArg, FilterError};
48 #[cfg(feature = "directives")]
49 pub use directives::post_process_directives;
50 #[cfg(feature = "doc-loader")]
51 pub use doc_loader::{DocIndexEntry, DocLoader, DocLoaderConfig, DocPage, DocSearchEntry};
52 #[cfg(feature = "frontmatter")]
53 pub use frontmatter::{Frontmatter, parse_frontmatter};
54 #[cfg(feature = "mentions")]
55 pub use mentions::{extract_mentions, resolve_mentions};
56 #[cfg(feature = "quotes")]
57 pub use quotes::{QuoteAuthor, post_process_quotes};
58 #[cfg(feature = "media-urls")]
59 pub use media_urls::{img_to_video, rewrite_media_paths};
60
61 /// Render markdown with the permissive preset (GFM features, default ammonia).
62 pub fn render_permissive(markdown: &str) -> String {
63 Renderer::permissive().render(markdown)
64 }
65
66 /// Render markdown with the standard preset (GFM features, no images).
67 pub fn render_standard(markdown: &str) -> String {
68 Renderer::standard().render(markdown)
69 }
70
71 /// Render markdown with the strict preset (no images, no raw HTML, nofollow).
72 pub fn render_strict(markdown: &str) -> String {
73 Renderer::strict().render(markdown)
74 }
75
76 /// Sanitize HTML without markdown parsing.
77 pub fn sanitize_html(html: &str) -> String {
78 Renderer::sanitize_only().sanitize_html(html)
79 }
80
81 #[cfg(test)]
82 mod top_level_tests {
83 use super::*;
84
85 // Direct unit tests for the top-level convenience wrappers. Without these,
86 // mutating any of them to return `String::new()` or a sentinel passes the
87 // suite (the wrappers were untested at the function level).
88
89 #[test]
90 fn render_permissive_emits_paragraph_markup() {
91 let out = render_permissive("Hello **world**.");
92 assert!(out.contains("<p>"), "expected paragraph: {out:?}");
93 assert!(out.contains("<strong>world</strong>"), "expected bold: {out:?}");
94 }
95
96 #[test]
97 fn render_standard_strips_images() {
98 let out = render_standard("![alt](pic.png)");
99 assert!(!out.contains("<img"), "standard preset must strip images: {out:?}");
100 }
101
102 #[test]
103 fn render_strict_strips_images_and_raw_html() {
104 let out = render_strict("Hi <script>evil()</script> ![x](y.png)");
105 assert!(!out.contains("<img"), "strict must strip images: {out:?}");
106 assert!(!out.contains("<script"), "strict must strip scripts: {out:?}");
107 }
108
109 #[test]
110 fn sanitize_html_strips_dangerous_tags() {
111 let out = sanitize_html("<p>safe</p><script>bad()</script>");
112 assert!(out.contains("safe"), "kept text: {out:?}");
113 assert!(!out.contains("<script"), "stripped script: {out:?}");
114 }
115 }
116