incodoc/
lib.rs

1mod tests;
2pub mod parsing;
3pub mod output;
4pub mod actions;
5pub mod reference_doc;
6
7use std::{
8    num::ParseIntError,
9    collections::{ HashMap, HashSet },
10};
11
12use pest_derive::Parser;
13
14/// Parser for incodoc.
15#[derive(Parser)]
16#[grammar = "parse/incodoc.pest"]
17pub struct IncodocParser;
18
19/// Document.
20#[derive(Clone, Default, Debug, Eq, PartialEq)]
21pub struct Doc {
22    pub tags: Tags,
23    pub props: Props,
24    pub items: Vec<DocItem>,
25}
26
27/// Document item.
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum DocItem {
30    /// Navigation.
31    Nav(Nav),
32    Paragraph(Paragraph),
33    Section(Section),
34}
35
36/// Tags metadata. Each tag is a string.
37pub type Tags = HashSet<String>;
38
39/// Properties metadata. Each property is a tuple of an identifier and a value.
40pub type Props = HashMap<String, PropVal>;
41
42/// Value properties can take.
43#[derive(Clone, Debug, Eq, PartialEq)]
44pub enum PropVal {
45    String(String),
46    Text(String),
47    Int(i64),
48    Date(Date),
49    Error(PropValError),
50}
51
52/// Error when no valid value could be parsed.
53#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum PropValError {
55    Int(ParseIntError),
56    Date(DateError),
57}
58
59impl PropVal {
60    fn is_error(&self) -> bool {
61        matches![self, PropVal::Error(_)]
62    }
63}
64
65fn insert_prop(props: &mut Props, (k, v): (String, PropVal)) {
66    let mut insert = true;
67    if v.is_error() && let Some(ov) = props.get(&k) && !ov.is_error() {
68        insert = false;
69    }
70    if insert {
71        props.insert(k, v);
72    }
73}
74
75/// A section is a heading followed by content that goes with it.
76#[derive(Clone, Default, Debug, Eq, PartialEq)]
77pub struct Section {
78    pub heading: Heading,
79    pub items: Vec<SectionItem>,
80    pub tags: Tags,
81    pub props: Props,
82}
83
84/// Section items are either paragraphs or sub-sections.
85#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum SectionItem {
87    Paragraph(Paragraph),
88    Section(Section),
89}
90
91/// Heading, a title for the accompanying content.
92#[derive(Clone, Default, Debug, Eq, PartialEq)]
93pub struct Heading {
94    pub level: u8,
95    pub items: Vec<HeadingItem>,
96    pub tags: Tags,
97    pub props: Props,
98}
99
100/// Headings are plain text that can have emphasis.
101#[derive(Clone, Debug, Eq, PartialEq)]
102pub enum HeadingItem {
103    String(String),
104    Em(Emphasis),
105}
106
107/// Paragraph is a grouping of content.
108#[derive(Clone, Default, Debug, Eq, PartialEq)]
109pub struct Paragraph {
110    pub items: Vec<ParagraphItem>,
111    pub tags: Tags,
112    pub props: Props,
113}
114
115/// Paragraphs can have content and further structure but no smaller sub-sections.
116#[derive(Clone, Debug, Eq, PartialEq)]
117pub enum ParagraphItem {
118    Text(String),
119    MText(TextWithMeta),
120    Em(Emphasis),
121    Code(Result<CodeBlock, CodeIdentError>),
122    Link(Link),
123    List(List),
124    Table(Table),
125}
126
127/// Emphasised or de-emphasised piece of text.
128#[derive(Clone, Default, Debug, Eq, PartialEq)]
129pub struct Emphasis {
130    pub strength: EmStrength,
131    pub etype: EmType,
132    pub text: String,
133    pub tags: Tags,
134    pub props: Props,
135}
136
137/// Degree of emphasis or de-emphasis.
138#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
139pub enum EmStrength {
140    #[default]
141    Light,
142    Medium,
143    Strong,
144}
145
146/// Whether it is an emphasis or de-emphasis.
147#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
148pub enum EmType {
149    #[default]
150    Emphasis,
151    Deemphasis,
152}
153
154/// Lists are fine-grained structure in a document.
155#[derive(Clone, Default, Debug, Eq, PartialEq)]
156pub struct List {
157    pub ltype: ListType,
158    pub items: Vec<Paragraph>,
159    pub tags: Tags,
160    pub props: Props,
161}
162
163#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
164pub enum ListType {
165    /// Each item has a distinct denotation.
166    Distinct,
167    /// Each item is denotated identically.
168    #[default] Identical,
169    /// Each item is either checked off or not.
170    Checked,
171}
172
173/// Navigation structure has a description, sub-navigation structures and links to navigate to.
174#[derive(Clone, Default, Debug, Eq, PartialEq)]
175pub struct Nav {
176    pub description: String,
177    pub subs: Vec<Nav>,
178    pub links: Vec<Link>,
179    pub tags: Tags,
180    pub props: Props,
181}
182
183/// Links are pieces of text with an accompanying URL.
184#[derive(Clone, Default, Debug, Eq, PartialEq)]
185pub struct Link {
186    pub url: String,
187    pub items: Vec<LinkItem>,
188    pub tags: Tags,
189    pub props: Props,
190}
191
192/// Links have an exterior of plain text that may be emphasised.
193#[derive(Clone, Debug, Eq, PartialEq)]
194pub enum LinkItem {
195    String(String),
196    Em(Emphasis),
197}
198
199/// `CodeBlock` contains computer code.
200#[derive(Clone, Default, Debug, Eq, PartialEq)]
201pub struct CodeBlock {
202    /// Computer language in which the code is written.
203    pub language: String,
204    /// Behavioural hint.
205    pub mode: CodeModeHint,
206    /// The code.
207    pub code: String,
208    pub tags: Tags,
209    pub props: Props,
210}
211
212/// Behavioural hint: the block hints what to do with the code.
213#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
214pub enum CodeModeHint {
215    /// Hint to show the code in the document.
216    #[default] Show,
217    /// Hint to show the code in the document and to signal that it is supposed to be able to run.
218    Runnable,
219    /// Hint to show the code in the document and to run the code and show the results as well.
220    Run,
221    /// Hint to run the code and show the results in the document instead of the code itself.
222    Replace,
223}
224
225/// `Table` contains rows of paragraphs.
226#[derive(Clone, Default, Debug, Eq, PartialEq)]
227pub struct Table {
228    pub rows: Vec<TableRow>,
229    pub tags: Tags,
230    pub props: Props,
231}
232
233/// Tables contain rows of paragraphs
234#[derive(Clone, Default, Debug, Eq, PartialEq)]
235pub struct TableRow {
236    pub items: Vec<Paragraph>,
237    pub is_header: bool,
238    pub tags: Tags,
239    pub props: Props,
240}
241
242/// Text that has metadata: tags and/or properties.
243#[derive(Clone, Default, Debug, Eq, PartialEq)]
244pub struct TextWithMeta {
245    pub text: String,
246    pub tags: Tags,
247    pub props: Props,
248}
249
250impl TextWithMeta {
251    fn meta_is_empty(&self) -> bool {
252        self.tags.is_empty() && self.props.is_empty()
253    }
254}
255
256/// Error to signal that the code was not formatted properly.
257#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
258pub struct CodeIdentError;
259
260/// Simple date: it is not checked if it actually exists on the calendar.
261#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
262pub struct Date {
263    pub year: i16,
264    pub month: u8,
265    pub day: u8,
266}
267
268#[derive(Clone, Debug, Eq, PartialEq)]
269pub enum DateError {
270    /// The year was out of range of i16.
271    YearRange(i64),
272    /// The month was out of range: bigger than 12.
273    MonthRange(u64),
274    /// The day was out of range: bigger than 31.
275    DayRange(u64),
276    /// There was an error parsing an integer in the date.
277    Parsing(ParseIntError),
278}
279
280impl Date {
281    pub fn new(y: i64, m: u64, d: u64) -> Result<Self, DateError> {
282        let year: i16 = y.try_into().map_err(|_| DateError::YearRange(y))?;
283        let month: u8 = m.try_into()
284            .map_err(|_| DateError::MonthRange(m))
285            .and_then(|m| if m == 0 { Err(DateError::MonthRange(d)) } else { Ok(m) } )?;
286        let day: u8 = d.try_into()
287            .map_err(|_| DateError::DayRange(d))
288            .and_then(|d| if d == 0 { Err(DateError::DayRange(u64::from(d))) } else { Ok(d) } )?;
289        if month > 12 { return Err(DateError::MonthRange(m)); }
290        if day > 31 { return Err(DateError::DayRange(d)); }
291        Ok(Self { year, month, day })
292    }
293}
294