Skip to main content

microcad_lang_markdown/
code_block.rs

1// Copyright © 2026 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! A markdown code block.
5
6/// A code block header.
7#[derive(Debug, Clone, PartialEq)]
8pub struct CodeBlockHeader {
9    /// Name of the code block.
10    pub name: Option<String>,
11    /// An optional fragment, e.g. to be used for test results: `#fragment`
12    pub fragment: Option<String>,
13    /// Parameters of the code block inside `()`
14    pub parameters: Vec<String>,
15}
16
17/// A code block header with, e.g.: `µcad#ok(hires)`
18impl CodeBlockHeader {
19    /// Test banner to show test result and access logs.
20    pub fn test_banner_string(name: &str) -> String {
21        format!("[![test](.test/{name}.svg)](.test/{name}.log)")
22    }
23
24    pub(crate) fn is_test_banner(line: &str) -> bool {
25        line.starts_with("[![test]")
26    }
27
28    pub(crate) fn is_code_block_start(line: &str) -> bool {
29        microcad_lang_base::MICROCAD_EXTENSIONS
30            .iter()
31            .any(|ext| line.starts_with(&format!("```{ext}")))
32            || Self::is_test_banner(line)
33    }
34}
35
36impl std::fmt::Display for CodeBlockHeader {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        if let Some(name) = &self.name {
39            writeln!(f, "{}\n", Self::test_banner_string(name))?
40        }
41
42        write!(f, "```µcad")?;
43        if let Some(name) = &self.name {
44            write!(f, ",{name}")?
45        }
46
47        match &self.fragment {
48            None => {}
49            Some(fragment) => {
50                write!(f, "#{fragment}")?;
51            }
52        };
53        if !self.parameters.is_empty() {
54            write!(f, "({})", self.parameters.join(","))?;
55        }
56        Ok(())
57    }
58}
59
60/// A code block starting inside ```
61#[derive(Debug, Clone, PartialEq)]
62pub struct CodeBlock {
63    /// The header of code block starting with ```
64    pub header: CodeBlockHeader,
65    /// The actual code.
66    pub code: String,
67    /// Line offset inside markdown file.
68    pub line_offset: usize,
69}
70
71impl CodeBlock {
72    /// Return the name of this code block.
73    ///
74    /// Must be unique within a markdown file.
75    pub fn name(&self) -> &Option<String> {
76        &self.header.name
77    }
78
79    /// Return test result.
80    pub fn fragment(&self) -> &Option<String> {
81        &self.header.fragment
82    }
83
84    pub fn code(&self) -> &str {
85        &self.code
86    }
87
88    pub fn line_offset(&self) -> usize {
89        self.line_offset
90    }
91
92    /// Returns true if this code block can be formatted.
93    ///
94    /// A code block can be formatted if there is no `no_format` parameter given.
95    pub fn can_format(&self) -> bool {
96        !self.header.parameters.contains(&String::from("no_format"))
97    }
98}
99
100impl std::fmt::Display for CodeBlock {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        writeln!(f, "{}", self.header)?;
103        if !self.code.is_empty() {
104            writeln!(f, "{}", self.code)?;
105        }
106        write!(f, "```")
107    }
108}