asciidoc_parser/parser/include_file_handler.rs
1use std::fmt::Debug;
2
3use crate::{Parser, attributes::Attrlist};
4
5/// An `IncludeFileHandler` is responsible for providing the text content for an
6/// `include::` directive when encountered.
7///
8/// A client of [`Parser`] may provide an `IncludeFileHandler` to customize how
9/// include file resolution is handled.
10///
11/// [`Parser`]: crate::Parser
12pub trait IncludeFileHandler: Debug {
13 /// Provide the file content for an `include::` directive, if available.
14 ///
15 /// # Parameters
16 /// - `source`: The path to the document that is including the file. A root
17 /// document may be signaled via `None` depending on how the parser was
18 /// invoked. This path should be considered when resolving relative paths.
19 /// - `target`: The path to the document that was provided in the
20 /// `include::` directive.
21 /// - `attrlist`: Any attributes specified on the include directive.
22 /// - `parser`: An implementation may read document attribute values from
23 /// the [`Parser`] state.
24 ///
25 /// Return the content of the include file (wrapped in [`IncludeContent`])
26 /// if found. If no file is found, return `None` and an appropriate warning
27 /// message will be generated.
28 ///
29 /// # Options
30 /// With the exception of `encoding` (see below), the implementation should
31 /// not attempt to interpret any of the built-in attributes (i.e.
32 /// `leveloffset`, `lines`, `tags`, or `indent`). Correct handling of these
33 /// attributes will be provided by the parser itself.
34 ///
35 /// # Encoding
36 /// The content returned in [`IncludeContent`] is a typical Rust [`String`]
37 /// and therefore must be encoded as UTF-8.
38 ///
39 /// If the implementation is capable of transcoding from other formats, it
40 /// may use the `encoding` attribute as a hint of the source format. When it
41 /// transcodes the content to UTF-8, it should return the result via
42 /// [`IncludeContent::transcoded`] so that the parser knows the requested
43 /// encoding was honored and suppresses the non-UTF-8 include-encoding
44 /// warning.
45 ///
46 /// An implementation that only deals in UTF-8 should return its content via
47 /// [`IncludeContent::new`] (or the [`From`] conversions). If the directive
48 /// requested a non-UTF-8 `encoding`, the parser will emit a non-UTF-8
49 /// include-encoding warning in that case.
50 ///
51 /// If the implementation finds a file that is not encoded in UTF-8 and is
52 /// incapable of transcoding it, it should return `None`.
53 fn resolve_target<'src>(
54 &self,
55 source: Option<&str>,
56 target: &str,
57 attrlist: &Attrlist<'src>,
58 parser: &Parser,
59 ) -> Option<IncludeContent>;
60}
61
62/// The content returned by an [`IncludeFileHandler`] for an `include::`
63/// directive, together with the metadata the parser needs to finish processing
64/// the include.
65///
66/// Construct one via [`IncludeContent::new`] for UTF-8 content that was read
67/// without interpreting the `encoding` attribute, or via
68/// [`IncludeContent::transcoded`] for content the handler transcoded to UTF-8
69/// while honoring a requested `encoding`. See the `# Encoding` section of
70/// [`IncludeFileHandler::resolve_target`] for details.
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub struct IncludeContent {
73 content: String,
74 encoding_handled: bool,
75}
76
77impl IncludeContent {
78 /// UTF-8 content that was read **without** interpreting the `encoding`
79 /// attribute.
80 ///
81 /// If the `include::` directive requested a non-UTF-8 `encoding`, the
82 /// parser will emit a non-UTF-8 include-encoding warning. This is the
83 /// appropriate constructor for a handler that only deals in UTF-8.
84 pub fn new(content: impl Into<String>) -> Self {
85 Self {
86 content: content.into(),
87 encoding_handled: false,
88 }
89 }
90
91 /// Content that the handler transcoded to UTF-8 while honoring the
92 /// requested `encoding` attribute.
93 ///
94 /// The parser will **not** emit a non-UTF-8 include-encoding warning for
95 /// content returned this way, since the handler has already reencoded it.
96 pub fn transcoded(content: impl Into<String>) -> Self {
97 Self {
98 content: content.into(),
99 encoding_handled: true,
100 }
101 }
102
103 /// Returns the UTF-8 content of the include.
104 pub fn content(&self) -> &str {
105 &self.content
106 }
107
108 /// Returns `true` if the handler honored the requested `encoding` attribute
109 /// (i.e. the content was created via [`IncludeContent::transcoded`]).
110 pub fn encoding_handled(&self) -> bool {
111 self.encoding_handled
112 }
113
114 /// Consumes the `IncludeContent`, returning the owned UTF-8 content.
115 pub fn into_content(self) -> String {
116 self.content
117 }
118}
119
120impl From<String> for IncludeContent {
121 fn from(content: String) -> Self {
122 Self::new(content)
123 }
124}
125
126impl From<&str> for IncludeContent {
127 fn from(content: &str) -> Self {
128 Self::new(content)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::IncludeContent;
135
136 #[test]
137 fn new_does_not_mark_encoding_handled() {
138 let content = IncludeContent::new("Content.");
139 assert_eq!(content.content(), "Content.");
140 assert!(!content.encoding_handled());
141 assert_eq!(content.into_content(), "Content.".to_owned());
142 }
143
144 #[test]
145 fn transcoded_marks_encoding_handled() {
146 let content = IncludeContent::transcoded("Résumé.");
147 assert_eq!(content.content(), "Résumé.");
148 assert!(content.encoding_handled());
149 assert_eq!(content.into_content(), "Résumé.".to_owned());
150 }
151
152 #[test]
153 fn from_string_and_str_do_not_mark_encoding_handled() {
154 let from_string = IncludeContent::from("Content.".to_owned());
155 assert_eq!(from_string, IncludeContent::new("Content."));
156 assert!(!from_string.encoding_handled());
157
158 let from_str: IncludeContent = "Content.".into();
159 assert_eq!(from_str, IncludeContent::new("Content."));
160 assert!(!from_str.encoding_handled());
161 }
162}