nmd_core/dossier/
document.rs

1pub mod chapter;
2
3
4use std::path::PathBuf;
5use std::time::Instant;
6
7pub use chapter::Chapter;
8use getset::{Getters, MutGetters, Setters};
9use rayon::slice::ParallelSliceMut;
10use serde::Serialize;
11use thiserror::Error;
12use crate::codex::Codex;
13use crate::compilation::compilation_configuration::compilation_configuration_overlay::CompilationConfigurationOverLay;
14use crate::compilation::compilation_configuration::CompilationConfiguration;
15use crate::compilation::compilation_error::CompilationError;
16use crate::compilation::compilable::Compilable;
17use crate::compilation::compilation_outcome::CompilationOutcome;
18use crate::content_bundle::ContentBundle;
19use crate::load::{LoadConfiguration, LoadConfigurationOverLay, LoadError};
20use crate::load_block::LoadBlock;
21use crate::output_format::OutputFormat;
22use crate::resource::disk_resource::DiskResource;
23use crate::resource::{Resource, ResourceError};
24use self::chapter::paragraph::ParagraphError;
25
26
27#[derive(Error, Debug)]
28pub enum DocumentError {
29    #[error(transparent)]
30    Load(#[from] ResourceError),
31
32    #[error(transparent)]
33    Compilation(#[from] CompilationError),
34
35    #[error(transparent)]
36    ParagraphError(#[from] ParagraphError),
37}
38
39#[derive(Debug, Getters, MutGetters, Setters, Serialize)]
40pub struct Document {
41
42    #[getset(get = "pub", set = "pub")]
43    name: String,
44
45    #[getset(get = "pub", get_mut = "pub", set = "pub")]
46    content: ContentBundle
47}
48
49
50impl Document {
51
52    pub fn new(name: String, content: ContentBundle) -> Self {
53        
54        Self {
55            name,
56            content,
57        }
58    }
59
60    pub fn load_document_from_str(document_name: &str, content: &str, codex: &Codex, configuration: &LoadConfiguration, mut configuration_overlay: LoadConfigurationOverLay) -> Result<Document, LoadError> {
61        
62        let now = Instant::now();
63
64        log::info!("loading document '{}' from its content...", document_name);
65
66        configuration_overlay.set_document_name(Some(document_name.to_string()));
67        
68        let mut blocks: Vec<LoadBlock> = LoadBlock::load_from_str(content, codex, configuration, configuration_overlay.clone())?;
69
70        blocks.par_sort_by(|a, b| a.start().cmp(&b.start()));
71
72        let document = Self::create_document_by_blocks(document_name, blocks)?;
73
74        log::info!("document '{}' loaded in {} ms (preamble: {}, chapters: {})", document_name, now.elapsed().as_millis(), document.content().preamble().is_empty(), document.content().chapters().len());
75
76        Ok(document)      
77    }
78
79    /// Load a document from its path (`PathBuf`). The document have to exist.
80    pub fn load_document_from_path(path_buf: &PathBuf, codex: &Codex, configuration: &LoadConfiguration, configuration_overlay: LoadConfigurationOverLay) -> Result<Document, LoadError> {
81
82        if !path_buf.exists() {
83            return Err(LoadError::ResourceError(ResourceError::InvalidResourceVerbose(format!("{} not exists", path_buf.to_string_lossy())))) 
84        }
85
86        let now = Instant::now();
87
88        let resource = DiskResource::try_from(path_buf.clone())?;
89
90        log::info!("document in {:?} read in {} ms", path_buf, now.elapsed().as_millis());
91
92        let content = resource.content()?;
93
94        let document_name = resource.name();
95
96        match Self::load_document_from_str(document_name, &content, codex, configuration, configuration_overlay.clone()) {
97            Ok(document) => {
98                return Ok(document)
99            },
100            Err(err) => return Err(LoadError::ElaborationError(err.to_string()))
101        }
102    }
103
104    fn create_document_by_blocks(document_name: &str, blocks: Vec<LoadBlock>) -> Result<Document, LoadError> {
105
106        log::debug!("create document '{}' using blocks: {:#?}", document_name, blocks);
107
108        let content = ContentBundle::from(blocks);
109
110        let document = Document::new(document_name.to_string(), content);
111
112        log::debug!("document '{}' created: {:#?}", document_name, document);
113
114        Ok(document)
115    }
116}
117
118
119impl Compilable for Document {
120    fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, mut compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
121
122        compilation_configuration_overlay.set_document_name(Some(self.name().clone()));
123
124        self.content.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())
125    }
126}
127
128
129
130#[cfg(test)]
131mod test {
132    use crate::{codex::Codex, dossier::document::Document, load::{LoadConfiguration, LoadConfigurationOverLay}};
133
134    #[test]
135    fn chapters_from_str() {
136
137        let codex = Codex::of_html();
138
139        let content: String = 
140r#"
141preamble
142
143# title 1a
144
145paragraph 1a
146
147## title 2a
148
149paragraph 2a
150
151# title 1b
152
153paragraph 1b
154"#.trim().to_string();
155
156        let document = Document::load_document_from_str("test", &content, &codex, &LoadConfiguration::default(), LoadConfigurationOverLay::default()).unwrap();
157
158        assert_eq!(document.content().preamble().len(), 1);
159
160        assert_eq!(document.content().chapters().len(), 3);
161
162    }
163}