flams_ontology/
archive_json.rs

1use crate::{
2    file_states::FileStateSummary,
3    uris::{ArchiveId, ArchiveURI, ArchiveURITrait, DocumentURI},
4};
5
6#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
7#[serde(untagged)]
8pub enum ArchiveDatum {
9    Document(DocumentKind),
10    Institution(Institution),
11}
12
13#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
14#[serde(tag = "type")]
15pub enum DocumentKind {
16    #[serde(rename = "library")]
17    Library {
18        title: Box<str>,
19        teaser: Option<Box<str>>,
20        thumbnail: Option<Box<str>>,
21    },
22    #[serde(rename = "book")]
23    Book {
24        title: Box<str>,
25        authors: Vec<Person>,
26        file: Box<str>,
27        thumbnail: Option<Box<str>>,
28        teaser: Option<Box<str>>,
29    },
30    #[serde(rename = "paper")]
31    Paper {
32        title: Box<str>,
33        authors: Vec<Person>,
34        file: Box<str>,
35        thumbnail: Option<Box<str>>,
36        teaser: Option<Box<str>>,
37        venue: Option<Box<str>>,
38        venue_url: Option<Box<str>>,
39    },
40    #[serde(rename = "course")]
41    Course {
42        title: Box<str>,
43        landing: Box<str>,
44        acronym: Option<Box<str>>,
45        instructors: Vec<Person>,
46        institution: Box<str>,
47        notes: Box<str>,
48        slides: Option<Box<str>>,
49        thumbnail: Option<Box<str>>,
50        #[serde(default)]
51        quizzes: bool,
52        #[serde(default)]
53        homeworks: bool,
54        #[serde(default)]
55        instances: Vec<PreInstance>,
56        teaser: Option<Box<str>>,
57    },
58    #[serde(rename = "self-study")]
59    SelfStudy {
60        title: Box<str>,
61        landing: Box<str>,
62        acronym: Option<Box<str>>,
63        notes: Box<str>,
64        slides: Option<Box<str>>,
65        teaser: Option<Box<str>>,
66        thumbnail: Option<Box<str>>,
67    },
68}
69impl DocumentKind {
70    #[inline]
71    #[must_use]
72    pub fn teaser(&self) -> Option<&str> {
73        match self {
74            Self::Library { teaser, .. }
75            | Self::Book { teaser, .. }
76            | Self::Paper { teaser, .. }
77            | Self::Course { teaser, .. }
78            | Self::SelfStudy { teaser, .. } => teaser.as_deref(),
79        }
80    }
81    pub fn set_teaser(&mut self, new_teaser: Box<str>) {
82        match self {
83            Self::Library { teaser, .. }
84            | Self::Book { teaser, .. }
85            | Self::Paper { teaser, .. }
86            | Self::Course { teaser, .. }
87            | Self::SelfStudy { teaser, .. } => *teaser = Some(new_teaser),
88        }
89    }
90}
91
92#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
93#[serde(tag = "type")]
94#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
95#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
96pub enum Institution {
97    #[serde(rename = "university")]
98    University {
99        title: Box<str>,
100        place: Box<str>,
101        country: Box<str>,
102        url: Box<str>,
103        acronym: Box<str>,
104        logo: Box<str>,
105    },
106    #[serde(rename = "school")]
107    School {
108        title: Box<str>,
109        place: Box<str>,
110        country: Box<str>,
111        url: Box<str>,
112        acronym: Box<str>,
113        logo: Box<str>,
114    },
115}
116impl Institution {
117    #[inline]
118    #[must_use]
119    pub const fn acronym(&self) -> &str {
120        match self {
121            Self::University { acronym, .. } | Self::School { acronym, .. } => acronym,
122        }
123    }
124    #[inline]
125    #[must_use]
126    pub const fn url(&self) -> &str {
127        match self {
128            Self::University { url, .. } | Self::School { url, .. } => url,
129        }
130    }
131    #[inline]
132    #[must_use]
133    pub const fn title(&self) -> &str {
134        match self {
135            Self::University { title, .. } | Self::School { title, .. } => title,
136        }
137    }
138    #[inline]
139    #[must_use]
140    pub const fn logo(&self) -> &str {
141        match self {
142            Self::University { logo, .. } | Self::School { logo, .. } => logo,
143        }
144    }
145}
146impl PartialEq for Institution {
147    fn eq(&self, other: &Self) -> bool {
148        match (self, other) {
149            (Self::University { title: t1, .. }, Self::University { title: t2, .. })
150            | (Self::School { title: t1, .. }, Self::School { title: t2, .. }) => t1 == t2,
151            _ => false,
152        }
153    }
154}
155
156#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
157pub struct Person {
158    pub name: Box<str>,
159}
160
161#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
162pub struct PreInstance {
163    pub semester: Box<str>,
164    pub instructors: Option<Vec<Person>>,
165    #[serde(rename = "TAs")]
166    pub tas: Option<Vec<Person>>,
167    #[serde(rename = "leadTAs")]
168    pub lead_tas: Option<Vec<Person>>,
169}
170
171#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
172#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
173#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
174#[serde(tag = "type")]
175pub enum ArchiveIndex {
176    #[serde(rename = "library")]
177    Library {
178        archive: ArchiveId,
179        title: Box<str>,
180        #[serde(default)]
181        teaser: Option<Box<str>>,
182        #[serde(default)]
183        thumbnail: Option<Box<str>>,
184    },
185    #[serde(rename = "book")]
186    Book {
187        title: Box<str>,
188        authors: Box<[Box<str>]>,
189        file: DocumentURI,
190        #[serde(default)]
191        teaser: Option<Box<str>>,
192        #[serde(default)]
193        thumbnail: Option<Box<str>>,
194    },
195    #[serde(rename = "paper")]
196    Paper {
197        title: Box<str>,
198        authors: Box<[Box<str>]>,
199        file: DocumentURI,
200        #[serde(default)]
201        thumbnail: Option<Box<str>>,
202        #[serde(default)]
203        teaser: Option<Box<str>>,
204        #[serde(default)]
205        venue: Option<Box<str>>,
206        #[serde(default)]
207        venue_url: Option<Box<str>>,
208    },
209    #[serde(rename = "course")]
210    Course {
211        title: Box<str>,
212        landing: DocumentURI,
213        acronym: Option<Box<str>>,
214        instructors: Box<[Box<str>]>,
215        institution: Box<str>,
216        instances: Box<[Instance]>,
217        notes: DocumentURI,
218        #[serde(default)]
219        slides: Option<DocumentURI>,
220        #[serde(default)]
221        thumbnail: Option<Box<str>>,
222        #[serde(default)]
223        quizzes: bool,
224        #[serde(default)]
225        homeworks: bool,
226        #[serde(default)]
227        teaser: Option<Box<str>>,
228    },
229    #[serde(rename = "self-study")]
230    SelfStudy {
231        title: Box<str>,
232        landing: DocumentURI,
233        notes: DocumentURI,
234        #[serde(default)]
235        acronym: Option<Box<str>>,
236        #[serde(default)]
237        slides: Option<DocumentURI>,
238        #[serde(default)]
239        thumbnail: Option<Box<str>>,
240        #[serde(default)]
241        teaser: Option<Box<str>>,
242    },
243}
244impl ArchiveIndex {
245    #[inline]
246    #[must_use]
247    pub fn teaser(&self) -> Option<&str> {
248        match self {
249            Self::Library { teaser, .. }
250            | Self::Book { teaser, .. }
251            | Self::Paper { teaser, .. }
252            | Self::Course { teaser, .. }
253            | Self::SelfStudy { teaser, .. } => teaser.as_deref(),
254        }
255    }
256    pub fn set_teaser(&mut self, new_teaser: Box<str>) {
257        match self {
258            Self::Library { teaser, .. }
259            | Self::Book { teaser, .. }
260            | Self::Paper { teaser, .. }
261            | Self::Course { teaser, .. }
262            | Self::SelfStudy { teaser, .. } => *teaser = Some(new_teaser),
263        }
264    }
265}
266impl Eq for ArchiveIndex {}
267impl PartialEq for ArchiveIndex {
268    fn eq(&self, other: &Self) -> bool {
269        match (self, other) {
270            (Self::Library { archive: a1, .. }, Self::Library { archive: a2, .. }) => a1 == a2,
271            (Self::Book { file: f1, .. }, Self::Book { file: f2, .. })
272            | (Self::Course { notes: f1, .. }, Self::Course { notes: f2, .. })
273            | (Self::Paper { file: f1, .. }, Self::Paper { file: f2, .. })
274            | (Self::SelfStudy { notes: f1, .. }, Self::SelfStudy { notes: f2, .. }) => f1 == f2,
275            _ => false,
276        }
277    }
278}
279impl ArchiveIndex {
280    pub fn from_kind(
281        d: DocumentKind,
282        a: &ArchiveURI,
283        images: impl FnMut(Box<str>) -> Box<str>,
284    ) -> eyre::Result<Self> {
285        Ok(match d {
286            DocumentKind::Library {
287                title,
288                teaser,
289                thumbnail,
290            } => Self::Library {
291                archive: a.archive_id().clone(),
292                title,
293                teaser,
294                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
295                    None
296                } else {
297                    thumbnail.map(images)
298                },
299            },
300            DocumentKind::Book {
301                title,
302                authors,
303                file,
304                teaser,
305                thumbnail,
306            } => Self::Book {
307                title,
308                teaser,
309                file: DocumentURI::from_archive_relpath(a.clone(), &file)?,
310                authors: authors.into_iter().map(|is| is.name).collect(),
311                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
312                    None
313                } else {
314                    thumbnail.map(images)
315                },
316            },
317            DocumentKind::Paper {
318                title,
319                authors,
320                file,
321                teaser,
322                thumbnail,
323                venue,
324                venue_url,
325            } => Self::Paper {
326                title,
327                teaser,
328                venue,
329                venue_url,
330                file: DocumentURI::from_archive_relpath(a.clone(), &file)?,
331                authors: authors.into_iter().map(|is| is.name).collect(),
332                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
333                    None
334                } else {
335                    thumbnail.map(images)
336                },
337            },
338            DocumentKind::Course {
339                title,
340                landing,
341                acronym,
342                instructors,
343                institution,
344                notes,
345                slides,
346                thumbnail,
347                quizzes,
348                homeworks,
349                instances,
350                teaser,
351            } => Self::Course {
352                title,
353                acronym,
354                institution,
355                quizzes,
356                homeworks,
357                teaser,
358                landing: DocumentURI::from_archive_relpath(a.clone(), &landing)?,
359                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
360                    None
361                } else {
362                    thumbnail.map(images)
363                },
364                notes: DocumentURI::from_archive_relpath(a.clone(), &notes)?,
365                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
366                    None
367                } else {
368                    slides
369                        .map(|s| DocumentURI::from_archive_relpath(a.clone(), &s))
370                        .transpose()?
371                },
372                instances: instances
373                    .into_iter()
374                    .map(|i| Instance {
375                        semester: i.semester,
376                        instructors: i
377                            .instructors
378                            .map(|is| is.into_iter().map(|i| i.name).collect()),
379                        tas: i.tas.map(|is| is.into_iter().map(|i| i.name).collect()),
380                        lead_tas: i
381                            .lead_tas
382                            .map(|is| is.into_iter().map(|i| i.name).collect()),
383                    })
384                    .collect(),
385                instructors: instructors.into_iter().map(|is| is.name).collect(),
386            },
387            DocumentKind::SelfStudy {
388                title,
389                landing,
390                acronym,
391                notes,
392                slides,
393                thumbnail,
394                teaser,
395            } => Self::SelfStudy {
396                title,
397                acronym,
398                teaser,
399                landing: DocumentURI::from_archive_relpath(a.clone(), &landing)?,
400                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
401                    None
402                } else {
403                    thumbnail.map(images)
404                },
405                notes: DocumentURI::from_archive_relpath(a.clone(), &notes)?,
406                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
407                    None
408                } else {
409                    slides
410                        .map(|s| DocumentURI::from_archive_relpath(a.clone(), &s))
411                        .transpose()?
412                },
413            },
414        })
415    }
416}
417
418#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
419#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
420#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
421pub struct Instance {
422    pub semester: Box<str>,
423    #[serde(default)]
424    pub instructors: Option<Box<[Box<str>]>>,
425    #[serde(rename = "TAs")]
426    #[serde(default)]
427    pub tas: Option<Box<[Box<str>]>>,
428    #[serde(rename = "leadTAs")]
429    #[serde(default)]
430    pub lead_tas: Option<Box<[Box<str>]>>,
431}
432
433#[cfg(unix)]
434#[test]
435fn test() {
436    use std::os::unix::ffi::OsStrExt;
437    tracing_subscriber::fmt().init();
438    let mathhubs: Vec<_> = std::env::var("MATHHUB")
439        .expect("No MathHub directory")
440        .split(',')
441        .map(|s| std::path::PathBuf::from(s.trim()))
442        .collect();
443
444    for m in mathhubs {
445        for entry in walkdir::WalkDir::new(m) {
446            let entry = entry.expect("Error reading directory");
447            if entry.file_type().is_file()
448                && entry
449                    .path()
450                    .extension()
451                    .is_some_and(|s| s.as_bytes() == b"json")
452                && entry
453                    .path()
454                    .file_stem()
455                    .is_some_and(|s| s.as_bytes() == b"archive")
456            {
457                tracing::info!("File: {}", entry.path().display());
458                let data = std::fs::read_to_string(entry.path()).expect("Error reading file");
459                let data: Vec<ArchiveDatum> =
460                    serde_json::from_str(&data).expect("Error parsing JSON");
461                for d in data {
462                    tracing::info!("{d:#?}");
463                }
464            }
465        }
466    }
467}
468
469#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
470#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
471#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
472pub struct ArchiveData {
473    pub id: ArchiveId,
474    #[serde(default)]
475    pub git: Option<String>,
476    #[serde(default)]
477    pub summary: Option<FileStateSummary>,
478}
479
480#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
481#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
482#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
483pub struct ArchiveGroupData {
484    pub id: ArchiveId,
485    #[serde(default)]
486    pub summary: Option<FileStateSummary>,
487}
488
489#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
490#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
491#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
492pub struct DirectoryData {
493    pub rel_path: String,
494    #[serde(default)]
495    pub summary: Option<FileStateSummary>,
496}
497
498#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
499#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
500#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
501pub struct FileData {
502    pub rel_path: String,
503    pub format: String, //pub summary:Option<FileStateSummary>,
504}