cdoc_parser/code_ast/
types.rs

1//! Types for exercise definitions.
2
3use linked_hash_map::LinkedHashMap;
4use serde::{Deserialize, Serialize};
5
6use cowstr::CowStr;
7use std::io::{BufWriter, Write};
8
9pub trait Output {
10    fn write_string(&self, solution: bool) -> String;
11}
12
13/// Represents a line of source code. Can either be markup (descriptions of the exercise) or
14/// code (regular source code).
15#[derive(Clone, Debug, Serialize, Deserialize)]
16pub enum Content {
17    Markup(CowStr),
18    Src(CowStr),
19}
20
21/// An exercise element with a placeholder and a solution
22#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
23pub struct Solution {
24    pub placeholder: Option<CowStr>,
25    pub solution: CowStr,
26}
27
28/// Top-level structure. A code file is split into these types.
29#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
30#[serde(rename = "value", untagged)]
31pub enum CodeElem {
32    Solution(Solution),
33    Src(String),
34}
35
36#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq)]
37pub struct CodeContent {
38    pub blocks: Vec<CodeElem>,
39    pub meta: LinkedHashMap<CowStr, CowStr>,
40    pub hash: u64,
41}
42
43impl CodeContent {
44    pub fn to_string(&self, with_solution: bool) -> anyhow::Result<String> {
45        let mut buf = BufWriter::new(Vec::new());
46        for block in &self.blocks {
47            match block {
48                CodeElem::Solution(s) => {
49                    if with_solution {
50                        buf.write_all(s.solution.as_bytes())?;
51                    } else {
52                        s.placeholder
53                            .as_ref()
54                            .map(|p| buf.write(p.as_bytes()))
55                            .transpose()?;
56                    }
57                }
58                CodeElem::Src(s) => {
59                    buf.write_all(s.as_bytes())?;
60                }
61            }
62        }
63
64        Ok(String::from_utf8(buf.into_inner()?)?)
65    }
66}