flams_system/
formats.rs

1use std::{io::Read, path::Path};
2
3use flams_ontology::{
4    content::modules::OpenModule, narration::documents::UncheckedDocument, DocumentRange, Unchecked,
5};
6pub use flams_utils::global;
7use flams_utils::CSS;
8
9use crate::{
10    backend::AnyBackend,
11    building::{BuildArtifact, BuildResult, BuildTask},
12};
13
14global! {SER SourceFormat {name,
15  description: &'static str,
16  file_exts: &'static [&'static str],
17  targets: &'static [BuildTargetId],
18  dependencies:fn(&AnyBackend,task:&BuildTask)
19}}
20
21#[macro_export]
22macro_rules! source_format {
23  ($name:ident[$($ft:literal),+] [$($tgt:expr)=>*] @ $desc:literal = $deps:expr) => {
24    $crate::formats::global!{NEW {$crate::formats}SourceFormat; $name [
25      $desc,&[$($ft),+],&[$($tgt),*],$deps
26    ]
27    }
28  }
29}
30
31global! {SER BuildTarget {name,
32  description: &'static str,
33  dependencies: &'static [BuildArtifactTypeId],
34  yields: &'static [BuildArtifactTypeId],
35  run: fn(&AnyBackend,task:&BuildTask) -> BuildResult
36}}
37
38#[macro_export]
39macro_rules! build_target {
40  ($name:ident [$($dep:expr),*] => [$($yield:expr),*] @ $desc:literal = $f:expr) => {
41    $crate::formats::global!{NEW {$crate::formats}BuildTarget; $name [
42      $desc,&[$($dep),*],&[$($yield),*],$f
43    ]
44    }
45  }
46}
47
48build_target!(check [UNCHECKED_OMDOC] => [OMDOC]
49  @ "Resolve OMDoc dependencies and type check"
50  = |_,_| { BuildResult::empty() }
51);
52
53global! {SER BuildArtifactType {name,
54  description: &'static str
55}}
56
57#[macro_export]
58macro_rules! build_result {
59    ($name:ident @ $desc:literal) => {
60        $crate::formats::global! {NEW {$crate::formats}BuildArtifactType; $name [$desc]
61        }
62    };
63}
64
65build_result!(unchecked_omdoc @ "OMDoc ready to be checked");
66build_result!(omdoc @ "OMDoc document and modules; fully checked");
67build_result!(pdf @ "PDF document (semantically opaque)");
68
69pub struct OMDocResult {
70    pub document: UncheckedDocument,
71    pub html: HTMLData,
72    pub modules: Vec<OpenModule<Unchecked>>,
73}
74
75#[derive(Debug)]
76pub struct HTMLData {
77    pub html: String,
78    pub css: Vec<CSS>,
79    pub body: DocumentRange,
80    pub inner_offset: usize,
81    pub refs: Vec<u8>,
82}
83
84impl OMDocResult {
85    pub(crate) fn load_html_body(path: &Path, full: bool) -> Option<(Vec<CSS>, String)> {
86        use std::io::{Seek, SeekFrom};
87        let file = std::fs::File::open(path).ok()?;
88        let mut file = std::io::BufReader::new(file);
89        let mut buf = [0; 20];
90        file.read_exact(&mut buf).ok()?;
91        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
92        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
93        let body_start = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
94        let body_start = 20 + css_offset + css_len + body_start;
95        let body_len = u32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]);
96        let inner_offset = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
97        let mut css = vec![0; css_len as usize];
98        file.seek(SeekFrom::Start(u64::from(css_offset + 20)))
99            .ok()?;
100        file.read_exact(&mut css).ok()?;
101        let css = bincode::serde::decode_from_slice(&css, bincode::config::standard())
102            .ok()?
103            .0;
104        if full {
105            file.seek(SeekFrom::Start(u64::from(body_start))).ok()?;
106            let mut html = vec![0; body_len as usize];
107            file.read_exact(&mut html).ok()?;
108            String::from_utf8(html).ok().map(|html| (css, html))
109        } else {
110            let inner_offset_real = body_start + inner_offset;
111            file.seek(SeekFrom::Start(u64::from(inner_offset_real)))
112                .ok()?;
113            let mut html = vec![0; ((body_len - inner_offset) as usize) - "</body>".len()];
114            file.read_exact(&mut html).ok()?;
115            String::from_utf8(html).ok().map(|html| (css, html))
116        }
117    }
118
119    #[cfg(feature = "tokio")]
120    pub(crate) async fn load_html_body_async(
121        path: std::path::PathBuf,
122        full: bool,
123    ) -> Option<(Vec<CSS>, String)> {
124        use std::io::SeekFrom;
125        use tokio::io::{AsyncReadExt, AsyncSeekExt};
126        //println!("loading {path:?}");
127        let file = tokio::fs::File::open(path).await.ok()?;
128        let mut file = tokio::io::BufReader::new(file);
129        let mut buf = [0; 20];
130        file.read_exact(&mut buf).await.ok()?;
131        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
132        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
133        let body_start = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
134        let body_start = 20 + css_offset + css_len + body_start;
135        let body_len = u32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]);
136        let inner_offset = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
137        //println!("css_offset: {}, css_len: {}, body_start: {}, body_len: {}, inner_offset: {}",css_offset,css_len,body_start,body_len,inner_offset);
138        let mut css = vec![0; css_len as usize];
139        file.seek(SeekFrom::Start(u64::from(css_offset + 20)))
140            .await
141            .ok()?;
142        file.read_exact(&mut css).await.ok()?;
143        let css = bincode::serde::decode_from_slice(&css, bincode::config::standard())
144            .ok()?
145            .0;
146        if full {
147            file.seek(SeekFrom::Start(u64::from(body_start)))
148                .await
149                .ok()?;
150            let mut html = vec![0; body_len as usize];
151            file.read_exact(&mut html).await.ok()?;
152            String::from_utf8(html).ok().map(|html| (css, html))
153        } else {
154            let inner_offset_real = body_start + inner_offset;
155            //println!("Seek");
156            file.seek(SeekFrom::Start(u64::from(inner_offset_real)))
157                .await
158                .ok()?;
159            let mut html = vec![0; ((body_len - inner_offset) as usize) - "</body>".len()];
160            //println!("Reading {}",html.len());
161            file.read_exact(&mut html).await.ok()?;
162            //println!("Decode");
163            String::from_utf8(html).ok().map(|html| (css, html))
164        }
165    }
166
167    #[cfg(feature = "tokio")]
168    pub(crate) async fn load_html_full_async(path: std::path::PathBuf) -> Option<String> {
169        use std::io::SeekFrom;
170        use tokio::io::{AsyncReadExt, AsyncSeekExt};
171        //println!("loading {path:?}");
172        let file = tokio::fs::File::open(path).await.ok()?;
173        let mut file = tokio::io::BufReader::new(file);
174        let mut buf = [0; 20];
175        file.read_exact(&mut buf).await.ok()?;
176        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
177        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
178        let html_start = 20 + css_offset + css_len;
179
180        file.seek(SeekFrom::Start(u64::from(html_start)))
181            .await
182            .ok()?;
183        let mut ret = String::new();
184        file.read_to_string(&mut ret).await.ok()?;
185        Some(ret)
186    }
187
188    pub(crate) fn load_html_full(path: std::path::PathBuf) -> Option<String> {
189        use std::io::{Seek, SeekFrom};
190        let file = std::fs::File::open(path).ok()?;
191        let mut file = std::io::BufReader::new(file);
192        let mut buf = [0; 20];
193        file.read_exact(&mut buf).ok()?;
194        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
195        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
196        let html_start = 20 + css_offset + css_len;
197
198        file.seek(SeekFrom::Start(u64::from(html_start))).ok()?;
199        let mut ret = String::new();
200        file.read_to_string(&mut ret).ok()?;
201        Some(ret)
202    }
203
204    pub(crate) fn load_html_fragment(
205        path: &Path,
206        range: DocumentRange,
207    ) -> Option<(Vec<CSS>, String)> {
208        use std::io::{Seek, SeekFrom};
209        let file = std::fs::File::open(path).ok()?;
210        let mut file = std::io::BufReader::new(file);
211        let mut buf = [0; 20];
212        file.read_exact(&mut buf).ok()?;
213        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
214        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
215        //let body_start = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
216        let html_start = 20 + css_offset + css_len;
217        //let body_len = u32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]);
218        //let inner_offset = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
219        let mut css = vec![0; css_len as usize];
220        file.seek(SeekFrom::Start(u64::from(css_offset + 20)))
221            .ok()?;
222        file.read_exact(&mut css).ok()?;
223        let css = bincode::serde::decode_from_slice(&css, bincode::config::standard())
224            .ok()?
225            .0;
226        file.seek(SeekFrom::Start(u64::from(html_start) + range.start as u64))
227            .ok()?;
228        let mut html = vec![0; range.end - range.start];
229        file.read_exact(&mut html).ok()?;
230        String::from_utf8(html).ok().map(|html| (css, html))
231    }
232
233    #[allow(clippy::cast_possible_wrap)]
234    pub(crate) fn load_reference<T: flams_ontology::Resourcable>(
235        path: &Path,
236        range: DocumentRange,
237    ) -> eyre::Result<T> {
238        use std::io::{Seek, SeekFrom};
239        let file = std::fs::File::open(path)?;
240        let mut file = std::io::BufReader::new(file);
241        let mut buf = [0; 20];
242        file.read_exact(&mut buf)?;
243        file.seek(SeekFrom::Current(range.start as i64))?;
244        let len = range.end - range.start;
245        let mut bytes = vec![0; len];
246        file.read_exact(&mut bytes)?;
247        let r = bincode::serde::decode_from_slice(&bytes, bincode::config::standard())?;
248        Ok(r.0)
249    }
250
251    #[cfg(feature = "tokio")]
252    pub(crate) async fn load_html_fragment_async(
253        path: std::path::PathBuf,
254        range: DocumentRange,
255    ) -> Option<(Vec<CSS>, String)> {
256        use std::io::SeekFrom;
257        use tokio::io::{AsyncReadExt, AsyncSeekExt};
258        let file = tokio::fs::File::open(path).await.ok()?;
259        let mut file = tokio::io::BufReader::new(file);
260        let mut buf = [0; 20];
261        file.read_exact(&mut buf).await.ok()?;
262        let css_offset = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
263        let css_len = u32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]);
264        let html_start = 20 + css_offset + css_len;
265        //let body_start = u32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]);
266        //let body_start = 20 + css_offset + css_len + body_start;
267        //let body_len = u32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]);
268        //let inner_offset = u32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]);
269        let mut css = vec![0; css_len as usize];
270        file.seek(SeekFrom::Start(u64::from(css_offset + 20)))
271            .await
272            .ok()?;
273        file.read_exact(&mut css).await.ok()?;
274        let css = bincode::serde::decode_from_slice(&css, bincode::config::standard())
275            .ok()?
276            .0;
277        file.seek(SeekFrom::Start(u64::from(html_start) + range.start as u64))
278            .await
279            .ok()?;
280        let mut html = vec![0; range.end - range.start];
281        file.read_exact(&mut html).await.ok()?;
282        String::from_utf8(html).ok().map(|html| (css, html))
283    }
284
285    #[allow(clippy::cast_possible_truncation)]
286    #[allow(clippy::cognitive_complexity)]
287    pub(crate) fn write(&self, path: &Path) {
288        use std::io::Write;
289        macro_rules! err {
290            ($e:expr) => {
291                match $e {
292                    Ok(r) => r,
293                    Err(e) => {
294                        tracing::error!("Failed to save {}: {}", path.display(), e);
295                        return;
296                    }
297                }
298            };
299        }
300        macro_rules! er {
301            ($e:expr) => {
302                if let Err(e) = $e {
303                    tracing::error!("Failed to save {}: {}", path.display(), e);
304                    return;
305                }
306            };
307        }
308        let file = err!(std::fs::File::create(path));
309        let mut buf = std::io::BufWriter::new(file);
310        let Self {
311            html:
312                HTMLData {
313                    css,
314                    body,
315                    inner_offset,
316                    refs,
317                    html,
318                },
319            ..
320        } = self;
321        let css_offset = (refs.len() as u32).to_be_bytes();
322        let css = err!(bincode::serde::encode_to_vec(
323            css,
324            bincode::config::standard()
325        ));
326        let css_len = (css.len() as u32).to_be_bytes();
327        let body_start = (body.start as u32).to_be_bytes();
328        let body_len = ((body.end - body.start) as u32).to_be_bytes();
329        let inner_offset = (*inner_offset as u32).to_be_bytes();
330        er!(buf.write_all(&css_offset));
331        er!(buf.write_all(&css_len));
332        er!(buf.write_all(&body_start));
333        er!(buf.write_all(&body_len));
334        er!(buf.write_all(&inner_offset));
335        er!(buf.write_all(refs));
336        er!(buf.write_all(&css));
337        er!(buf.write_all(html.as_bytes()));
338        er!(buf.flush());
339    }
340}
341
342impl BuildArtifact for OMDocResult {
343    #[inline]
344    fn get_type(&self) -> BuildArtifactTypeId {
345        UNCHECKED_OMDOC
346    }
347    #[inline]
348    fn get_type_id() -> BuildArtifactTypeId
349    where
350        Self: Sized,
351    {
352        UNCHECKED_OMDOC
353    }
354    fn write(&self, _path: &std::path::Path) -> Result<(), std::io::Error> {
355        unreachable!()
356    }
357    fn load(_p: &std::path::Path) -> Result<Self, std::io::Error>
358    where
359        Self: Sized,
360    {
361        unreachable!()
362    }
363    #[inline]
364    fn as_any(&self) -> &dyn std::any::Any {
365        self
366    }
367}
368
369global! {FLAMSExtension {name,
370  on_start: fn()
371}}
372
373#[macro_export]
374macro_rules! flams_extension {
375    ($name:ident = $f:expr) => {
376        $crate::formats::global! {NEW {$crate::formats} FLAMSExtension; $name [$f]}
377    };
378}
379
380impl FLAMSExtension {
381    pub fn initialize() {
382        for e in Self::all() {
383            let f = move || {
384                tracing::info_span!("Initializing", extension = e.name())
385                    .in_scope(|| (e.on_start())());
386            };
387            #[cfg(feature = "tokio")]
388            flams_utils::background(f);
389            #[cfg(not(feature = "tokio"))]
390            f();
391        }
392    }
393}
394
395#[derive(Copy, Clone, Debug, PartialEq, Eq)]
396pub enum FormatOrTargets<'a> {
397    Format(SourceFormatId),
398    Targets(&'a [BuildTargetId]),
399}