asciidoc_parser/document/
document.rs

1//! Describes the top-level document structure.
2
3use std::{marker::PhantomData, slice::Iter};
4
5use self_cell::self_cell;
6
7use crate::{
8    Parser, Span,
9    attributes::Attrlist,
10    blocks::{Block, ContentModel, IsBlock, parse_utils::parse_blocks_until},
11    document::Header,
12    strings::CowStr,
13    warnings::Warning,
14};
15
16/// A document represents the top-level block element in AsciiDoc. It consists
17/// of an optional document header and either a) one or more sections preceded
18/// by an optional preamble or b) a sequence of top-level blocks only.
19///
20/// The document can be configured using a document header. The header is not a
21/// block itself, but contributes metadata to the document, such as the document
22/// title and document attributes.
23///
24/// The `Document` structure is a self-contained package of the original content
25/// that was parsed and the data structures that describe that parsed content.
26/// The API functions on this struct can be used to understand the parse
27/// results.
28#[derive(Eq, PartialEq)]
29pub struct Document<'src> {
30    internal: Internal,
31    _phantom: PhantomData<&'src ()>,
32}
33
34/// Internal dependent struct containing the actual data members that reference
35/// the owned source.
36#[derive(Debug, Eq, PartialEq)]
37struct InternalDependent<'src> {
38    header: Header<'src>,
39    blocks: Vec<Block<'src>>,
40    source: Span<'src>,
41    warnings: Vec<Warning<'src>>,
42}
43
44self_cell! {
45    /// Internal implementation struct containing the actual data members.
46    struct Internal {
47        owner: String,
48        #[covariant]
49        dependent: InternalDependent,
50    }
51    impl {Debug, Eq, PartialEq}
52}
53
54impl<'src> Document<'src> {
55    pub(crate) fn parse(source: &str, parser: &mut Parser) -> Self {
56        let owned_source = source.to_string();
57
58        let internal = Internal::new(owned_source, |owned_src| {
59            let source = Span::new(owned_src);
60
61            let mi = Header::parse(source, parser);
62            let next = mi.item.after;
63
64            let header = mi.item.item;
65            let mut warnings = mi.warnings;
66
67            let mut maw_blocks = parse_blocks_until(next, |_| false, parser);
68
69            if !maw_blocks.warnings.is_empty() {
70                warnings.append(&mut maw_blocks.warnings);
71            }
72
73            InternalDependent {
74                header,
75                blocks: maw_blocks.item.item,
76                source: source.trim_trailing_whitespace(),
77                warnings,
78            }
79        });
80
81        Self {
82            internal,
83            _phantom: PhantomData,
84        }
85    }
86
87    /// Return the document header.
88    pub fn header(&self) -> &Header<'_> {
89        &self.internal.borrow_dependent().header
90    }
91
92    /// Return an iterator over any warnings found during parsing.
93    pub fn warnings(&self) -> Iter<'_, Warning<'_>> {
94        self.internal.borrow_dependent().warnings.iter()
95    }
96
97    /// Return a [`Span`] describing the entire document source.
98    pub fn span(&self) -> Span<'_> {
99        self.internal.borrow_dependent().source
100    }
101}
102
103impl<'src> IsBlock<'src> for Document<'src> {
104    fn content_model(&self) -> ContentModel {
105        ContentModel::Compound
106    }
107
108    fn raw_context(&self) -> CowStr<'src> {
109        "document".into()
110    }
111
112    fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
113        self.internal.borrow_dependent().blocks.iter()
114    }
115
116    fn title_source(&'src self) -> Option<Span<'src>> {
117        // Document title is reflected in the Header.
118        None
119    }
120
121    fn title(&self) -> Option<&str> {
122        // Document title is reflected in the Header.
123        None
124    }
125
126    fn anchor(&'src self) -> Option<Span<'src>> {
127        None
128    }
129
130    fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
131        // Document attributes are reflected in the Header.
132        None
133    }
134}
135
136impl std::fmt::Debug for Document<'_> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        let dependent = self.internal.borrow_dependent();
139        f.debug_struct("Document")
140            .field("header", &dependent.header)
141            .field("blocks", &dependent.blocks)
142            .field("source", &dependent.source)
143            .field("warnings", &dependent.warnings)
144            .finish()
145    }
146}