yarner_lib/
document.rs

1//! The internal representation of a literate document
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::default::Default;
5use std::path::{Path, PathBuf};
6
7/// A representation of a `Document` of literate code
8#[derive(Debug, Serialize, Deserialize)]
9pub struct Document {
10    /// The nodes forming the document
11    pub nodes: Vec<Node>,
12    /// The newline character(s) used in the sources
13    pub newline: String,
14}
15
16/// A node, representing text and code blocks, as well as transclusions
17#[derive(Debug, Serialize, Deserialize)]
18pub enum Node {
19    /// A text block
20    Text(TextBlock),
21    /// A code block
22    Code(CodeBlock),
23    /// A transclusion
24    Transclusion(Transclusion),
25}
26
27impl Document {
28    /// Creates a new document with the given nodes
29    pub fn new(nodes: Vec<Node>, newline: String) -> Self {
30        Document { nodes, newline }
31    }
32
33    /// The newline character(s) used in the sources
34    pub fn newline(&self) -> &str {
35        &self.newline
36    }
37
38    /// Gets all the code blocks of this document
39    pub fn code_blocks(&self) -> impl Iterator<Item = &CodeBlock> {
40        self.nodes.iter().filter_map(|node| match node {
41            Node::Code(block) => Some(block),
42            _ => None,
43        })
44    }
45
46    /// Code blocks, mapped by name
47    pub fn code_blocks_by_name(&self) -> HashMap<Option<&str>, Vec<&CodeBlock>> {
48        let mut code_blocks = HashMap::<_, Vec<&CodeBlock>>::new();
49
50        for block in self.code_blocks() {
51            code_blocks
52                .entry(block.name.as_deref())
53                .or_default()
54                .push(block);
55        }
56
57        code_blocks
58    }
59
60    /// Gets all the transclusions of this document
61    pub fn transclusions(&self) -> impl Iterator<Item = &Transclusion> {
62        self.nodes.iter().filter_map(|node| match node {
63            Node::Transclusion(trans) => Some(trans),
64            _ => None,
65        })
66    }
67
68    /// Finds all file-specific entry points
69    pub fn entry_points(&self) -> HashMap<Option<&str>, (&Path, Option<PathBuf>)> {
70        let mut entries = HashMap::new();
71        for block in self.code_blocks() {
72            if let Some(name) = block.name.as_deref() {
73                if block.is_file {
74                    entries.insert(
75                        Some(name),
76                        (
77                            Path::new(name),
78                            block.source_file.as_ref().map(|file| file.into()),
79                        ),
80                    );
81                }
82            }
83        }
84        entries
85    }
86}
87
88/// A `TextBlock` is just text that will be copied verbatim into the output documentation file
89#[derive(Debug, Default, Serialize, Deserialize)]
90pub struct TextBlock {
91    /// The source text
92    pub text: Vec<String>,
93}
94
95/// A `Transclusion` is a reference to another file that should be pulled into the source
96#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
97pub struct Transclusion {
98    /// The target file path
99    pub file: PathBuf,
100    /// The original string of the transclusion
101    pub original: String,
102}
103
104/// A `CodeBlock` is a block of code as defined by the input format.
105#[derive(Default, Debug, Serialize, Deserialize)]
106pub struct CodeBlock {
107    /// Source line number of the first code line
108    pub line_number: usize,
109    /// The indent of this code block is in the documentation file
110    pub indent: String,
111    /// The name of this code block
112    pub name: Option<String>,
113    /// Whether the code block was originally unnamed
114    pub is_unnamed: bool,
115    /// The language this block was written in
116    pub language: Option<String>,
117    /// Marks the code block as hidden from docs
118    pub is_hidden: bool,
119    /// Marks the code block as a file-based entrypoint
120    pub is_file: bool,
121    /// Marks the code block as fenced by alternative sequence
122    pub is_alternative: bool,
123    /// The source is the lines of code
124    pub source: Vec<Line>,
125    /// Source file, for transcluded blocks
126    pub source_file: Option<String>,
127}
128
129impl CodeBlock {
130    pub fn new(
131        line_number: usize,
132        indent: String,
133        language: Option<String>,
134        alternative: bool,
135    ) -> Self {
136        CodeBlock {
137            line_number,
138            indent,
139            language,
140            is_alternative: alternative,
141            ..Default::default()
142        }
143    }
144}
145
146/// A `Source` represents the source code on a line.
147#[derive(Debug, Serialize, Deserialize)]
148pub enum Line {
149    /// A macro invocation
150    Macro {
151        /// Indentation of the line, without block indent
152        indent: String,
153        /// Name of the macro
154        name: String,
155    },
156    /// A line of source code
157    Source {
158        /// Indentation of the line, without block indent
159        indent: String,
160        /// Source code in the line
161        source: String,
162    },
163}