1pub mod format_config;
2pub mod parser;
3
4use format_config::FormatConfig;
5use std::error::Error as StdError;
6use std::fmt;
7
8#[derive(Debug)]
10pub enum ParseError {
11 EmptyInput,
13 SyntaxError(String),
15 InternalError(String),
17}
18
19impl fmt::Display for ParseError {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 ParseError::EmptyInput => write!(f, "Empty input"),
23 ParseError::SyntaxError(msg) => write!(f, "Syntax error: {}", msg),
24 ParseError::InternalError(msg) => write!(f, "Internal error: {}", msg),
25 }
26 }
27}
28
29impl StdError for ParseError {}
30
31#[derive(Debug, Clone, PartialEq)]
32pub enum LiNo<T> {
33 Link { id: Option<T>, values: Vec<Self> },
34 Ref(T),
35}
36
37impl<T> LiNo<T> {
38 pub fn is_ref(&self) -> bool {
39 matches!(self, LiNo::Ref(_))
40 }
41
42 pub fn is_link(&self) -> bool {
43 matches!(self, LiNo::Link { .. })
44 }
45}
46
47impl<T: ToString + Clone> LiNo<T> {
48 pub fn format_with_config(&self, config: &FormatConfig) -> String {
56 match self {
57 LiNo::Ref(value) => {
58 let escaped = escape_reference(&value.to_string());
59 if config.less_parentheses {
60 escaped
61 } else {
62 format!("({})", escaped)
63 }
64 }
65 LiNo::Link { id, values } => {
66 if id.is_none() && values.is_empty() {
68 return if config.less_parentheses {
69 String::new()
70 } else {
71 "()".to_string()
72 };
73 }
74
75 if values.is_empty() {
77 if let Some(ref id_val) = id {
78 let escaped_id = escape_reference(&id_val.to_string());
79 return if config.less_parentheses && !needs_parentheses(&id_val.to_string())
80 {
81 escaped_id
82 } else {
83 format!("({})", escaped_id)
84 };
85 }
86 return if config.less_parentheses {
87 String::new()
88 } else {
89 "()".to_string()
90 };
91 }
92
93 let mut should_indent = false;
95 if config.should_indent_by_ref_count(values.len()) {
96 should_indent = true;
97 } else {
98 let values_str = values
100 .iter()
101 .map(|v| format_value(v))
102 .collect::<Vec<_>>()
103 .join(" ");
104
105 let test_line = if let Some(ref id_val) = id {
106 let id_str = escape_reference(&id_val.to_string());
107 if config.less_parentheses {
108 format!("{}: {}", id_str, values_str)
109 } else {
110 format!("({}: {})", id_str, values_str)
111 }
112 } else if config.less_parentheses {
113 values_str.clone()
114 } else {
115 format!("({})", values_str)
116 };
117
118 if config.should_indent_by_length(&test_line) {
119 should_indent = true;
120 }
121 }
122
123 if should_indent && !config.prefer_inline {
125 return self.format_indented(config);
126 }
127
128 let values_str = values
130 .iter()
131 .map(|v| format_value(v))
132 .collect::<Vec<_>>()
133 .join(" ");
134
135 if id.is_none() {
137 if config.less_parentheses {
138 let all_simple = values.iter().all(|v| matches!(v, LiNo::Ref(_)));
140 if all_simple {
141 return values
142 .iter()
143 .map(|v| match v {
144 LiNo::Ref(r) => escape_reference(&r.to_string()),
145 _ => format_value(v),
146 })
147 .collect::<Vec<_>>()
148 .join(" ");
149 }
150 return values_str;
151 }
152 return format!("({})", values_str);
153 }
154
155 let id_str = escape_reference(&id.as_ref().unwrap().to_string());
157 let with_colon = format!("{}: {}", id_str, values_str);
158 if config.less_parentheses && !needs_parentheses(&id.as_ref().unwrap().to_string())
159 {
160 with_colon
161 } else {
162 format!("({})", with_colon)
163 }
164 }
165 }
166 }
167
168 fn format_indented(&self, config: &FormatConfig) -> String {
170 match self {
171 LiNo::Ref(value) => {
172 let escaped = escape_reference(&value.to_string());
173 format!("({})", escaped)
174 }
175 LiNo::Link { id, values } => {
176 if id.is_none() {
177 values
179 .iter()
180 .map(|v| format!("{}{}", config.indent_string, format_value(v)))
181 .collect::<Vec<_>>()
182 .join("\n")
183 } else {
184 let id_str = escape_reference(&id.as_ref().unwrap().to_string());
186 let mut lines = vec![format!("{}:", id_str)];
187 for v in values {
188 lines.push(format!("{}{}", config.indent_string, format_value(v)));
189 }
190 lines.join("\n")
191 }
192 }
193 }
194 }
195}
196
197impl<T: ToString> fmt::Display for LiNo<T> {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 match self {
200 LiNo::Ref(value) => write!(f, "{}", value.to_string()),
201 LiNo::Link { id, values } => {
202 let id_str = id
203 .as_ref()
204 .map(|id| format!("{}: ", id.to_string()))
205 .unwrap_or_default();
206
207 if f.alternate() {
208 let lines = values
210 .iter()
211 .map(|value| {
212 match value {
215 LiNo::Ref(_) => format!("{}({})", id_str, value),
216 _ => format!("{}{}", id_str, value),
217 }
218 })
219 .collect::<Vec<_>>()
220 .join("\n");
221 write!(f, "{}", lines)
222 } else {
223 let values_str = values
224 .iter()
225 .map(|value| value.to_string())
226 .collect::<Vec<_>>()
227 .join(" ");
228 write!(f, "({}{})", id_str, values_str)
229 }
230 }
231 }
232 }
233}
234
235impl From<parser::Link> for LiNo<String> {
237 fn from(link: parser::Link) -> Self {
238 if link.values.is_empty() && link.children.is_empty() {
239 if let Some(id) = link.id {
240 LiNo::Ref(id)
241 } else {
242 LiNo::Link {
243 id: None,
244 values: vec![],
245 }
246 }
247 } else {
248 let values: Vec<LiNo<String>> = link.values.into_iter().map(|v| v.into()).collect();
249 LiNo::Link {
250 id: link.id,
251 values,
252 }
253 }
254 }
255}
256
257fn flatten_links(links: Vec<parser::Link>) -> Vec<LiNo<String>> {
259 let mut result = vec![];
260
261 for link in links {
262 flatten_link_recursive(&link, None, &mut result);
263 }
264
265 result
266}
267
268fn flatten_link_recursive(
269 link: &parser::Link,
270 parent: Option<&LiNo<String>>,
271 result: &mut Vec<LiNo<String>>,
272) {
273 if link.is_indented_id
276 && link.id.is_some()
277 && link.values.is_empty()
278 && !link.children.is_empty()
279 {
280 let child_values: Vec<LiNo<String>> = link
281 .children
282 .iter()
283 .map(|child| {
284 if child.values.len() == 1
286 && child.values[0].values.is_empty()
287 && child.values[0].children.is_empty()
288 {
289 if let Some(ref id) = child.values[0].id {
291 LiNo::Ref(id.clone())
292 } else {
293 parser::Link {
295 id: child.id.clone(),
296 values: child.values.clone(),
297 children: vec![],
298 is_indented_id: false,
299 }
300 .into()
301 }
302 } else {
303 parser::Link {
304 id: child.id.clone(),
305 values: child.values.clone(),
306 children: vec![],
307 is_indented_id: false,
308 }
309 .into()
310 }
311 })
312 .collect();
313
314 let current = LiNo::Link {
315 id: link.id.clone(),
316 values: child_values,
317 };
318
319 let combined = if let Some(parent) = parent {
320 let wrapped_parent = match parent {
322 LiNo::Ref(ref_id) => LiNo::Link {
323 id: None,
324 values: vec![LiNo::Ref(ref_id.clone())],
325 },
326 link => link.clone(),
327 };
328
329 LiNo::Link {
330 id: None,
331 values: vec![wrapped_parent, current],
332 }
333 } else {
334 current
335 };
336
337 result.push(combined);
338 return; }
340
341 let current = if link.values.is_empty() {
343 if let Some(id) = &link.id {
344 LiNo::Ref(id.clone())
345 } else {
346 LiNo::Link {
347 id: None,
348 values: vec![],
349 }
350 }
351 } else {
352 let values: Vec<LiNo<String>> = link
353 .values
354 .iter()
355 .map(|v| {
356 parser::Link {
357 id: v.id.clone(),
358 values: v.values.clone(),
359 children: vec![],
360 is_indented_id: false,
361 }
362 .into()
363 })
364 .collect();
365 LiNo::Link {
366 id: link.id.clone(),
367 values,
368 }
369 };
370
371 let combined = if let Some(parent) = parent {
373 let wrapped_parent = match parent {
375 LiNo::Ref(ref_id) => LiNo::Link {
376 id: None,
377 values: vec![LiNo::Ref(ref_id.clone())],
378 },
379 link => link.clone(),
380 };
381
382 let wrapped_current = match ¤t {
384 LiNo::Ref(ref_id) => LiNo::Link {
385 id: None,
386 values: vec![LiNo::Ref(ref_id.clone())],
387 },
388 link => link.clone(),
389 };
390
391 LiNo::Link {
392 id: None,
393 values: vec![wrapped_parent, wrapped_current],
394 }
395 } else {
396 current.clone()
397 };
398
399 result.push(combined.clone());
400
401 for child in &link.children {
403 flatten_link_recursive(child, Some(&combined), result);
404 }
405}
406
407pub fn parse_lino(document: &str) -> Result<LiNo<String>, ParseError> {
408 if document.trim().is_empty() {
410 return Ok(LiNo::Link {
411 id: None,
412 values: vec![],
413 });
414 }
415
416 match parser::parse_document(document) {
417 Ok((_, links)) => {
418 if links.is_empty() {
419 Ok(LiNo::Link {
420 id: None,
421 values: vec![],
422 })
423 } else {
424 let flattened = flatten_links(links);
426 Ok(LiNo::Link {
427 id: None,
428 values: flattened,
429 })
430 }
431 }
432 Err(e) => Err(ParseError::SyntaxError(format!("{:?}", e))),
433 }
434}
435
436pub fn parse_lino_to_links(document: &str) -> Result<Vec<LiNo<String>>, ParseError> {
438 if document.trim().is_empty() {
440 return Ok(vec![]);
441 }
442
443 match parser::parse_document(document) {
444 Ok((_, links)) => {
445 if links.is_empty() {
446 Ok(vec![])
447 } else {
448 let flattened = flatten_links(links);
450 Ok(flattened)
451 }
452 }
453 Err(e) => Err(ParseError::SyntaxError(format!("{:?}", e))),
454 }
455}
456
457pub fn format_links(links: &[LiNo<String>]) -> String {
460 links
461 .iter()
462 .map(|link| format!("{}", link))
463 .collect::<Vec<_>>()
464 .join("\n")
465}
466
467pub fn format_links_with_config(links: &[LiNo<String>], config: &FormatConfig) -> String {
477 if links.is_empty() {
478 return String::new();
479 }
480
481 let links_to_format = if config.group_consecutive {
483 group_consecutive_links(links)
484 } else {
485 links.to_vec()
486 };
487
488 links_to_format
489 .iter()
490 .map(|link| link.format_with_config(config))
491 .collect::<Vec<_>>()
492 .join("\n")
493}
494
495fn group_consecutive_links(links: &[LiNo<String>]) -> Vec<LiNo<String>> {
511 if links.is_empty() {
512 return vec![];
513 }
514
515 let mut grouped = vec![];
516 let mut i = 0;
517
518 while i < links.len() {
519 let current = &links[i];
520
521 if let LiNo::Link {
523 id: Some(ref current_id),
524 values: ref current_values,
525 } = current
526 {
527 if !current_values.is_empty() {
528 let mut same_id_values = current_values.clone();
530 let mut j = i + 1;
531
532 while j < links.len() {
533 if let LiNo::Link {
534 id: Some(ref next_id),
535 values: ref next_values,
536 } = &links[j]
537 {
538 if next_id == current_id && !next_values.is_empty() {
539 same_id_values.extend(next_values.clone());
540 j += 1;
541 } else {
542 break;
543 }
544 } else {
545 break;
546 }
547 }
548
549 if j > i + 1 {
551 grouped.push(LiNo::Link {
552 id: Some(current_id.clone()),
553 values: same_id_values,
554 });
555 i = j;
556 continue;
557 }
558 }
559 }
560
561 grouped.push(current.clone());
562 i += 1;
563 }
564
565 grouped
566}
567
568fn escape_reference(reference: &str) -> String {
570 if reference.is_empty() || reference.trim().is_empty() {
571 return String::new();
572 }
573
574 let has_single_quote = reference.contains('\'');
575 let has_double_quote = reference.contains('"');
576
577 let needs_quoting = reference.contains(':')
578 || reference.contains('(')
579 || reference.contains(')')
580 || reference.contains(' ')
581 || reference.contains('\t')
582 || reference.contains('\n')
583 || reference.contains('\r')
584 || has_double_quote
585 || has_single_quote;
586
587 if has_single_quote && has_double_quote {
589 return format!("'{}'", reference.replace('\'', "\\'"));
591 }
592
593 if has_double_quote {
595 return format!("'{}'", reference);
596 }
597
598 if has_single_quote {
600 return format!("\"{}\"", reference);
601 }
602
603 if needs_quoting {
605 return format!("'{}'", reference);
606 }
607
608 reference.to_string()
610}
611
612fn needs_parentheses(s: &str) -> bool {
614 s.contains(' ') || s.contains(':') || s.contains('(') || s.contains(')')
615}
616
617fn format_value<T: ToString>(value: &LiNo<T>) -> String {
619 match value {
620 LiNo::Ref(r) => escape_reference(&r.to_string()),
621 LiNo::Link { id, values } => {
622 if values.is_empty() {
624 if let Some(ref id_val) = id {
625 return escape_reference(&id_val.to_string());
626 }
627 return String::new();
628 }
629 format!("{}", value)
631 }
632 }
633}