Skip to main content

lean_ctx_doc/
model.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Pages, sections and notes.
4
5use serde::{Deserialize, Serialize};
6
7use crate::anchor::Anchor;
8
9/// Where a page stands in its curation.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum PageStatus {
13    /// New material, not yet vouched for.
14    Draft,
15    /// Settled: what the next reader should trust.
16    Canonical,
17    /// History: kept, behind nothing.
18    Retired,
19}
20
21impl PageStatus {
22    /// Parses the status names emspace and the wire use.
23    pub fn parse(s: &str) -> Option<Self> {
24        match s {
25            "draft" => Some(Self::Draft),
26            "canonical" => Some(Self::Canonical),
27            "retired" => Some(Self::Retired),
28            _ => None,
29        }
30    }
31}
32
33/// One stretch of a page body.
34///
35/// `text` is raw markdown and includes the section's own heading line when it
36/// has one; concatenating the sections in `order` reproduces the body.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct Section {
39    pub order: u32,
40    pub text: String,
41}
42
43impl Section {
44    /// The heading this section opens with (`## Foo` → `Foo`), if any.
45    pub fn heading(&self) -> Option<&str> {
46        let first = self.text.lines().next()?;
47        let hashes = first.bytes().take_while(|b| *b == b'#').count();
48        if (2..=6).contains(&hashes) && first.as_bytes().get(hashes) == Some(&b' ') {
49            Some(first[hashes + 1..].trim())
50        } else {
51            None
52        }
53    }
54}
55
56/// A dated addendum to a page. A note that `covers` anchors accounts for
57/// their change in words, so the page reads noted instead of stale.
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub struct Note {
60    pub body: String,
61    #[serde(default, skip_serializing_if = "Vec::is_empty")]
62    pub covers: Vec<String>,
63}
64
65/// A documentation page.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct Page {
68    pub slug: String,
69    pub title: String,
70    /// Free classification: `reference`, `design`, `project`, ….
71    pub kind: String,
72    pub status: PageStatus,
73    pub sections: Vec<Section>,
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub anchors: Vec<Anchor>,
76    #[serde(default, skip_serializing_if = "Vec::is_empty")]
77    pub notes: Vec<Note>,
78}
79
80impl Page {
81    /// The body as one markdown document, title line first.
82    pub fn body(&self) -> String {
83        crate::sections::join(&self.title, &self.sections)
84    }
85}