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) => {
105                if index == 0 {
106                    Some(item)
107                } else {
108                    None
109                }
110            }
111            Self::Constants(items) => items.get_mut(index),
112            Self::OverloadedFunctions(items) => items.get_mut(index),
113        }
114    }
115
116    pub fn iter_items(&self) -> ParseItemIter<'_> {
117        match self {
118            Self::Empty => ParseItemIter { next: None, other: None },
119            Self::Single(item) => ParseItemIter { next: Some(item), other: None },
120            Self::Constants(items) => ParseItemIter { next: None, other: Some(items.iter()) },
121            Self::OverloadedFunctions(items) => {
122                ParseItemIter { next: None, other: Some(items.iter()) }
123            }
124        }
125    }
126
127    pub fn iter_items_mut(&mut self) -> ParseItemIterMut<'_> {
128        match self {
129            Self::Empty => ParseItemIterMut { next: None, other: None },
130            Self::Single(item) => ParseItemIterMut { next: Some(item), other: None },
131            Self::Constants(items) => {
132                ParseItemIterMut { next: None, other: Some(items.iter_mut()) }
133            }
134            Self::OverloadedFunctions(items) => {
135                ParseItemIterMut { next: None, other: Some(items.iter_mut()) }
136            }
137        }
138    }
139}
140
141#[derive(Debug)]
142pub struct ParseItemIter<'a> {
143    next: Option<&'a ParseItem>,
144    other: Option<std::slice::Iter<'a, ParseItem>>,
145}
146
147impl<'a> Iterator for ParseItemIter<'a> {
148    type Item = &'a ParseItem;
149
150    fn next(&mut self) -> Option<Self::Item> {
151        if let Some(next) = self.next.take() {
152            return Some(next)
153        }
154        if let Some(other) = self.other.as_mut() {
155            return other.next()
156        }
157
158        None
159    }
160}
161
162#[derive(Debug)]
163pub struct ParseItemIterMut<'a> {
164    next: Option<&'a mut ParseItem>,
165    other: Option<IterMut<'a, ParseItem>>,
166}
167
168impl<'a> Iterator for ParseItemIterMut<'a> {
169    type Item = &'a mut ParseItem;
170
171    fn next(&mut self) -> Option<Self::Item> {
172        if let Some(next) = self.next.take() {
173            return Some(next)
174        }
175        if let Some(other) = self.other.as_mut() {
176            return other.next()
177        }
178
179        None
180    }
181}
182
183/// Read the preprocessor output variant from document context.
184/// Returns [None] if there is no output.
185macro_rules! read_context {
186    ($doc: expr, $id: expr, $variant: ident) => {
187        $doc.get_from_context($id).and_then(|out| match out {
188            // Only a single variant is matched. Otherwise the code is invalid.
189            PreprocessorOutput::$variant(inner) => Some(inner),
190            _ => None,
191        })
192    };
193}
194
195pub(crate) use read_context;