1pub mod document;
2pub mod dossier_configuration;
3
4
5use std::{collections::HashSet, path::PathBuf, time::Instant};
6use document::chapter::heading::Heading;
7use document::Document;
8use getset::{Getters, MutGetters, Setters};
9use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator};
10use thiserror::Error;
11use crate::{codex::Codex, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_error::CompilationError, compilation_outcome::CompilationOutcome}, load::{LoadConfiguration, LoadConfigurationOverLay, LoadError}, output_format::OutputFormat, resource::ResourceError};
12
13use self::dossier_configuration::DossierConfiguration;
14use super::{bibliography::Bibliography, table_of_contents::TableOfContents};
15use serde::Serialize;
16
17
18pub const ASSETS_DIR: &str = "assets";
19pub const IMAGES_DIR: &str = "images";
20pub const DOCUMENTS_DIR: &str = "documents";
21pub const STYLES_DIR: &str = "styles";
22
23
24#[derive(Error, Debug)]
25pub enum DossierError {
26 #[error("dossier loading failed: '{0}'")]
27 Load(#[from] ResourceError)
28}
29
30
31#[derive(Debug, Getters, MutGetters, Setters, Serialize)]
33pub struct Dossier {
34
35 #[getset(get = "pub", set = "pub")]
36 configuration: DossierConfiguration,
37
38 #[getset(get = "pub", set = "pub", get_mut = "pub")]
39 documents: Vec<Document>,
40}
41
42impl Dossier {
43
44 pub fn new(configuration: DossierConfiguration, documents: Vec<Document>) -> Self {
45
46 Self {
47 configuration,
48 documents,
49 }
50 }
51
52 pub fn name(&self) -> &String {
53 self.configuration.name()
54 }
55
56 pub fn replace_document(&mut self, document_name: &str, new_document: Document) {
58 let index = self.documents.iter().position(|document| document.name().eq(document_name));
59
60 if let Some(index) = index {
61 self.documents[index] = new_document;
62 }
63 }
64
65 pub fn load_dossier_from_path_buf(path_buf: &PathBuf, codex: &Codex, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Dossier, LoadError> {
67 let dossier_configuration = DossierConfiguration::try_from(path_buf)?;
68
69 Self::load_dossier_from_dossier_configuration(&dossier_configuration, codex, configuration, configuration_overlay.clone())
70 }
71
72 pub fn load_dossier_from_path_buf_only_documents(path_buf: &PathBuf, only_documents: &HashSet<String>, codex: &Codex, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Dossier, LoadError> {
74 let mut dossier_configuration = DossierConfiguration::try_from(path_buf)?;
75
76 let d: Vec<String> = dossier_configuration.raw_documents_paths().iter()
77 .filter(|item| {
78
79 let file_name = PathBuf::from(*item).file_name().unwrap().to_string_lossy().to_string();
80
81 only_documents.contains(file_name.as_str())
82 })
83 .map(|item| item.clone())
84 .collect();
85
86 dossier_configuration.set_raw_documents_paths(d);
87
88 let mut configuration_overlay = configuration_overlay.clone();
89
90 configuration_overlay.set_dossier_name(Some(dossier_configuration.name().clone()));
91
92 Self::load_dossier_from_dossier_configuration(&dossier_configuration, codex, configuration, configuration_overlay)
93 }
94
95 pub fn load_dossier_from_dossier_configuration(dossier_configuration: &DossierConfiguration, codex: &Codex, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Dossier, LoadError> {
97
98 if dossier_configuration.documents_paths().is_empty() {
100 return Err(LoadError::ResourceError(ResourceError::InvalidResourceVerbose("there are no documents".to_string())))
101 }
102
103 if dossier_configuration.name().is_empty() {
105 return Err(LoadError::ResourceError(ResourceError::InvalidResourceVerbose("there is no name".to_string())))
106 }
107
108 if configuration.parallelization() {
109
110 let mut documents_res: Vec<Result<Document, LoadError>> = Vec::new();
111
112 dossier_configuration.documents_paths().par_iter()
113 .map(|document_path| {
114 Document::load_document_from_path(&PathBuf::from(document_path), codex, configuration, configuration_overlay.clone())
115 }).collect_into_vec(&mut documents_res);
116
117 let error = documents_res.par_iter().find_any(|result| result.is_err());
118
119 if let Some(Err(err)) = error.as_ref() {
121 return Err(err.clone())
122 }
123
124 let documents = documents_res.into_iter().map(|d| d.unwrap()).collect();
125
126 return Ok(Dossier::new(dossier_configuration.clone(), documents))
127
128
129 } else {
130
131 let mut documents: Vec<Document> = Vec::new();
132
133 for document_path in dossier_configuration.documents_paths() {
134
135 let document = Document::load_document_from_path(&PathBuf::from(document_path), codex, configuration, configuration_overlay.clone())?;
136
137 documents.push(document)
138 }
139
140 return Ok(Dossier::new(dossier_configuration.clone(), documents))
141 }
142 }
143}
144
145
146impl Compilable for Dossier {
147 fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, mut compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
148
149 log::info!("compile dossier {} with ({} documents, parallelization: {})", self.name(), self.documents().len(), compilation_configuration.parallelization());
150
151 compilation_configuration_overlay.set_dossier_name(Some(self.name().clone()));
152
153 let fast_draft = compilation_configuration.fast_draft();
154
155 let mut documents_outcomes: Vec<CompilationOutcome> = Vec::new();
156
157 if compilation_configuration.parallelization() {
158
159 let compile_only_documents = compilation_configuration_overlay.compile_only_documents();
160
161 let documents_results: Vec<Result<CompilationOutcome, CompilationError>> = self.documents_mut().par_iter_mut()
162 .filter(|document| {
163 if fast_draft {
164
165 if let Some(subset) = compile_only_documents {
166
167 let skip = !subset.contains(document.name());
168
169 if skip {
170 log::info!("document {} compilation is skipped", document.name());
171 }
172
173 return !skip;
174 }
175 }
176
177 true
178 })
179 .map(|document| {
180
181 let now = Instant::now();
182
183 let res = document.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone());
184
185 log::info!("document '{}' compiled in {} ms", document.name(), now.elapsed().as_millis());
186
187 res
188 })
189 .collect();
190
191 let mut errors: Vec<CompilationError> = Vec::new();
192
193 for result in documents_results {
194 match result {
195 Ok(outcome) => documents_outcomes.push(outcome),
196 Err(err) => errors.push(err),
197 }
198 }
199
200 if !errors.is_empty() {
201 return Err(CompilationError::BucketOfErrors(errors))
202 }
203
204
205 } else {
206
207 let compile_only_documents = compilation_configuration_overlay.compile_only_documents();
208
209 let documents_to_compile = self.documents_mut().iter_mut()
210 .filter(|document| {
211
212 if fast_draft {
213
214 if let Some(subset) = compile_only_documents {
215
216 let skip = !subset.contains(document.name());
217
218 if skip {
219 log::info!("document {} compilation is skipped", document.name());
220 }
221
222 return !skip;
223 }
224 }
225
226 true
227 });
228
229 for document in documents_to_compile {let now = Instant::now();
230
231 let outcome = document.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?;
232
233 log::info!("document '{}' compiled in {} ms", document.name(), now.elapsed().as_millis());
234
235 documents_outcomes.push(outcome);
236
237 }
238 }
239
240 let mut compiled_toc: Option<CompilationOutcome> = None;
241 let mut compiled_bib: Option<CompilationOutcome> = None;
242
243 if self.configuration().table_of_contents_configuration().include_in_output() {
244
245 log::info!("dossier table of contents will be included in output");
246
247 let mut headings: Vec<Heading> = Vec::new();
248
249 for document in self.documents() {
250 for chapter in document.content().chapters() {
251 headings.push(chapter.header().heading().clone());
252 }
253 }
254
255 let mut table_of_contents = TableOfContents::new(
256 self.configuration().table_of_contents_configuration().title().clone(),
257 self.configuration().table_of_contents_configuration().page_numbers(),
258 self.configuration().table_of_contents_configuration().plain(),
259 self.configuration().table_of_contents_configuration().maximum_heading_level(),
260 headings
261 );
262
263 compiled_toc = Some(table_of_contents.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
264 }
265
266 if self.configuration().bibliography().include_in_output() {
267 let mut bibliography = Bibliography::new(
268 self.configuration().bibliography().title().clone(),
269 self.configuration().bibliography().records().clone()
270 );
271
272 compiled_bib = Some(bibliography.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
273 }
274
275 Ok(CompilationOutcome::from(codex.assembler().assemble_dossier(&documents_outcomes, compiled_toc.as_ref(), compiled_bib.as_ref(), &self.configuration, compilation_configuration_overlay.assembler_configuration())?))
276 }
277}
278
279
280