Skip to main content

acdc_parser/
options.rs

1use std::{
2    borrow::Cow,
3    path::{Path, PathBuf},
4};
5
6pub use crate::safe_mode::SafeMode;
7
8use crate::{
9    AttributeValue, DocumentAttributes, Error,
10    document_attribute::{InputKind, initialize_configuration, initialize_intrinsics},
11    model::RawAttributes,
12};
13
14#[derive(Debug, Clone, Default)]
15#[non_exhaustive]
16pub struct Options<'a> {
17    pub safe_mode: SafeMode,
18    pub timings: bool,
19    pub(crate) document_attributes: DocumentAttributes<'a>,
20    /// Directory used to resolve relative includes from the entry input.
21    ///
22    /// String and reader input default to the current working directory. File
23    /// input normally uses the entry file's parent, unless this value overrides
24    /// it. In Safe and Server modes this directory is also the local-include
25    /// boundary.
26    pub base_dir: Option<PathBuf>,
27    /// Reject a manpage title that does not use the `name(volume)` form.
28    ///
29    /// This does not promote all warnings to errors. Use
30    /// [`crate::ParseResult::source_recovery`] to detect recovered content.
31    pub strict: bool,
32    /// Enable Setext-style (underlined) header parsing.
33    ///
34    /// When enabled, headers can use the legacy two-line syntax:
35    /// ```text
36    /// Document Title
37    /// ==============
38    /// ```
39    #[cfg(feature = "setext")]
40    pub setext: bool,
41}
42
43impl<'a> Options<'a> {
44    /// Create a new `OptionsBuilder` for fluent configuration.
45    ///
46    /// # Example
47    ///
48    /// ```
49    /// use acdc_parser::{Options, SafeMode};
50    ///
51    /// let options = Options::builder()
52    ///     .with_safe_mode(SafeMode::Safe)
53    ///     .with_timings()
54    ///     .with_attribute("toc", "left")
55    ///     .build()?;
56    /// # Ok::<(), acdc_parser::Error>(())
57    /// ```
58    #[must_use]
59    pub fn builder() -> OptionsBuilder<'a> {
60        OptionsBuilder::default()
61    }
62
63    /// Create a new `Options` with default settings.
64    ///
65    /// Equivalent to `Options::default()`.
66    #[must_use]
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Create options from application-supplied attributes.
72    ///
73    /// # Errors
74    ///
75    /// Returns a structured error for an invalid attribute value.
76    pub fn with_attributes<N, V>(
77        attributes: impl IntoIterator<Item = (N, V)>,
78    ) -> Result<Self, Error>
79    where
80        N: Into<Cow<'a, str>>,
81        V: Into<AttributeValue<'a>>,
82    {
83        Self::builder().with_attributes(attributes).build()
84    }
85
86    /// Return the validated attributes configured for parsing.
87    #[must_use]
88    pub const fn document_attributes(&self) -> &DocumentAttributes<'a> {
89        &self.document_attributes
90    }
91
92    /// Consume the options and return their validated attribute collection.
93    #[must_use]
94    pub fn into_document_attributes(self) -> DocumentAttributes<'a> {
95        self.document_attributes
96    }
97
98    /// Replace the attribute snapshot with one from an already parsed document.
99    ///
100    /// This does not reclassify or revalidate assignments. Use the builder when
101    /// supplied values should become new application overrides.
102    #[must_use]
103    pub fn with_document_attributes(mut self, attributes: DocumentAttributes<'a>) -> Self {
104        self.document_attributes = attributes;
105        self
106    }
107
108    /// Reopen configuration while preserving application overrides and defaults.
109    #[must_use]
110    pub fn into_builder(self) -> OptionsBuilder<'a> {
111        let (attributes, defaults) = self.document_attributes.into_configuration();
112        OptionsBuilder {
113            attributes,
114            defaults,
115            safe_mode: self.safe_mode,
116            timings: self.timings,
117            base_dir: self.base_dir,
118            strict: self.strict,
119            #[cfg(feature = "setext")]
120            setext: self.setext,
121        }
122    }
123
124    pub(crate) fn prepare_for_parse(mut self, input_kind: InputKind<'_>) -> Self {
125        initialize_intrinsics(&mut self.document_attributes, self.safe_mode, input_kind);
126        self
127    }
128
129    /// Consume the options, producing an independent `'static` copy.
130    #[must_use]
131    pub fn into_static(self) -> Options<'static> {
132        Options {
133            safe_mode: self.safe_mode,
134            timings: self.timings,
135            document_attributes: self.document_attributes.into_static(),
136            base_dir: self.base_dir,
137            strict: self.strict,
138            #[cfg(feature = "setext")]
139            setext: self.setext,
140        }
141    }
142}
143
144/// Builder for `Options` that provides an API for configuration.
145///
146/// Create an `OptionsBuilder` using `Options::builder()`.
147///
148/// # Example
149///
150/// ```
151/// use acdc_parser::{Options, SafeMode};
152///
153/// let options = Options::builder()
154///     .with_safe_mode(SafeMode::Safe)
155///     .with_timings()
156///     .with_attribute("toc", "left")
157///     .with_attribute("sectnums", true)
158///     .build()?;
159/// # Ok::<(), acdc_parser::Error>(())
160/// ```
161#[derive(Debug, Clone, Default)]
162#[non_exhaustive]
163pub struct OptionsBuilder<'a> {
164    safe_mode: SafeMode,
165    timings: bool,
166    attributes: RawAttributes<'a>,
167    defaults: RawAttributes<'a>,
168    base_dir: Option<PathBuf>,
169    strict: bool,
170    #[cfg(feature = "setext")]
171    setext: bool,
172}
173
174impl<'a> OptionsBuilder<'a> {
175    /// Set the safe mode for parsing.
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use acdc_parser::{Options, SafeMode};
181    ///
182    /// let options = Options::builder()
183    ///     .with_safe_mode(SafeMode::Safe)
184    ///     .build()?;
185    /// # Ok::<(), acdc_parser::Error>(())
186    /// ```
187    #[must_use]
188    pub fn with_safe_mode(mut self, safe_mode: SafeMode) -> Self {
189        self.safe_mode = safe_mode;
190        self
191    }
192
193    /// Enable timing information during parsing.
194    ///
195    /// # Example
196    ///
197    /// ```
198    /// use acdc_parser::Options;
199    ///
200    /// let options = Options::builder()
201    ///     .with_timings()
202    ///     .build()?;
203    /// # Ok::<(), acdc_parser::Error>(())
204    /// ```
205    #[must_use]
206    pub fn with_timings(mut self) -> Self {
207        self.timings = true;
208        self
209    }
210
211    /// Set the directory used to resolve relative includes from the entry input.
212    ///
213    /// For file input this overrides the entry file's parent directory. Nested
214    /// includes remain relative to the file that contains them.
215    #[must_use]
216    pub fn with_base_dir(mut self, base_dir: impl AsRef<Path>) -> Self {
217        self.base_dir = Some(base_dir.as_ref().to_path_buf());
218        self
219    }
220
221    /// Reject manpage titles that do not use the `name(volume)` form.
222    ///
223    /// Other recoverable problems still produce warnings.
224    ///
225    /// # Example
226    ///
227    /// ```
228    /// use acdc_parser::Options;
229    ///
230    /// let options = Options::builder()
231    ///     .with_strict()
232    ///     .build()?;
233    /// # Ok::<(), acdc_parser::Error>(())
234    /// ```
235    #[must_use]
236    pub fn with_strict(mut self) -> Self {
237        self.strict = true;
238        self
239    }
240
241    /// Supply an application attribute.
242    ///
243    /// Application values take precedence over document entries, subject to the
244    /// attribute's standard assignment rules.
245    ///
246    /// This is a convenience method that accepts various types for the value:
247    /// - `&str` becomes `AttributeValue::String`
248    /// - `bool` becomes `AttributeValue::Bool`
249    /// - `()` becomes `AttributeValue::None`
250    ///
251    /// # Example
252    ///
253    /// ```
254    /// use acdc_parser::Options;
255    ///
256    /// let options = Options::builder()
257    ///     .with_attribute("toc", "left")
258    ///     .with_attribute("sectnums", true)
259    ///     .build()?;
260    /// # Ok::<(), acdc_parser::Error>(())
261    /// ```
262    #[must_use]
263    pub fn with_attribute(
264        mut self,
265        name: impl Into<Cow<'a, str>>,
266        value: impl Into<AttributeValue<'a>>,
267    ) -> Self {
268        self.attributes.insert(name.into(), value.into());
269        self
270    }
271
272    /// Replace the application-supplied attributes.
273    #[must_use]
274    pub fn with_attributes<N, V>(mut self, attributes: impl IntoIterator<Item = (N, V)>) -> Self
275    where
276        N: Into<Cow<'a, str>>,
277        V: Into<AttributeValue<'a>>,
278    {
279        self.attributes = attributes
280            .into_iter()
281            .map(|(name, value)| (name.into(), value.into()))
282            .collect();
283        self
284    }
285
286    /// Replace defaults for attributes not supplied by the application.
287    ///
288    /// Document entries can replace these defaults when the attribute permits it.
289    /// Intrinsic backend values retain their normal precedence.
290    /// Backend, base backend, file type, and document type defaults also determine
291    /// their convenience attributes when the options are built.
292    #[must_use]
293    pub fn with_defaults<N, V>(mut self, defaults: impl IntoIterator<Item = (N, V)>) -> Self
294    where
295        N: Into<Cow<'a, str>>,
296        V: Into<AttributeValue<'a>>,
297    {
298        self.defaults = defaults
299            .into_iter()
300            .map(|(name, value)| (name.into(), value.into()))
301            .collect();
302        self
303    }
304
305    /// Supply a default without changing an application override.
306    #[must_use]
307    pub fn with_default_attribute(
308        mut self,
309        name: impl Into<Cow<'a, str>>,
310        value: impl Into<AttributeValue<'a>>,
311    ) -> Self {
312        self.defaults.insert(name.into(), value.into());
313        self
314    }
315
316    /// Inspect a supplied override or default before validation.
317    #[must_use]
318    pub fn attribute(&self, name: &str) -> Option<&AttributeValue<'a>> {
319        self.attributes
320            .get(name)
321            .or_else(|| self.defaults.get(name))
322    }
323
324    /// Enable Setext-style (underlined) header parsing.
325    ///
326    /// When enabled, headers can use the legacy two-line syntax where
327    /// the title is underlined with `=`, `-`, `~`, `^`, or `+` characters.
328    ///
329    /// # Example
330    ///
331    /// ```
332    /// use acdc_parser::Options;
333    ///
334    /// let options = Options::builder()
335    ///     .with_setext()
336    ///     .build()?;
337    /// # Ok::<(), acdc_parser::Error>(())
338    /// ```
339    #[cfg(feature = "setext")]
340    #[must_use]
341    pub fn with_setext(mut self) -> Self {
342        self.setext = true;
343        self
344    }
345
346    /// Build the `Options` from this builder.
347    ///
348    /// # Example
349    ///
350    /// ```
351    /// use acdc_parser::{Options, SafeMode};
352    ///
353    /// let options = Options::builder()
354    ///     .with_safe_mode(SafeMode::Safe)
355    ///     .build()?;
356    /// # Ok::<(), acdc_parser::Error>(())
357    /// ```
358    ///
359    /// # Errors
360    ///
361    /// Returns a structured error for an invalid attribute value.
362    pub fn build(self) -> Result<Options<'a>, Error> {
363        let document_attributes = initialize_configuration(self.attributes, self.defaults)?;
364        Ok(Options {
365            safe_mode: self.safe_mode,
366            timings: self.timings,
367            document_attributes,
368            base_dir: self.base_dir,
369            strict: self.strict,
370            #[cfg(feature = "setext")]
371            setext: self.setext,
372        })
373    }
374}