1use std::path::Path;
2
3#[cfg(feature = "rdf")]
4use ftml_uris::FtmlUri;
5use ftml_uris::{ArchiveId, DocumentUri, ModuleUri, UriPath};
6
7use crate::{
8 artifacts::{Artifact, FileOrString},
9 backend::AnyBackend,
10};
11
12pub mod __reexport {
13 pub use inventory::*;
14}
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17pub enum FormatOrTargets<'a> {
18 Format(SourceFormatId),
19 Targets(&'a [BuildTargetId]),
20}
21
22#[derive(Copy, Clone, Debug)]
23pub struct SourceFormat {
24 pub name: &'static str,
25 pub description: &'static str,
26 pub targets: &'static [BuildTargetId],
27 pub file_extensions: &'static [&'static str],
28 pub dependencies: fn(BuildSpec) -> Vec<(BuildTargetId, TaskDependency)>,
29}
30impl SourceFormat {
31 #[inline]
32 #[must_use]
33 pub const fn id(&'static self) -> SourceFormatId {
34 SourceFormatId(self)
35 }
36}
37inventory::collect!(SourceFormat);
38#[macro_export]
39macro_rules! source_format {
40 ($i:ident { $($t:tt)* }) => {
41 pub static $i : $crate::formats::SourceFormat = $crate::formats::SourceFormat { $($t)* };
42 $crate::formats::__reexport::submit!{ $i }
43 };
44}
45
46impl SourceFormat {
47 #[inline]
48 pub fn all() -> impl Iterator<Item = SourceFormatId> {
49 inventory::iter.into_iter().map(SourceFormatId)
50 }
51 #[must_use]
52 pub fn get(name: &str) -> Option<SourceFormatId> {
53 Self::all().find(|e| e.name == name)
54 }
55}
56
57pub struct BuildResult {
58 pub log: FileOrString,
59 pub result: Result<Option<Box<dyn Artifact>>, Vec<TaskDependency>>,
60}
61impl BuildResult {
62 #[must_use]
63 pub fn err() -> Self {
64 Self {
65 log: FileOrString::Str(String::new().into_boxed_str()),
66 result: Err(Vec::new()),
67 }
68 }
69}
70impl Default for BuildResult {
71 fn default() -> Self {
72 Self {
73 log: FileOrString::Str(String::new().into_boxed_str()),
74 result: Ok(None),
75 }
76 }
77}
78
79pub struct BuildSpec<'a> {
80 pub uri: &'a DocumentUri,
81 pub source: either::Either<&'a Path, &'a str>,
82 pub backend: &'a AnyBackend,
83 pub rel_path: &'a UriPath,
84}
85
86#[derive(Debug, Clone, Hash, PartialEq, Eq)]
87pub struct TaskRef {
88 pub archive: ArchiveId,
89 pub rel_path: UriPath,
90 pub target: BuildTargetId,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum TaskDependency {
95 Physical { task: TaskRef, strict: bool },
96 Logical { uri: ModuleUri, strict: bool },
97}
98
99#[derive(Copy, Clone, Debug)]
100pub struct BuildTarget {
101 pub name: &'static str,
102 pub description: &'static str,
103 pub run: fn(BuildSpec<'_>) -> BuildResult,
106}
107impl BuildTarget {
108 #[inline]
109 #[must_use]
110 pub const fn id(&'static self) -> BuildTargetId {
111 BuildTargetId(self)
112 }
113}
114inventory::collect!(BuildTarget);
115#[macro_export]
116macro_rules! build_target {
117 ($i:ident { $($t:tt)* }) => {
118 pub static $i : $crate::formats::BuildTarget = $crate::formats::BuildTarget { $($t)* };
119 $crate::formats::__reexport::submit!{ $i }
120 };
121}
122
123impl BuildTarget {
124 #[inline]
125 pub fn all() -> impl Iterator<Item = BuildTargetId> {
126 inventory::iter.into_iter().map(BuildTargetId)
127 }
128 #[must_use]
129 pub fn get(name: &str) -> Option<BuildTargetId> {
130 Self::all().find(|e| e.name == name)
131 }
132}
133
134#[derive(Copy, Clone)]
135pub struct SourceFormatId(&'static SourceFormat);
136impl std::ops::Deref for SourceFormatId {
137 type Target = &'static SourceFormat;
138 #[inline]
139 fn deref(&self) -> &Self::Target {
140 &self.0
141 }
142}
143impl PartialEq for SourceFormatId {
144 fn eq(&self, other: &Self) -> bool {
145 std::ptr::addr_eq(self.0, other.0)
146 }
147}
148impl Eq for SourceFormatId {}
149impl std::hash::Hash for SourceFormatId {
150 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
151 self.0.name.hash(state);
152 }
153}
154impl std::fmt::Debug for SourceFormatId {
155 #[inline]
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 self.0.name.fmt(f)
158 }
159}
160
161impl std::fmt::Display for SourceFormatId {
162 #[inline]
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 self.0.name.fmt(f)
165 }
166}
167
168impl ::serde::Serialize for SourceFormatId {
169 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
170 where
171 S: ::serde::Serializer,
172 {
173 serializer.serialize_str(self.0.name)
174 }
175}
176
177impl<'de> ::serde::Deserialize<'de> for SourceFormatId {
178 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
179 where
180 D: ::serde::Deserializer<'de>,
181 {
182 let s = String::deserialize(deserializer)?;
183 SourceFormat::get(&s).map_or_else(
184 || Err(::serde::de::Error::custom("Unknown source format")),
185 Ok,
186 )
187 }
188}
189
190#[derive(Copy, Clone)]
191pub struct BuildTargetId(&'static BuildTarget);
192impl std::ops::Deref for BuildTargetId {
193 type Target = &'static BuildTarget;
194 #[inline]
195 fn deref(&self) -> &Self::Target {
196 &self.0
197 }
198}
199impl PartialEq for BuildTargetId {
200 fn eq(&self, other: &Self) -> bool {
201 std::ptr::addr_eq(self.0, other.0)
202 }
203}
204impl Eq for BuildTargetId {}
205impl std::hash::Hash for BuildTargetId {
206 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
207 self.0.name.hash(state);
208 }
209}
210impl std::fmt::Debug for BuildTargetId {
211 #[inline]
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 self.0.name.fmt(f)
214 }
215}
216
217impl std::fmt::Display for BuildTargetId {
218 #[inline]
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 self.0.name.fmt(f)
221 }
222}
223
224impl ::serde::Serialize for BuildTargetId {
225 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
226 where
227 S: ::serde::Serializer,
228 {
229 serializer.serialize_str(self.0.name)
230 }
231}
232
233impl<'de> ::serde::Deserialize<'de> for BuildTargetId {
234 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
235 where
236 D: ::serde::Deserializer<'de>,
237 {
238 let s = String::deserialize(deserializer)?;
239 BuildTarget::get(&s).map_or_else(
240 || Err(::serde::de::Error::custom("Unknown build target")),
241 Ok,
242 )
243 }
244}
245
246#[cfg(feature = "rdf")]
247source_format! { RDF {
248 name:"rdf",
249 description:"Import .ttl-RDF-files into the triple store",
250 targets:&[RDF_IMPORT.id()],
251 file_extensions: &["ttl"],
252 dependencies: |_| Vec::new()
253}}
254
255#[cfg(feature = "rdf")]
256build_target! { RDF_IMPORT {
257 name:"import-rdf",
258 description:"imports existent .ttl file",
259 run: |s| {
260 let v = match s.source {
261 either::Left(path) => {
262 let file = match std::fs::File::open(path) {
263 Err(e) => return BuildResult {
264 log: FileOrString::Str(format!("Error reading {}: {e}",path.display()).into_boxed_str()),
265 result: Err(Vec::new())
266 },
267 Ok(f) => f
268 };
269 let buf = std::io::BufReader::new(file);
270 let reader = oxigraph::io::RdfParser::from_format(oxigraph::io::RdfFormat::Turtle)
271 .for_reader(buf);
272 match reader.map(|q| q.map(|q| ulo::rdf_types::Triple {
273 subject:q.subject,
274 predicate:q.predicate,
275 object:q.object
276 })).collect::<Result<Vec<_>,_>>() {
277 Ok(v) => v,
278 Err(e) => return BuildResult {
279 log: FileOrString::Str(format!("Error reading {}: {e}",path.display()).into_boxed_str()),
280 result: Err(Vec::new())
281 }
282 }
283 }
284 either::Right(txt) => {
285 let reader = oxigraph::io::RdfParser::from_format(oxigraph::io::RdfFormat::Turtle)
286 .for_reader(txt.as_bytes());
287 match reader.map(|q| q.map(|q| ulo::rdf_types::Triple {
288 subject:q.subject,
289 predicate:q.predicate,
290 object:q.object
291 })).collect::<Result<Vec<_>,_>>() {
292 Ok(v) => v,
293 Err(e) => return BuildResult {
294 log: FileOrString::Str(format!("Error reading ttl: {e}").into_boxed_str()),
295 result: Err(Vec::new())
296 }
297 }
298 }
299 };
300 BuildResult {
301 log: FileOrString::Str(String::new().into_boxed_str()),
302 result: Ok(Some(Box::new(v)))
303 }
304 }
305}}