Skip to main content

sim_lib_view_doc/
citizen.rs

1use sim_citizen_derive::Citizen;
2use sim_kernel::{Expr, Result, Symbol};
3
4use crate::{article, prose};
5
6/// A validated documentation article wrapped as a runtime Citizen object.
7#[derive(Clone, Debug, PartialEq, Citizen)]
8#[citizen(symbol = "doc/Article", version = 1)]
9pub struct DocArticleDescriptor {
10    #[citizen(with = "article_expr")]
11    article: Expr,
12}
13
14impl DocArticleDescriptor {
15    /// Builds a descriptor from an article expression, validating it.
16    ///
17    /// # Errors
18    ///
19    /// Returns an error when `article` is not a well-formed article expression.
20    pub fn from_expr(article: Expr) -> Result<Self> {
21        article_expr::decode(&article)?;
22        Ok(Self { article })
23    }
24
25    /// Returns the underlying article expression.
26    pub fn as_expr(&self) -> &Expr {
27        &self.article
28    }
29}
30
31impl Default for DocArticleDescriptor {
32    fn default() -> Self {
33        Self::from_expr(article("Citizen Article", vec![prose("citizen doc")]))
34            .expect("default article descriptor should be valid")
35    }
36}
37
38/// Returns the class symbol for the documentation article Citizen.
39pub fn doc_article_class_symbol() -> Symbol {
40    Symbol::qualified("doc", "Article")
41}
42
43pub(crate) mod article_expr {
44    use sim_kernel::{Error, Expr, Result};
45
46    use crate::{blocks, title};
47
48    pub fn encode(expr: &Expr) -> Expr {
49        expr.clone()
50    }
51
52    pub fn decode(expr: &Expr) -> Result<Expr> {
53        title(expr).ok_or_else(|| Error::Eval("doc article is missing title".to_owned()))?;
54        let blocks = blocks(expr);
55        if blocks.is_empty() {
56            return Err(Error::Eval("doc article is missing blocks".to_owned()));
57        }
58        Ok(expr.clone())
59    }
60}