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}
125
126/// Emphasised or de-emphasised piece of text.
127#[derive(Clone, Default, Debug, Eq, PartialEq)]
128pub struct Emphasis {
129    pub strength: EmStrength,
130    pub etype: EmType,
131    pub text: String,
132    pub tags: Tags,
133    pub props: Props,
134}
135
136/// Degree of emphasis or de-emphasis.
137#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
138pub enum EmStrength {
139    #[default]
140    Light,
141    Medium,
142    Strong,
143}
144
145/// Whether it is an emphasis or de-emphasis.
146#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
147pub enum EmType {
148    #[default]
149    Emphasis,
150    Deemphasis,
151}
152
153/// Lists are fine-grained structure in a document.
154#[derive(Clone, Default, Debug, Eq, PartialEq)]
155pub struct List {
156    pub ltype: ListType,
157    pub items: Vec<Paragraph>,
158    pub tags: Tags,
159    pub props: Props,
160}
161
162#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
163pub enum ListType {
164    /// Each item has a distinct denotation.
165    Distinct,
166    /// Each item is denotated identically.
167    #[default] Identical,
168    /// Each item is either checked off or not.
169    Checked,
170}
171
172/// Navigation structure contains sub-navigation structures.
173pub type Nav = Vec<SNav>;
174
175/// Sub-navigation structure has a description, sub-navigation structures and links to navigate to.
176#[derive(Clone, Default, Debug, Eq, PartialEq)]
177pub struct SNav {
178    pub description: String,
179    pub subs: Vec<SNav>,
180    pub links: Vec<Link>,
181    pub tags: Tags,
182    pub props: Props,
183}
184
185/// Links are pieces of text with an accompanying URL.
186#[derive(Clone, Default, Debug, Eq, PartialEq)]
187pub struct Link {
188    pub url: String,
189    pub items: Vec<LinkItem>,
190    pub tags: Tags,
191    pub props: Props,
192}
193
194/// Links have an exterior of plain text that may be emphasised.
195#[derive(Clone, Debug, Eq, PartialEq)]
196pub enum LinkItem {
197    String(String),
198    Em(Emphasis),
199}
200
201/// `CodeBlock` contains computer code.
202#[derive(Clone, Default, Debug, Eq, PartialEq)]
203pub struct CodeBlock {
204    /// Computer language in which the code is written.
205    pub language: String,
206    /// Behavioural hint.
207    pub mode: CodeModeHint,
208    /// The code.
209    pub code: String,
210    pub tags: Tags,
211    pub props: Props,
212}
213
214/// Behavioural hint: the block hints what to do with the code.
215#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
216pub enum CodeModeHint {
217    /// Hint to show the code in the document.
218    #[default] Show,
219    /// Hint to show the code in the document and to signal that it is supposed to be able to run.
220    Runnable,
221    /// Hint to show the code in the document and to run the code and show the results as well.
222    Run,
223    /// Hint to run the code and show the results in the document instead of the code itself.
224    Replace,
225}
226
227/// Text that has metadata: tags and/or properties.
228#[derive(Clone, Default, Debug, Eq, PartialEq)]
229pub struct TextWithMeta {
230    pub text: String,
231    pub tags: Tags,
232    pub props: Props,
233}
234
235impl TextWithMeta {
236    fn meta_is_empty(&self) -> bool {
237        self.tags.is_empty() && self.props.is_empty()
238    }
239}
240
241/// Error to signal that the code was not formatted properly.
242#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
243pub struct CodeIdentError;
244
245/// Simple date: it is not checked if it actually exists on the calendar.
246#[derive(Clone, Copy, Default, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)]
247pub struct Date {
248    pub year: i16,
249    pub month: u8,
250    pub day: u8,
251}
252
253#[derive(Clone, Debug, Eq, PartialEq)]
254pub enum DateError {
255    /// The year was out of range of i16.
256    YearRange(i64),
257    /// The month was out of range: bigger than 12.
258    MonthRange(u64),
259    /// The day was out of range: bigger than 31.
260    DayRange(u64),
261    /// There was an error parsing an integer in the date.
262    Parsing(ParseIntError),
263}
264
265impl Date {
266    pub fn new(y: i64, m: u64, d: u64) -> Result<Self, DateError> {
267        let year: i16 = y.try_into().map_err(|_| DateError::YearRange(y))?;
268        let month: u8 = m.try_into()
269            .map_err(|_| DateError::MonthRange(m))
270            .and_then(|m| if m == 0 { Err(DateError::MonthRange(d)) } else { Ok(m) } )?;
271        let day: u8 = d.try_into()
272            .map_err(|_| DateError::DayRange(d))
273            .and_then(|d| if d == 0 { Err(DateError::DayRange(u64::from(d))) } else { Ok(d) } )?;
274        if month > 12 { return Err(DateError::MonthRange(m)); }
275        if day > 31 { return Err(DateError::DayRange(d)); }
276        Ok(Self { year, month, day })
277    }
278}
279