flams_system/backend/
docfile.rs

1use std::{collections::HashMap, fs::File, io::BufReader, path::Path};
2
3use eyre::{eyre, Context};
4use flams_ontology::narration::{
5    documents::{Document, UncheckedDocument},
6    problems::{Quiz, QuizElement, QuizProblem},
7    DocumentElement, NarrationTrait,
8};
9use flams_utils::{impossible, vecmap::VecSet};
10use smallvec::SmallVec;
11
12use super::Backend;
13
14/*
15#[derive(Debug)]
16pub struct Offsets {
17    pub refs_offset: u32,
18    pub css_offset: u32,
19    pub html_offset: u32,
20    pub body_offset: u32,
21    pub body_len: u32,
22}
23*/
24
25pub struct PreDocFile;
26
27impl PreDocFile {
28    pub(crate) fn read_from_file(path: &Path) -> Option<UncheckedDocument> {
29        macro_rules! err {
30            ($e:expr) => {
31                match $e {
32                    Ok(e) => e,
33                    Err(e) => {
34                        tracing::error!("Error loading {}: {e}", path.display());
35                        return None;
36                    }
37                }
38            };
39        }
40        let file = err!(File::open(path));
41        let file = BufReader::new(file);
42        //UncheckedDocument::from_byte_stream(&mut file).ok()
43        Some(err!(bincode::serde::decode_from_reader(
44            file,
45            bincode::config::standard()
46        )))
47        //let offsets = Self::read_initials(&mut file)?;
48        //let doc = UncheckedDocument::from_byte_stream(&mut file).ok()?;
49        //Some(doc)//Some(Self { path, doc, offsets })
50    }
51}
52
53pub trait QuizExtension {
54    /// #### Errors
55    fn as_quiz(&self, backend: &impl Backend) -> eyre::Result<Quiz>;
56}
57impl QuizExtension for Document {
58    #[allow(clippy::redundant_else)]
59    #[allow(clippy::too_many_lines)]
60    fn as_quiz(&self, backend: &impl Backend) -> eyre::Result<Quiz> {
61        let mut css = VecSet::default();
62        let mut elements = Vec::new();
63        let mut solutions = HashMap::default();
64        let mut answer_classes: HashMap<_, Vec<_>> = HashMap::default();
65        let mut in_problem = false;
66
67        let mut stack: SmallVec<_, 2> = SmallVec::new();
68        let mut curr = self.children().iter();
69
70        macro_rules! push {
71            ($c:expr;$e:expr) => {
72                stack.push((
73                    std::mem::replace(&mut curr, $c),
74                    std::mem::take(&mut elements),
75                    $e,
76                ))
77            };
78        }
79        macro_rules! pop {
80            () => {
81                if let Some((c, mut e, s)) = stack.pop() {
82                    curr = c;
83                    std::mem::swap(&mut elements, &mut e);
84                    match s {
85                        Some(either::Either::Left(s)) => elements.push(QuizElement::Section {
86                            title: s,
87                            elements: e,
88                        }),
89                        Some(either::Either::Right(b)) => {
90                            in_problem = b;
91                            elements.extend(e.into_iter());
92                        }
93                        _ => elements.extend(e.into_iter()),
94                    }
95                    continue;
96                } else {
97                    break;
98                }
99            };
100        }
101
102        loop {
103            let Some(e) = curr.next() else { pop!() };
104            match e {
105                DocumentElement::DocumentReference { target, .. } => {
106                    // safety first
107                    let uri = target.id();
108                    let Some(d) = backend.get_document(&uri) else {
109                        return Err(eyre!("Missing document {uri}"));
110                    };
111                    let ret = d.as_quiz(backend)?;
112                    /*
113                    let ret = if let Some(d) = target.get() {
114                        d.as_quiz(backend)?
115                    } else {
116                        let uri = target.id();
117                        let Some(d) = backend.get_document(&uri) else {
118                            return Err(eyre!("Missing document {uri}"));
119                        };
120                        d.as_quiz(backend)?
121                    };*/
122                    for c in ret.css {
123                        css.insert(c);
124                    }
125                    elements.extend(ret.elements);
126                    for (u, s) in ret.solutions {
127                        solutions.insert(u, s);
128                    }
129                }
130                DocumentElement::Section(sect) => {
131                    if let Some(title) = sect.title {
132                        let Some((c, s)) = backend.get_html_fragment(self.uri(), title) else {
133                            return Err(eyre!("Missing FTML fragment for {}", sect.uri));
134                        };
135                        for c in c {
136                            css.insert(c);
137                        }
138                        push!(sect.children().iter();Some(either::Either::Left(s)));
139                    } else {
140                        push!(sect.children().iter();None);
141                    }
142                }
143                DocumentElement::Paragraph(p) => {
144                    let Some((c, html)) = backend.get_html_fragment(self.uri(), p.range) else {
145                        return Err(eyre!("Missing FTML fragment for {}", p.uri));
146                    };
147                    for c in c {
148                        css.insert(c);
149                    }
150                    elements.push(QuizElement::Paragraph { html });
151                }
152                DocumentElement::Problem(e) if in_problem => {
153                    let solution = backend
154                        .get_reference(&e.solutions)
155                        .wrap_err_with(|| format!("Missing solutions for {}", e.uri))?;
156                    let Some(solution) = solution.to_jstring() else {
157                        return Err(eyre!("Invalid solutions for {}", e.uri));
158                    };
159                    solutions.insert(e.uri.clone(), solution);
160                }
161                DocumentElement::Problem(e) => {
162                    let Some((c, html)) = backend.get_html_fragment(self.uri(), e.range) else {
163                        return Err(eyre!("Missing FTML fragment for {}", e.uri));
164                    };
165                    for c in c {
166                        css.insert(c);
167                    }
168                    let solution = backend
169                        .get_reference(&e.solutions)
170                        .wrap_err_with(|| format!("Missing solutions for {}", e.uri))?;
171                    let title_html = if let Some(ttl) = e.title {
172                        let Some(t) = backend.get_html_fragment(self.uri(), ttl) else {
173                            return Err(eyre!("Missing FTML fragment for title of {}", e.uri));
174                        };
175                        Some(t.1)
176                    } else {
177                        None
178                    };
179                    let Some(solution) = solution.to_jstring() else {
180                        return Err(eyre!("Invalid solutions for {}", e.uri));
181                    };
182                    for note in &e.gnotes {
183                        let gnote = backend
184                            .get_reference(note)
185                            .wrap_err_with(|| format!("Missing gnote for {}", e.uri))?;
186                        answer_classes
187                            .entry(e.uri.clone())
188                            .or_default()
189                            .extend(gnote.answer_classes);
190                    }
191                    solutions.insert(e.uri.clone(), solution);
192                    elements.push(QuizElement::Problem(QuizProblem {
193                        html, //solution,
194                        title_html,
195                        uri: e.uri.clone(),
196                        preconditions: e.preconditions.to_vec(),
197                        objectives: e.objectives.to_vec(),
198                        total_points: e.points,
199                    }));
200                    push!(e.children().iter();Some(either::Either::Right(in_problem)));
201                    in_problem = true;
202                }
203                e => {
204                    let c = e.children();
205                    if !c.is_empty() {
206                        push!(c.iter();None);
207                    }
208                }
209            }
210        }
211        if elements.len() == 1 && matches!(elements.first(), Some(QuizElement::Section { .. })) {
212            let Some(QuizElement::Section { elements: es, .. }) = elements.pop() else {
213                impossible!()
214            };
215            elements = es;
216        }
217        Ok(Quiz {
218            title: self.title().map(ToString::to_string),
219            answer_classes,
220            elements,
221            css: css.0,
222            solutions,
223        })
224    }
225}