1#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use 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 }
126
127pub 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}