flams_ftml/
lib.rs

1//#![feature(string_from_utf8_lossy_owned)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//mod parser;
5
6use flams_math_archives::{
7    artifacts::{ContentResult, FileOrString, FtmlFile, FtmlString},
8    backend::{AnyBackend, LocalBackend},
9    build_target,
10    formats::{BuildResult, BuildSpec},
11    source_format, Archive, LocallyBuilt, MathArchive,
12};
13pub use ftml5ever::FtmlResult;
14use ftml_uris::{DocumentUri, UriWithArchive, UriWithPath};
15
16source_format! { FTML {
17    name:"ftml",
18    description:"Flexiformal HTML",
19    targets:&[FTML_IMPORT.id(),FTML_CONTENT.id()],
20    file_extensions: &["html","html","xhtml"],
21    dependencies: |_| Vec::new()
22}}
23
24build_target! { FTML_IMPORT {
25    name:"import-ftml",
26    description:"imports existent FTML",
27    run: import
28}}
29
30build_target! { FTML_CONTENT {
31    name:"ftml-content",
32    description:"extracts content from FTML",
33    run: extract
34}}
35
36#[allow(clippy::needless_pass_by_value)]
37fn import(spec: BuildSpec) -> BuildResult {
38    match spec.source {
39        either::Either::Right(s) => BuildResult {
40            log: FileOrString::Str("ok".to_string().into_boxed_str()),
41            result: Ok(Some(
42                Box::new(FtmlString(s.to_string().into_boxed_str())) as _
43            )),
44        },
45        either::Either::Left(p) => BuildResult {
46            log: FileOrString::Str("ok".to_string().into_boxed_str()),
47            result: Ok(Some(Box::new(FtmlFile(p.to_path_buf())) as _)),
48        },
49    }
50}
51
52#[deprecated(note = "uses local archives only")]
53#[allow(clippy::too_many_lines)]
54#[allow(clippy::needless_pass_by_value)]
55fn extract(spec: BuildSpec) -> BuildResult {
56    let html: Result<String, _> = spec.backend.with_archive(spec.uri.archive_id(), |a| {
57        let Some(Archive::Local(a)) = a else {
58            return Err(BuildResult::err());
59        };
60        let path = a
61            .out_path_of(
62                spec.uri.path(),
63                &spec.uri.name,
64                Some(spec.rel_path),
65                spec.uri.language,
66            )
67            .join(FTML.name);
68        std::fs::read_to_string(path).map_err(|e| {
69            let mut err = BuildResult::err();
70            err.log = FileOrString::Str(e.to_string().into_boxed_str());
71            err
72        })
73    });
74    let html = match html {
75        Err(e) => return e,
76        Ok(h) => h,
77    };
78    let uri = spec.uri.clone();
79    let (lg, r) = flams_system::span_capture(|| build_ftml(spec.backend, &html, uri));
80    match r {
81        Err(e) => {
82            let mut err = BuildResult::err();
83            err.log = FileOrString::Str(format!("{lg}\n{e}").into_boxed_str());
84            err
85        }
86        Ok(FtmlResult {
87            ftml,
88            css,
89            errors,
90            doc,
91            body,
92            inner_offset,
93        }) => {
94            let has_errored = !errors.is_empty();
95            BuildResult {
96                log: FileOrString::Str(format!("{lg}\n{errors:?}").into_boxed_str()),
97                result: if has_errored {
98                    Err(Vec::new())
99                } else {
100                    Ok(Some(Box::new(ContentResult {
101                        document: doc.document,
102                        modules: doc.modules,
103                        data: doc.data,
104                        body,
105                        inner_offset,
106                        css,
107                        ftml,
108                        triples: doc.triples,
109                    })))
110                },
111            }
112        }
113    }
114
115    /*match build_ftml(backend, &html.0, uri, task.rel_path()) {
116        Err(e) => BuildResult {
117            log: Either::Left(e),
118            result: Err(Vec::new()),
119        },
120        Ok((r, s)) => BuildResult {
121            log: Either::Left(s),
122            result: Ok(BuildResultArtifact::Data(Box::new(r))),
123        },
124    }*/
125}
126
127/// # Errors
128pub fn build_ftml(
129    backend: &AnyBackend,
130    html: &str,
131    uri: DocumentUri,
132) -> Result<FtmlResult, String> {
133    static CSS_SUBSTS: [(&str, &str); 1] = [(
134        "https://raw.githack.com/FlexiFormal/RusTeX/main/rustex/src/resources/rustex.css",
135        "srv:/rustex.css",
136    )];
137    ftml5ever::run(
138        html,
139        |src| {
140            let path = std::path::Path::new(src);
141            if let Some(s) =
142                backend.archive_of(path, |a, rp| format!("srv:/img?a={}&rp={}", a.id(), rp))
143            {
144                return Some(s);
145            }
146            let kpsewhich = &*tex_engine::engine::filesystem::kpathsea::KPATHSEA;
147            let last = src.rsplit_once('/').map_or(src, |(_, p)| p);
148            kpsewhich.which(last).map_or_else(
149                || Some(format!("srv:/img?file={src}")),
150                |file| {
151                    if file == path {
152                        Some(format!("srv:/img?kpse={last}"))
153                    } else {
154                        None
155                    }
156                },
157            )
158        },
159        |css| {
160            CSS_SUBSTS.iter().find_map(|(old, new)| {
161                if css == *old {
162                    Some((*new).to_string().into_boxed_str())
163                } else {
164                    None
165                }
166            })
167        },
168        uri,
169        true,
170    )
171}