1use std::fmt::{Display, Formatter};
2
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6use super::parse::parse_document;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct AaakMeta {
10 pub wing: String,
11 pub room: String,
12 pub date: String,
13 pub source: String,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct AaakHeader {
18 pub version: u8,
19 pub wing: String,
20 pub room: String,
21 pub date: String,
22 pub source: String,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct Zettel {
27 pub id: usize,
28 pub entities: Vec<String>,
29 pub topics: Vec<String>,
30 pub quote: String,
31 pub weight: u8,
32 pub emotions: Vec<String>,
33 pub flags: Vec<String>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct Tunnel {
38 pub left: usize,
39 pub right: usize,
40 pub label: String,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct ArcLine {
45 pub emotions: Vec<String>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub enum AaakLine {
50 Zettel(Zettel),
51 Tunnel(Tunnel),
52 Arc(ArcLine),
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct AaakDocument {
57 pub header: AaakHeader,
58 pub body: Vec<AaakLine>,
59 pub zettels: Vec<Zettel>,
60}
61
62impl AaakDocument {
63 pub fn parse(input: &str) -> Result<Self, ParseError> {
64 parse_document(input)
65 }
66
67 pub fn zettel_lines(&self) -> Vec<Zettel> {
68 if !self.body.is_empty() {
69 return self
70 .body
71 .iter()
72 .filter_map(|line| match line {
73 AaakLine::Zettel(zettel) => Some(zettel.clone()),
74 AaakLine::Tunnel(_) | AaakLine::Arc(_) => None,
75 })
76 .collect();
77 }
78
79 self.zettels.clone()
80 }
81}
82
83impl Display for AaakDocument {
84 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85 writeln!(
86 f,
87 "V{}|{}|{}|{}|{}",
88 self.header.version,
89 self.header.wing,
90 self.header.room,
91 self.header.date,
92 self.header.source
93 )?;
94
95 for (index, line) in self.body_lines().iter().enumerate() {
96 if index > 0 {
97 writeln!(f)?;
98 }
99 match line {
100 AaakLine::Zettel(zettel) => write!(
101 f,
102 "{}:{}|{}|\"{}\"|{}|{}|{}",
103 zettel.id,
104 zettel.entities.join("+"),
105 zettel.topics.join("_"),
106 zettel.quote.replace('"', "'"),
107 "★".repeat(usize::from(zettel.weight.max(1))),
108 zettel.emotions.join("+"),
109 zettel.flags.join("+"),
110 )?,
111 AaakLine::Tunnel(tunnel) => {
112 write!(f, "T:{}<->{}|{}", tunnel.left, tunnel.right, tunnel.label)?
113 }
114 AaakLine::Arc(arc) => write!(f, "ARC:{}", arc.emotions.join("->"))?,
115 }
116 }
117
118 Ok(())
119 }
120}
121
122impl AaakDocument {
123 fn body_lines(&self) -> Vec<AaakLine> {
124 if !self.body.is_empty() {
125 return self.body.clone();
126 }
127
128 self.zettels.iter().cloned().map(AaakLine::Zettel).collect()
129 }
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub struct EncodeReport {
134 pub topics_truncated: usize,
135 pub key_sentence_truncated: bool,
136 pub coverage: f32,
137 pub lost_assertions: Vec<String>,
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
141pub struct EncodeOutput {
142 pub document: AaakDocument,
143 pub report: EncodeReport,
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub struct RoundtripReport {
148 pub preserved: Vec<String>,
149 pub lost: Vec<String>,
150 pub coverage: f32,
151}
152
153#[derive(Debug, Error, PartialEq, Eq)]
154pub enum ParseError {
155 #[error("missing AAAK header")]
156 MissingHeader,
157 #[error("invalid AAAK header")]
158 InvalidHeader,
159 #[error("invalid version marker")]
160 InvalidVersion,
161 #[error("invalid zettel line: {0}")]
162 InvalidZettel(String),
163}