1use std::{
20 borrow::Cow,
21 fmt::{Debug, Display},
22 iter::repeat,
23};
24
25pub type Str<'a> = Cow<'a, str>;
26#[allow(dead_code)]
30type Pretties<'a> = Cow<'a, [Pretty<'a>]>;
31type BTreeMap<K, V> = Vec<(K, V)>;
33pub type CowAssocArr<'a> = BTreeMap<Str<'a>, Pretty<'a>>;
35pub type StrAssocArr<'a> = BTreeMap<&'a str, Pretty<'a>>;
36
37pub mod ascii;
38pub mod unicode;
39
40pub mod helper;
41
42#[derive(Clone)]
43pub struct XmlNode<'a> {
44 pub name: Str<'a>,
45 pub fields: CowAssocArr<'a>,
46 pub(crate) fields_is_linear: bool,
49 pub children: Vec<Pretty<'a>>,
50}
51
52impl<'a> XmlNode<'a> {
53 pub fn simple_record(
54 name: impl Into<Str<'a>>,
55 fields: StrAssocArr<'a>,
56 children: Vec<Pretty<'a>>,
57 ) -> Self {
58 let name = name.into();
59 let fields = fields.into_iter().map(|(k, v)| (k.into(), v)).collect();
60 Self::new(name, fields, children)
61 }
62
63 pub fn has_children(&self) -> bool {
64 !self.children.is_empty() || (self.fields.iter()).any(|(_, x)| x.has_children())
65 }
66
67 fn ol_build_str_ascii(&self, reduced_ws: bool, builder: &mut String) {
68 builder.push_str(&self.name);
69 if self.fields.is_empty() {
70 return;
71 }
72 builder.push_str(" { ");
73 for (i, (k, v)) in self.fields.iter().enumerate() {
74 if i > 0 {
75 builder.push_str(", ");
76 }
77 builder.push_str(k);
78 builder.push_str(": ");
79 v.ol_build_str_ascii(reduced_ws, builder);
80 }
81 builder.push_str(" }");
82 }
83
84 fn ol_len(&self, reduced_ws: bool) -> usize {
85 let mem: usize = (self.fields.iter())
86 .map(|(k, v)| k.chars().count() + ": ".len() + v.ol_len(reduced_ws))
87 .sum();
88 let mid = self.fields.len().saturating_sub(1) * ", ".len();
89 let begin_end = if self.fields.is_empty() {
90 0
91 } else {
92 " { }".len()
93 } + self.name.chars().count();
94 mem + mid + begin_end
95 }
96
97 pub fn new(name: Str<'a>, fields: CowAssocArr<'a>, children: Vec<Pretty<'a>>) -> Self {
98 Self {
99 name,
100 fields,
101 fields_is_linear: false,
102 children,
103 }
104 }
105}
106
107#[derive(Clone)]
109pub enum Pretty<'a> {
110 Text(Str<'a>),
111 Record(XmlNode<'a>),
112 Array(Vec<Self>),
113 Linearized(&'a Self, usize),
114}
115
116impl<'a> Pretty<'a> {
117 pub fn simple_record(
118 name: impl Into<Str<'a>>,
119 fields: StrAssocArr<'a>,
120 children: Vec<Self>,
121 ) -> Self {
122 Self::Record(XmlNode::simple_record(name, fields, children))
123 }
124 pub fn fieldless_record(name: impl Into<Str<'a>>, children: Vec<Self>) -> Self {
126 Self::simple_record(name, Default::default(), children)
127 }
128 pub fn childless_record(name: impl Into<Str<'a>>, fields: StrAssocArr<'a>) -> Self {
129 Self::simple_record(name, fields, Default::default())
130 }
131 pub fn list_of_strings(list: &'a [&'a str]) -> Self {
132 Self::Array(list.iter().map(|&s| s.into()).collect())
133 }
134
135 pub fn display(display: &impl Display) -> Self {
136 display.to_string().into()
137 }
138
139 pub fn debug(debug: &impl Debug) -> Self {
140 format!("{:?}", debug).into()
141 }
142
143 pub fn has_children(&self) -> bool {
144 use Pretty::*;
145 match self {
146 Record(xml) => xml.has_children(),
147 Array(v) => v.iter().any(Self::has_children),
148 Text(..) => false,
149 Linearized(..) => false,
151 }
152 }
153
154 pub(crate) fn ol_build_str_ascii(&self, reduced_ws: bool, builder: &mut String) {
157 use Pretty::*;
158 match self {
159 Text(s) => builder.push_str(s),
160 Record(xml) => xml.ol_build_str_ascii(reduced_ws, builder),
161 Array(v) => {
162 if v.is_empty() {
163 builder.push_str("[]");
164 return;
165 }
166 builder.push('[');
167 if !reduced_ws {
168 builder.push(' ');
169 }
170 for (i, e) in v.iter().enumerate() {
171 if i > 0 {
172 builder.push_str(", ");
173 }
174 e.ol_build_str_ascii(reduced_ws, builder);
175 }
176 if !reduced_ws {
177 builder.push(' ');
178 }
179 builder.push(']');
180 }
181 Linearized(p, _) => p.ol_build_str_ascii(reduced_ws, builder),
182 }
183 }
184
185 pub fn to_one_line_string(&self, reduced_ws: bool) -> String {
186 let mut builder = String::with_capacity(self.ol_len(reduced_ws));
187 self.ol_build_str_ascii(reduced_ws, &mut builder);
188 builder
189 }
190
191 pub(crate) fn ol_len(&self, reduced_ws: bool) -> usize {
193 use Pretty::*;
194 match self {
195 Text(s) => s.chars().count(),
196 Record(xml) => xml.ol_len(reduced_ws),
197 Array(v) => {
198 if v.is_empty() {
199 return "[]".len();
200 }
201 let mem: usize = v.iter().map(|x| x.ol_len(reduced_ws)).sum();
202 let mid = (v.len() - 1) * ", ".len();
203 let beg = if reduced_ws { "[]".len() } else { "[ ]".len() };
204 mem + mid + beg
205 }
206 Linearized(_, len) => *len,
207 }
208 }
209}
210
211impl<'a, T: Into<Str<'a>>> From<T> for Pretty<'a> {
212 fn from(s: T) -> Self {
213 Pretty::Text(s.into())
214 }
215}
216
217#[derive(Clone)]
218pub struct PrettyConfig {
219 pub indent: usize,
220 pub width: usize,
222 pub need_boundaries: bool,
223 pub reduced_spaces: bool,
226}
227
228impl PrettyConfig {
229 pub fn horizon(&self, out: &mut String, width: usize) {
230 if !self.need_boundaries {
231 return;
232 }
233 out.push_str("+");
234 out.extend(repeat("-").take(width + 2));
235 out.push_str("+");
236 }
237}
238
239struct LinedBuffer<'a> {
240 width: usize,
241 pub already_occupied: usize,
243 out: &'a mut String,
244 config: &'a PrettyConfig,
245}
246impl<'a> LinedBuffer<'a> {
247 fn begin_line(&mut self) {
248 if self.config.need_boundaries {
249 self.out.push_str("| ");
250 }
251 }
252 fn push(&mut self, s: &str) {
253 self.out.push_str(s);
254 self.already_occupied += s.chars().count();
255 }
256 fn pip(&mut self, amount: usize) {
257 self.push(" ".repeat(amount).as_str());
258 }
259 fn pusheen(&mut self) {
260 let eol = if self.config.need_boundaries {
268 self.pip(self.width - self.already_occupied);
269 " |\n"
270 } else {
271 "\n"
272 };
273 self.push(eol);
274 self.already_occupied = 0;
276 }
277}
278
279impl Default for PrettyConfig {
280 fn default() -> Self {
281 Self {
282 indent: 4,
283 width: 120,
284 need_boundaries: true,
285 reduced_spaces: false,
286 }
287 }
288}