1use std::fmt;
2
3use crate::document::group::GroupIdentifier;
4
5pub mod group;
6
7#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
8pub enum Document<'a> {
9 String(&'a str),
10 Array(Vec<Document<'a>>),
11 Indent(Vec<Document<'a>>),
13 IndentIfBreak(IndentIfBreak<'a>),
14 Group(Group<'a>),
20 Line(Line),
24 LineSuffix(Vec<Document<'a>>),
28 LineSuffixBoundary,
29 IfBreak(IfBreak<'a>),
31 Fill(Fill<'a>),
35 BreakParent,
37 Align(Align<'a>),
38 Trim(Trim),
40 DoNotTrim,
42}
43
44#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
45pub struct Align<'a> {
46 pub alignment: &'a str,
47 pub contents: Vec<Document<'a>>,
48}
49
50#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
51pub enum Trim {
52 Whitespace,
54 Newlines,
56}
57
58#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord)]
59pub struct Line {
60 pub hard: bool,
61 pub soft: bool,
62 pub literal: bool,
63}
64
65#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
66pub struct Group<'a> {
67 pub contents: Vec<Document<'a>>,
68 pub should_break: bool,
69 pub expanded_states: Option<Vec<Document<'a>>>,
70 pub id: Option<GroupIdentifier>,
71}
72
73#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
74pub struct IndentIfBreak<'a> {
75 pub contents: Vec<Document<'a>>,
76 pub group_id: Option<GroupIdentifier>,
77}
78
79#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
80pub struct Fill<'a> {
81 pub parts: Vec<Document<'a>>,
82}
83
84#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
85pub struct IfBreak<'a> {
86 pub break_contents: Box<Document<'a>>,
87 pub flat_content: Box<Document<'a>>,
88 pub group_id: Option<GroupIdentifier>,
89}
90
91#[derive(Clone, Copy)]
92pub enum Separator {
93 #[allow(unused)]
94 SoftLine,
95 HardLine,
96 LiteralLine,
97 CommaLine, Space,
99}
100
101impl Line {
102 pub fn soft() -> Self {
105 Self { soft: true, ..Self::default() }
106 }
107
108 pub fn hard() -> Self {
111 Self { hard: true, ..Self::default() }
112 }
113
114 pub fn literal() -> Self {
115 Self { hard: true, literal: true, ..Default::default() }
116 }
117}
118
119impl<'a> Group<'a> {
120 pub fn new(contents: Vec<Document<'a>>) -> Self {
121 Self { contents, should_break: false, id: None, expanded_states: None }
122 }
123
124 pub fn conditional(contents: Vec<Document<'a>>, expanded_states: Vec<Document<'a>>) -> Self {
125 Self { contents, should_break: false, id: None, expanded_states: Some(expanded_states) }
126 }
127
128 pub fn with_break(mut self, yes: bool) -> Self {
129 self.should_break = yes;
130 self
131 }
132
133 pub fn with_id(mut self, id: GroupIdentifier) -> Self {
134 self.id = Some(id);
135 self
136 }
137}
138
139impl<'a> IndentIfBreak<'a> {
140 pub fn new(contents: Vec<Document<'a>>) -> Self {
141 Self { contents, group_id: None }
142 }
143
144 pub fn with_id(mut self, id: GroupIdentifier) -> Self {
145 self.group_id = Some(id);
146 self
147 }
148}
149
150impl<'a> Fill<'a> {
151 pub fn drain_out_pair(&mut self) -> (Option<Document<'a>>, Option<Document<'a>>) {
152 let content = if !self.parts.is_empty() { Some(self.parts.remove(0)) } else { None };
153 let whitespace = if !self.parts.is_empty() { Some(self.parts.remove(0)) } else { None };
154
155 (content, whitespace)
156 }
157
158 pub fn dequeue(&mut self) -> Option<Document<'a>> {
159 if !self.parts.is_empty() { Some(self.parts.remove(0)) } else { None }
160 }
161
162 pub fn enqueue(&mut self, doc: Document<'a>) {
163 self.parts.insert(0, doc);
164 }
165
166 pub fn parts(&self) -> &[Document<'a>] {
167 &self.parts
168 }
169}
170
171impl<'a> IfBreak<'a> {
172 pub fn new(break_contents: Document<'a>, flat_content: Document<'a>) -> Self {
173 Self { break_contents: Box::new(break_contents), flat_content: Box::new(flat_content), group_id: None }
174 }
175
176 pub fn then(break_contents: Document<'a>) -> Self {
177 Self { break_contents: Box::new(break_contents), flat_content: Box::new(Document::empty()), group_id: None }
178 }
179
180 pub fn with_id(mut self, id: GroupIdentifier) -> Self {
181 self.group_id = Some(id);
182 self
183 }
184}
185
186impl<'a> Document<'a> {
187 #[inline]
188 pub fn empty() -> Document<'a> {
189 Document::String("")
190 }
191
192 #[inline]
193 pub fn space() -> Document<'a> {
194 Document::String(" ")
195 }
196
197 pub fn can_break(&self) -> bool {
198 self.any(|doc| matches!(doc, Document::Line(_)))
199 }
200
201 pub fn any<F>(&self, predicate: F) -> bool
202 where
203 F: Fn(&Document<'a>) -> bool,
204 {
205 if predicate(self) {
206 return true;
207 }
208
209 match self {
210 Document::Array(docs) | Document::LineSuffix(docs) | Document::Indent(docs) => docs.iter().any(predicate),
211 Document::IndentIfBreak(IndentIfBreak { contents, .. }) | Document::Group(Group { contents, .. }) => {
212 contents.iter().any(predicate)
213 }
214 Document::IfBreak(IfBreak { break_contents, flat_content, .. }) => {
215 predicate(break_contents) || predicate(flat_content)
216 }
217 Document::Fill(fill) => fill.parts.iter().any(predicate),
218 _ => false,
219 }
220 }
221
222 pub fn join(documents: Vec<Document<'a>>, separator: Separator) -> Vec<Document<'a>> {
223 let mut parts = vec![];
224 for (i, document) in documents.into_iter().enumerate() {
225 if i != 0 {
226 parts.push(match separator {
227 Separator::Space => Document::String(" "),
228 Separator::SoftLine => Document::Line(Line::soft()),
229 Separator::HardLine => Document::Line(Line::hard()),
230 Separator::LiteralLine => {
231 Document::Array(vec![Document::Line(Line::literal()), Document::BreakParent])
232 }
233 Separator::CommaLine => {
234 Document::Array(vec![Document::String(","), Document::Line(Line::default())])
235 }
236 });
237 }
238
239 parts.push(document);
240 }
241 parts
242 }
243}
244
245impl fmt::Display for Document<'_> {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 match self {
248 Document::String(s) => write!(f, "{s:?}"),
249 Document::Array(docs) => {
250 let mut printed: Vec<String> = docs.iter().map(|d| d.to_string()).collect();
251
252 if printed.len() == 1 {
253 write!(f, "{}", printed.pop().unwrap())
254 } else {
255 write!(f, "[{}]", printed.join(", "))
256 }
257 }
258 Document::Indent(docs) => {
259 write!(f, "indent({})", Document::Array(docs.clone()))
260 }
261 Document::IndentIfBreak(IndentIfBreak { contents, group_id }) => {
262 let mut options = vec![];
263 if let Some(id) = group_id {
264 options.push(format!("groupId: {id}"));
265 }
266 let options_str =
267 if options.is_empty() { String::new() } else { format!(", {{ {} }}", options.join(", ")) };
268 write!(f, "indentIfBreak({}{})", Document::Array(contents.clone()), options_str)
269 }
270 Document::Group(Group { contents, should_break, expanded_states, id }) => {
271 let mut options = vec![];
272 if *should_break {
273 options.push("shouldBreak: true".to_string());
274 }
275 if let Some(id) = id {
276 options.push(format!("id: {id}"));
277 }
278 let expanded_states_str = if let Some(states) = expanded_states {
279 format!(
280 "conditionalGroup([{}]",
281 states.iter().map(|s| s.to_string()).collect::<Vec<_>>().join(", ")
282 )
283 } else {
284 String::new()
285 };
286 let options_str =
287 if options.is_empty() { String::new() } else { format!(", {{ {} }}", options.join(", ")) };
288
289 if expanded_states_str.is_empty() {
290 write!(f, "group({}{})", Document::Array(contents.clone()), options_str)
291 } else {
292 write!(f, "{}, {}{})", expanded_states_str, Document::Array(contents.clone()), options_str,)
293 }
294 }
295 Document::Line(line) => {
296 if line.literal {
297 write!(f, "literalLine")
298 } else if line.hard {
299 write!(f, "hardline")
300 } else if line.soft {
301 write!(f, "softline")
302 } else {
303 write!(f, "line")
304 }
305 }
306 Document::LineSuffix(docs) => {
307 write!(f, "lineSuffix({})", Document::Array(docs.clone()))
308 }
309 Document::LineSuffixBoundary => write!(f, "lineSuffixBoundary"),
310 Document::IfBreak(IfBreak { break_contents, flat_content, group_id }) => {
311 let mut options = vec![];
312 if let Some(id) = group_id {
313 options.push(format!("groupId: {id}"));
314 }
315 let options_str =
316 if options.is_empty() { String::new() } else { format!(", {{ {} }}", options.join(", ")) };
317
318 write!(f, "ifBreak({break_contents}, {flat_content}{options_str})")
319 }
320 Document::Fill(Fill { parts }) => {
321 write!(f, "fill([{}])", parts.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", "))
322 }
323 Document::BreakParent => write!(f, "breakParent"),
324 Document::Align(Align { alignment, contents }) => {
325 write!(f, "dedentToRoot(align({:?}, {}))", alignment, Document::Array(contents.clone()))
326 }
327 Document::Trim(trim) => match trim {
328 Trim::Whitespace => write!(f, "trim"),
329 Trim::Newlines => write!(f, "trimNewlines"),
330 },
331 Document::DoNotTrim => write!(f, "doNotTrim"),
332 }
333 }
334}