Skip to main content

forge_doc/
document.rs

1use crate::{DocBuilder, ParseItem, PreprocessorId, PreprocessorOutput};
2use alloy_primitives::map::HashMap;
3use std::{
4    path::{Path, PathBuf},
5    slice::IterMut,
6    sync::Mutex,
7};
8
9/// The wrapper around the [ParseItem] containing additional
10/// information the original item and extra context for outputting it.
11#[derive(Debug)]
12pub struct Document {
13    /// The underlying parsed items.
14    pub content: DocumentContent,
15    /// The original item path.
16    pub item_path: PathBuf,
17    /// The original item file content.
18    pub item_content: String,
19    /// The target path where the document will be written.
20    pub target_path: PathBuf,
21    /// The document display identity.
22    pub identity: String,
23    /// The preprocessors results.
24    context: Mutex<HashMap<PreprocessorId, PreprocessorOutput>>,
25    /// Whether the document is from external library.
26    pub from_library: bool,
27    /// The target directory for the doc output.
28    pub out_target_dir: PathBuf,
29}
30
31impl Document {
32    /// Create new instance of [Document].
33    pub fn new(
34        item_path: PathBuf,
35        target_path: PathBuf,
36        from_library: bool,
37        out_target_dir: PathBuf,
38    ) -> Self {
39        Self {
40            item_path,
41            target_path,
42            from_library,
43            item_content: String::default(),
44            identity: String::default(),
45            content: DocumentContent::Empty,
46            out_target_dir,
47            context: Mutex::new(HashMap::default()),
48        }
49    }
50
51    /// Set content and identity on the [Document].
52    #[must_use]
53    pub fn with_content(mut self, content: DocumentContent, identity: String) -> Self {
54        self.content = content;
55        self.identity = identity;
56        self
57    }
58
59    /// Add a preprocessor result to inner document context.
60    pub fn add_context(&self, id: PreprocessorId, output: PreprocessorOutput) {
61        let mut context = self.context.lock().expect("failed to lock context");
62        context.insert(id, output);
63    }
64
65    /// Read preprocessor result from context
66    pub fn get_from_context(&self, id: PreprocessorId) -> Option<PreprocessorOutput> {
67        let context = self.context.lock().expect("failed to lock context");
68        context.get(&id).cloned()
69    }
70
71    fn try_relative_output_path(&self) -> Option<&Path> {
72        self.target_path.strip_prefix(&self.out_target_dir).ok()?.strip_prefix(DocBuilder::SRC).ok()
73    }
74
75    /// Returns the relative path of the document output.
76    pub fn relative_output_path(&self) -> &Path {
77        self.try_relative_output_path().unwrap_or(self.target_path.as_path())
78    }
79}
80
81/// The content of the document.
82#[derive(Debug)]
83#[allow(clippy::large_enum_variant)]
84pub enum DocumentContent {
85    Empty,
86    Single(ParseItem),
87    Constants(Vec<ParseItem>),
88    OverloadedFunctions(Vec<ParseItem>),
89}
90
91impl DocumentContent {
92    pub(crate) fn len(&self) -> usize {
93        match self {
94            Self::Empty => 0,
95            Self::Single(_) => 1,
96            Self::Constants(items) => items.len(),
97            Self::OverloadedFunctions(items) => items.len(),
98        }
99    }
100
101    pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> {
102        match self {
103            Self::Empty => None,
104            Self::Single(item) => (index == 0).then_some(item),
105            Self::Constants(items) => items.get_mut(index),
106            Self::OverloadedFunctions(items) => items.get_mut(index),
107        }
108    }
109
110    pub fn iter_items(&self) -> ParseItemIter<'_> {
111        match self {
112            Self::Empty => ParseItemIter { next: None, other: None },
113            Self::Single(item) => ParseItemIter { next: Some(item), other: None },
114            Self::Constants(items) => ParseItemIter { next: None, other: Some(items.iter()) },
115            Self::OverloadedFunctions(items) => {
116                ParseItemIter { next: None, other: Some(items.iter()) }
117            }
118        }
119    }
120
121    pub fn iter_items_mut(&mut self) -> ParseItemIterMut<'_> {
122        match self {
123            Self::Empty => ParseItemIterMut { next: None, other: None },
124            Self::Single(item) => ParseItemIterMut { next: Some(item), other: None },
125            Self::Constants(items) => {
126                ParseItemIterMut { next: None, other: Some(items.iter_mut()) }
127            }
128            Self::OverloadedFunctions(items) => {
129                ParseItemIterMut { next: None, other: Some(items.iter_mut()) }
130            }
131        }
132    }
133}
134
135#[derive(Debug)]
136pub struct ParseItemIter<'a> {
137    next: Option<&'a ParseItem>,
138    other: Option<std::slice::Iter<'a, ParseItem>>,
139}
140
141impl<'a> Iterator for ParseItemIter<'a> {
142    type Item = &'a ParseItem;
143
144    fn next(&mut self) -> Option<Self::Item> {
145        if let Some(next) = self.next.take() {
146            return Some(next);
147        }
148        if let Some(other) = self.other.as_mut() {
149            return other.next();
150        }
151
152        None
153    }
154}
155
156#[derive(Debug)]
157pub struct ParseItemIterMut<'a> {
158    next: Option<&'a mut ParseItem>,
159    other: Option<IterMut<'a, ParseItem>>,
160}
161
162impl<'a> Iterator for ParseItemIterMut<'a> {
163    type Item = &'a mut ParseItem;
164
165    fn next(&mut self) -> Option<Self::Item> {
166        if let Some(next) = self.next.take() {
167            return Some(next);
168        }
169        if let Some(other) = self.other.as_mut() {
170            return other.next();
171        }
172
173        None
174    }
175}
176
177/// Read the preprocessor output variant from document context.
178/// Returns [None] if there is no output.
179macro_rules! read_context {
180    ($doc:expr, $id:expr, $variant:ident) => {
181        $doc.get_from_context($id).and_then(|out| match out {
182            // Only a single variant is matched. Otherwise the code is invalid.
183            PreprocessorOutput::$variant(inner) => Some(inner),
184            _ => None,
185        })
186    };
187}
188
189pub(crate) use read_context;