1use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
49use links_notation::{parse_lino_to_links, LiNo};
50use std::collections::{HashMap, HashSet};
51use std::fmt;
52
53pub mod readable;
54
55pub use readable::{BASE64_MARKER, DEFAULT_INDENT};
56
57mod type_ids {
59 pub const NULL: &str = "null";
60 pub const BOOL: &str = "bool";
61 pub const INT: &str = "int";
62 pub const FLOAT: &str = "float";
63 pub const STR: &str = "str";
64 pub const ARRAY: &str = "array";
65 pub const OBJECT: &str = "object";
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum CodecError {
71 ParseError(String),
73 DecodeError(String),
75 UnknownType(String),
77}
78
79impl fmt::Display for CodecError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 CodecError::ParseError(msg) => write!(f, "Parse error: {}", msg),
83 CodecError::DecodeError(msg) => write!(f, "Decode error: {}", msg),
84 CodecError::UnknownType(t) => write!(f, "Unknown type marker: {}", t),
85 }
86 }
87}
88
89impl std::error::Error for CodecError {}
90
91#[derive(Debug, Clone)]
96pub enum LinoValue {
97 Null,
99 Bool(bool),
101 Int(i64),
103 Float(f64),
105 String(String),
107 Array(Vec<LinoValue>),
109 Object(Vec<(String, LinoValue)>),
111}
112
113impl PartialEq for LinoValue {
114 fn eq(&self, other: &Self) -> bool {
115 match (self, other) {
116 (LinoValue::Null, LinoValue::Null) => true,
117 (LinoValue::Bool(a), LinoValue::Bool(b)) => a == b,
118 (LinoValue::Int(a), LinoValue::Int(b)) => a == b,
119 (LinoValue::Float(a), LinoValue::Float(b)) => {
120 if a.is_nan() && b.is_nan() {
122 true
123 } else {
124 a == b
125 }
126 }
127 (LinoValue::String(a), LinoValue::String(b)) => a == b,
128 (LinoValue::Array(a), LinoValue::Array(b)) => a == b,
129 (LinoValue::Object(a), LinoValue::Object(b)) => {
130 if a.len() != b.len() {
132 return false;
133 }
134 let a_map: HashMap<&str, &LinoValue> =
136 a.iter().map(|(k, v)| (k.as_str(), v)).collect();
137 let b_map: HashMap<&str, &LinoValue> =
138 b.iter().map(|(k, v)| (k.as_str(), v)).collect();
139 a_map == b_map
140 }
141 _ => false,
142 }
143 }
144}
145
146impl LinoValue {
147 pub fn object<I, K, V>(iter: I) -> Self
149 where
150 I: IntoIterator<Item = (K, V)>,
151 K: Into<String>,
152 V: Into<LinoValue>,
153 {
154 LinoValue::Object(
155 iter.into_iter()
156 .map(|(k, v)| (k.into(), v.into()))
157 .collect(),
158 )
159 }
160
161 pub fn array<I, V>(iter: I) -> Self
163 where
164 I: IntoIterator<Item = V>,
165 V: Into<LinoValue>,
166 {
167 LinoValue::Array(iter.into_iter().map(|v| v.into()).collect())
168 }
169
170 pub fn is_null(&self) -> bool {
172 matches!(self, LinoValue::Null)
173 }
174
175 pub fn as_bool(&self) -> Option<bool> {
177 match self {
178 LinoValue::Bool(b) => Some(*b),
179 _ => None,
180 }
181 }
182
183 pub fn as_int(&self) -> Option<i64> {
185 match self {
186 LinoValue::Int(i) => Some(*i),
187 _ => None,
188 }
189 }
190
191 pub fn as_float(&self) -> Option<f64> {
193 match self {
194 LinoValue::Float(f) => Some(*f),
195 LinoValue::Int(i) => Some(*i as f64),
196 _ => None,
197 }
198 }
199
200 pub fn as_str(&self) -> Option<&str> {
202 match self {
203 LinoValue::String(s) => Some(s),
204 _ => None,
205 }
206 }
207
208 pub fn as_array(&self) -> Option<&Vec<LinoValue>> {
210 match self {
211 LinoValue::Array(a) => Some(a),
212 _ => None,
213 }
214 }
215
216 pub fn as_object(&self) -> Option<&Vec<(String, LinoValue)>> {
218 match self {
219 LinoValue::Object(o) => Some(o),
220 _ => None,
221 }
222 }
223
224 pub fn get(&self, key: &str) -> Option<&LinoValue> {
226 match self {
227 LinoValue::Object(o) => o.iter().find(|(k, _)| k == key).map(|(_, v)| v),
228 _ => None,
229 }
230 }
231
232 pub fn get_index(&self, index: usize) -> Option<&LinoValue> {
234 match self {
235 LinoValue::Array(a) => a.get(index),
236 _ => None,
237 }
238 }
239}
240
241impl From<()> for LinoValue {
243 fn from(_: ()) -> Self {
244 LinoValue::Null
245 }
246}
247
248impl From<bool> for LinoValue {
249 fn from(b: bool) -> Self {
250 LinoValue::Bool(b)
251 }
252}
253
254impl From<i32> for LinoValue {
255 fn from(i: i32) -> Self {
256 LinoValue::Int(i as i64)
257 }
258}
259
260impl From<i64> for LinoValue {
261 fn from(i: i64) -> Self {
262 LinoValue::Int(i)
263 }
264}
265
266impl From<f64> for LinoValue {
267 fn from(f: f64) -> Self {
268 LinoValue::Float(f)
269 }
270}
271
272impl From<&str> for LinoValue {
273 fn from(s: &str) -> Self {
274 LinoValue::String(s.to_string())
275 }
276}
277
278impl From<String> for LinoValue {
279 fn from(s: String) -> Self {
280 LinoValue::String(s)
281 }
282}
283
284impl<T: Into<LinoValue>> From<Vec<T>> for LinoValue {
285 fn from(v: Vec<T>) -> Self {
286 LinoValue::Array(v.into_iter().map(|x| x.into()).collect())
287 }
288}
289
290impl<T: Into<LinoValue>> From<Option<T>> for LinoValue {
291 fn from(opt: Option<T>) -> Self {
292 match opt {
293 Some(v) => v.into(),
294 None => LinoValue::Null,
295 }
296 }
297}
298
299pub struct ObjectCodec {
305 encode_counter: usize,
307 encode_memo: HashMap<String, String>,
309 needs_id: HashSet<String>,
311 all_definitions: Vec<(String, LiNo<String>)>,
313 decode_memo: HashMap<String, LinoValue>,
315 all_links: Vec<LiNo<String>>,
317}
318
319impl Default for ObjectCodec {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325impl ObjectCodec {
326 pub fn new() -> Self {
328 ObjectCodec {
329 encode_counter: 0,
330 encode_memo: HashMap::new(),
331 needs_id: HashSet::new(),
332 all_definitions: Vec::new(),
333 decode_memo: HashMap::new(),
334 all_links: Vec::new(),
335 }
336 }
337
338 fn reset_encode_state(&mut self) {
340 self.encode_counter = 0;
341 self.encode_memo.clear();
342 self.needs_id.clear();
343 self.all_definitions.clear();
344 }
345
346 fn reset_decode_state(&mut self) {
348 self.decode_memo.clear();
349 self.all_links.clear();
350 }
351
352 fn make_link(&self, parts: &[&str]) -> LiNo<String> {
354 let values: Vec<LiNo<String>> = parts.iter().map(|p| LiNo::Ref(p.to_string())).collect();
355 LiNo::Link { id: None, values }
356 }
357
358 fn make_ref(&self, id: &str) -> LiNo<String> {
360 LiNo::Ref(id.to_string())
361 }
362
363 fn object_key(&self, value: &LinoValue) -> String {
365 format!("{:p}", value)
367 }
368
369 fn find_objects_needing_ids(&mut self, value: &LinoValue, seen: &mut HashMap<String, bool>) {
371 match value {
373 LinoValue::Array(arr) => {
374 let key = self.object_key(value);
375
376 if seen.contains_key(&key) {
377 self.needs_id.insert(key);
379 return;
380 }
381
382 seen.insert(key, true);
383
384 for item in arr {
385 self.find_objects_needing_ids(item, seen);
386 }
387 }
388 LinoValue::Object(obj) => {
389 let key = self.object_key(value);
390
391 if seen.contains_key(&key) {
392 self.needs_id.insert(key);
394 return;
395 }
396
397 seen.insert(key, true);
398
399 for (_, v) in obj {
400 self.find_objects_needing_ids(v, seen);
401 }
402 }
403 _ => {}
404 }
405 }
406
407 pub fn encode(&mut self, value: &LinoValue) -> String {
421 readable::encode(value, DEFAULT_INDENT)
422 }
423
424 pub fn encode_with_indent(&mut self, value: &LinoValue, indent: &str) -> String {
431 readable::encode(value, indent)
432 }
433
434 pub fn encode_compact(&mut self, value: &LinoValue) -> String {
448 self.reset_encode_state();
449
450 let mut seen = HashMap::new();
452 self.find_objects_needing_ids(value, &mut seen);
453
454 let mut visited = HashSet::new();
456 let main_link = self.encode_value(value, &mut visited, 0);
457
458 if !self.all_definitions.is_empty() {
460 let mut all_links = vec![main_link];
461
462 for (ref_id, link) in &self.all_definitions {
464 let main_id = match &all_links[0] {
465 LiNo::Link { id: Some(id), .. } => Some(id.clone()),
466 _ => None,
467 };
468 if main_id.as_ref() != Some(ref_id) {
469 all_links.push(link.clone());
470 }
471 }
472
473 all_links
475 .iter()
476 .map(Self::format_link)
477 .collect::<Vec<_>>()
478 .join("\n")
479 } else {
480 Self::format_link(&main_link)
481 }
482 }
483
484 pub fn encode_obfuscated(&mut self, value: &LinoValue) -> String {
489 self.encode_compact(value)
490 }
491
492 fn format_link(link: &LiNo<String>) -> String {
494 match link {
495 LiNo::Ref(s) => s.clone(),
496 LiNo::Link { id, values } => {
497 let inner: Vec<String> = values.iter().map(Self::format_link).collect();
498
499 if let Some(link_id) = id {
500 if inner.is_empty() {
501 format!("({}:)", link_id)
502 } else {
503 format!("({}: {})", link_id, inner.join(" "))
504 }
505 } else if inner.is_empty() {
506 "()".to_string()
507 } else {
508 format!("({})", inner.join(" "))
509 }
510 }
511 }
512 }
513
514 fn encode_value(
516 &mut self,
517 value: &LinoValue,
518 visited: &mut HashSet<String>,
519 depth: usize,
520 ) -> LiNo<String> {
521 match value {
522 LinoValue::Null => self.make_link(&[type_ids::NULL]),
523
524 LinoValue::Bool(b) => {
525 if *b {
526 self.make_link(&[type_ids::BOOL, "true"])
527 } else {
528 self.make_link(&[type_ids::BOOL, "false"])
529 }
530 }
531
532 LinoValue::Int(i) => self.make_link(&[type_ids::INT, &i.to_string()]),
533
534 LinoValue::Float(f) => {
535 if f.is_nan() {
536 self.make_link(&[type_ids::FLOAT, "NaN"])
537 } else if f.is_infinite() {
538 if f.is_sign_positive() {
539 self.make_link(&[type_ids::FLOAT, "Infinity"])
540 } else {
541 self.make_link(&[type_ids::FLOAT, "-Infinity"])
542 }
543 } else {
544 self.make_link(&[type_ids::FLOAT, &f.to_string()])
545 }
546 }
547
548 LinoValue::String(s) => {
549 let b64_encoded = BASE64.encode(s.as_bytes());
550 self.make_link(&[type_ids::STR, &b64_encoded])
551 }
552
553 LinoValue::Array(arr) => {
554 let obj_key = self.object_key(value);
555
556 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
558 return self.make_ref(&ref_id);
559 }
560
561 let needs_id = self.needs_id.contains(&obj_key);
563
564 if needs_id {
565 if visited.contains(&obj_key) {
567 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
569 return self.make_ref(ref_id);
570 }
571 }
572
573 let ref_id = format!("obj_{}", self.encode_counter);
575 self.encode_counter += 1;
576 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
577 visited.insert(obj_key.clone());
578
579 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
581 for item in arr {
582 let item_link = self.encode_value(item, visited, depth + 1);
583 parts.push(item_link);
584 }
585
586 let definition = LiNo::Link {
587 id: Some(ref_id.clone()),
588 values: parts,
589 };
590
591 if depth > 0 {
593 self.all_definitions.push((ref_id.clone(), definition));
594 return self.make_ref(&ref_id);
595 }
596
597 definition
598 } else {
599 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
601 for item in arr {
602 let item_link = self.encode_value(item, visited, depth + 1);
603 parts.push(item_link);
604 }
605 LiNo::Link {
606 id: None,
607 values: parts,
608 }
609 }
610 }
611
612 LinoValue::Object(obj) => {
613 let obj_key = self.object_key(value);
614
615 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
617 return self.make_ref(&ref_id);
618 }
619
620 let needs_id = self.needs_id.contains(&obj_key);
622
623 if needs_id {
624 if visited.contains(&obj_key) {
626 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
628 return self.make_ref(ref_id);
629 }
630 }
631
632 let ref_id = format!("obj_{}", self.encode_counter);
634 self.encode_counter += 1;
635 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
636 visited.insert(obj_key.clone());
637
638 let mut parts: Vec<LiNo<String>> =
640 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
641 for (k, v) in obj {
642 let key_link =
643 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
644 let value_link = self.encode_value(v, visited, depth + 1);
645 let pair = LiNo::Link {
646 id: None,
647 values: vec![key_link, value_link],
648 };
649 parts.push(pair);
650 }
651
652 let definition = LiNo::Link {
653 id: Some(ref_id.clone()),
654 values: parts,
655 };
656
657 if depth > 0 {
659 self.all_definitions.push((ref_id.clone(), definition));
660 return self.make_ref(&ref_id);
661 }
662
663 definition
664 } else {
665 let mut parts: Vec<LiNo<String>> =
667 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
668 for (k, v) in obj {
669 let key_link =
670 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
671 let value_link = self.encode_value(v, visited, depth + 1);
672 let pair = LiNo::Link {
673 id: None,
674 values: vec![key_link, value_link],
675 };
676 parts.push(pair);
677 }
678 LiNo::Link {
679 id: None,
680 values: parts,
681 }
682 }
683 }
684 }
685 }
686
687 pub fn decode(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
700 if notation.trim().is_empty() {
701 return Ok(LinoValue::Null);
702 }
703
704 if is_compact_notation(notation) {
705 return self.decode_compact(notation);
706 }
707
708 readable::decode(notation)
709 }
710
711 pub fn decode_compact(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
721 self.reset_decode_state();
722
723 let links = parse_lino_to_links(notation)
724 .map_err(|e| CodecError::ParseError(format!("{:?}", e)))?;
725
726 if links.is_empty() {
727 return Ok(LinoValue::Null);
728 }
729
730 if links.len() > 1 {
732 self.all_links = links.clone();
733 }
734
735 self.decode_link(&links[0])
737 }
738
739 fn decode_link(&mut self, link: &LiNo<String>) -> Result<LinoValue, CodecError> {
741 match link {
742 LiNo::Ref(id) => {
743 if let Some(value) = self.decode_memo.get(id) {
745 return Ok(value.clone());
746 }
747
748 if id.starts_with("obj_") && !self.all_links.is_empty() {
750 for other_link in self.all_links.clone() {
752 if let LiNo::Link {
753 id: Some(link_id), ..
754 } = &other_link
755 {
756 if link_id == id {
757 return self.decode_link(&other_link);
758 }
759 }
760 }
761 let result = LinoValue::Array(vec![]);
763 self.decode_memo.insert(id.clone(), result.clone());
764 return Ok(result);
765 }
766
767 match id.as_str() {
769 type_ids::NULL => return Ok(LinoValue::Null),
770 type_ids::ARRAY => return Ok(LinoValue::Array(vec![])),
771 type_ids::OBJECT => return Ok(LinoValue::Object(vec![])),
772 type_ids::STR => return Ok(LinoValue::String(String::new())),
773 _ => {}
774 }
775
776 Ok(LinoValue::String(id.clone()))
778 }
779
780 LiNo::Link { id, values } => {
781 let self_ref_id = id.as_ref().filter(|i| i.starts_with("obj_"));
783 if let Some(ref_id) = self_ref_id {
784 if let Some(value) = self.decode_memo.get(ref_id) {
785 return Ok(value.clone());
786 }
787 }
788
789 if values.is_empty() {
790 return Ok(LinoValue::Null);
791 }
792
793 let type_marker = match &values[0] {
795 LiNo::Ref(t) => t.as_str(),
796 LiNo::Link { .. } => return Ok(LinoValue::Null),
797 };
798
799 match type_marker {
800 type_ids::NULL => Ok(LinoValue::Null),
801
802 type_ids::BOOL => {
803 if values.len() > 1 {
804 if let LiNo::Ref(val) = &values[1] {
805 return Ok(LinoValue::Bool(val == "true"));
806 }
807 }
808 Ok(LinoValue::Bool(false))
809 }
810
811 type_ids::INT => {
812 if values.len() > 1 {
813 if let LiNo::Ref(val) = &values[1] {
814 if let Ok(i) = val.parse::<i64>() {
815 return Ok(LinoValue::Int(i));
816 }
817 }
818 }
819 Ok(LinoValue::Int(0))
820 }
821
822 type_ids::FLOAT => {
823 if values.len() > 1 {
824 if let LiNo::Ref(val) = &values[1] {
825 return match val.as_str() {
826 "NaN" => Ok(LinoValue::Float(f64::NAN)),
827 "Infinity" => Ok(LinoValue::Float(f64::INFINITY)),
828 "-Infinity" => Ok(LinoValue::Float(f64::NEG_INFINITY)),
829 s => {
830 if let Ok(f) = s.parse::<f64>() {
831 Ok(LinoValue::Float(f))
832 } else {
833 Ok(LinoValue::Float(0.0))
834 }
835 }
836 };
837 }
838 }
839 Ok(LinoValue::Float(0.0))
840 }
841
842 type_ids::STR => {
843 if values.len() > 1 {
844 if let LiNo::Ref(b64_str) = &values[1] {
845 if let Ok(bytes) = BASE64.decode(b64_str) {
846 if let Ok(s) = String::from_utf8(bytes) {
847 return Ok(LinoValue::String(s));
848 }
849 }
850 return Ok(LinoValue::String(b64_str.clone()));
852 }
853 }
854 Ok(LinoValue::String(String::new()))
855 }
856
857 type_ids::ARRAY => {
858 let result_array = LinoValue::Array(vec![]);
860 if let Some(ref_id) = self_ref_id {
861 self.decode_memo.insert(ref_id.clone(), result_array);
862 }
863
864 let mut items = Vec::new();
866 for item_link in values.iter().skip(1) {
867 let decoded = self.decode_link(item_link)?;
868 items.push(decoded);
869 }
870
871 let result = LinoValue::Array(items);
872
873 if let Some(ref_id) = self_ref_id {
875 self.decode_memo.insert(ref_id.clone(), result.clone());
876 }
877
878 Ok(result)
879 }
880
881 type_ids::OBJECT => {
882 let result_object = LinoValue::Object(vec![]);
884 if let Some(ref_id) = self_ref_id {
885 self.decode_memo.insert(ref_id.clone(), result_object);
886 }
887
888 let mut obj = Vec::new();
890 for pair_link in values.iter().skip(1) {
891 if let LiNo::Link { values: pair, .. } = pair_link {
892 if pair.len() >= 2 {
893 let key = self.decode_link(&pair[0])?;
894 let value = self.decode_link(&pair[1])?;
895
896 if let LinoValue::String(k) = key {
898 obj.push((k, value));
899 }
900 }
901 }
902 }
903
904 let result = LinoValue::Object(obj);
905
906 if let Some(ref_id) = self_ref_id {
908 self.decode_memo.insert(ref_id.clone(), result.clone());
909 }
910
911 Ok(result)
912 }
913
914 unknown => Err(CodecError::UnknownType(unknown.to_string())),
915 }
916 }
917 }
918 }
919}
920
921fn is_compact_notation(notation: &str) -> bool {
927 let Some(first_line) = notation.lines().map(str::trim).find(|l| !l.is_empty()) else {
928 return false;
929 };
930
931 let Some(rest) = first_line.strip_prefix('(') else {
932 return false;
933 };
934
935 let mut tokens = rest
936 .split(|c: char| c.is_whitespace() || c == '(' || c == ')')
937 .filter(|t| !t.is_empty());
938
939 let Some(mut marker) = tokens.next() else {
940 return false;
941 };
942
943 if let Some(id) = marker.strip_suffix(':') {
945 if !id.starts_with("obj_") {
946 return false;
947 }
948 let Some(next) = tokens.next() else {
949 return false;
950 };
951 marker = next;
952 }
953
954 matches!(
955 marker,
956 type_ids::NULL
957 | type_ids::BOOL
958 | type_ids::INT
959 | type_ids::FLOAT
960 | type_ids::STR
961 | type_ids::ARRAY
962 | type_ids::OBJECT
963 )
964}
965
966thread_local! {
968 static DEFAULT_CODEC: std::cell::RefCell<ObjectCodec> = std::cell::RefCell::new(ObjectCodec::new());
969}
970
971pub fn encode(value: &LinoValue) -> String {
997 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode(value))
998}
999
1000pub fn encode_with_indent(value: &LinoValue, indent: &str) -> String {
1016 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_with_indent(value, indent))
1017}
1018
1019pub fn encode_compact(value: &LinoValue) -> String {
1045 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_compact(value))
1046}
1047
1048pub fn encode_obfuscated(value: &LinoValue) -> String {
1052 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_obfuscated(value))
1053}
1054
1055pub fn decode(notation: &str) -> Result<LinoValue, CodecError> {
1078 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode(notation))
1079}
1080
1081pub mod format {
1083 use super::{parse_lino_to_links, LiNo};
1084 use std::collections::HashMap;
1085
1086 #[derive(Debug, Clone, PartialEq, Eq)]
1088 pub enum FormatError {
1089 MissingField(String),
1091 InvalidInput(String),
1093 }
1094
1095 impl std::fmt::Display for FormatError {
1096 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1097 match self {
1098 FormatError::MissingField(field) => write!(f, "Missing required field: {}", field),
1099 FormatError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
1100 }
1101 }
1102 }
1103
1104 impl std::error::Error for FormatError {}
1105
1106 pub fn escape_reference(value: &str) -> String {
1118 let needs_escaping = value.chars().any(|c| {
1120 c.is_whitespace() || c == '(' || c == ')' || c == '\'' || c == '"' || c == ':'
1121 }) || value.contains('\n');
1122
1123 if !needs_escaping {
1124 return value.to_string();
1125 }
1126
1127 let has_single = value.contains('\'');
1128 let has_double = value.contains('"');
1129
1130 if has_single && !has_double {
1132 return format!("\"{}\"", value);
1133 }
1134
1135 if has_double && !has_single {
1137 return format!("'{}'", value);
1138 }
1139
1140 if has_single && has_double {
1142 let single_count = value.chars().filter(|&c| c == '\'').count();
1143 let double_count = value.chars().filter(|&c| c == '"').count();
1144
1145 if double_count < single_count {
1146 let escaped = value.replace('"', "\"\"");
1148 return format!("\"{}\"", escaped);
1149 }
1150 let escaped = value.replace('\'', "''");
1152 return format!("'{}'", escaped);
1153 }
1154
1155 format!("'{}'", value)
1157 }
1158
1159 pub fn unescape_reference(s: &str) -> String {
1171 s.replace("\"\"", "\"").replace("''", "'")
1172 }
1173
1174 fn format_indented_value(value: &str) -> String {
1179 let has_single = value.contains('\'');
1180 let has_double = value.contains('"');
1181
1182 if has_double && !has_single {
1184 return format!("'{}'", value);
1185 }
1186
1187 if has_single && !has_double {
1189 return format!("\"{}\"", value);
1190 }
1191
1192 if has_single && has_double {
1194 let escaped = value.replace('\'', "''");
1195 return format!("'{}'", escaped);
1196 }
1197
1198 format!("\"{}\"", value)
1200 }
1201
1202 pub fn format_indented<S: ::std::hash::BuildHasher>(
1237 id: &str,
1238 obj: &HashMap<String, String, S>,
1239 indent: &str,
1240 ) -> Result<String, FormatError> {
1241 if id.is_empty() {
1242 return Err(FormatError::MissingField("id".to_string()));
1243 }
1244
1245 let mut lines = vec![id.to_string()];
1246
1247 for (key, value) in obj {
1248 let escaped_key = escape_reference(key);
1249 let formatted_value = format_indented_value(value);
1250 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1251 }
1252
1253 Ok(lines.join("\n"))
1254 }
1255
1256 pub fn format_indented_ordered(
1271 id: &str,
1272 pairs: &[(&str, &str)],
1273 indent: &str,
1274 ) -> Result<String, FormatError> {
1275 if id.is_empty() {
1276 return Err(FormatError::MissingField("id".to_string()));
1277 }
1278
1279 let mut lines = vec![id.to_string()];
1280
1281 for (key, value) in pairs {
1282 let escaped_key = escape_reference(key);
1283 let formatted_value = format_indented_value(value);
1284 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1285 }
1286
1287 Ok(lines.join("\n"))
1288 }
1289
1290 pub fn parse_indented(text: &str) -> Result<(String, HashMap<String, String>), FormatError> {
1330 if text.is_empty() {
1331 return Err(FormatError::InvalidInput(
1332 "text is required for parse_indented".to_string(),
1333 ));
1334 }
1335
1336 let lines: Vec<&str> = text.lines().collect();
1337 if lines.is_empty() {
1338 return Err(FormatError::InvalidInput(
1339 "text must have at least one line (the identifier)".to_string(),
1340 ));
1341 }
1342
1343 let non_empty_lines: Vec<&str> = lines
1346 .iter()
1347 .filter(|l| !l.trim().is_empty())
1348 .copied()
1349 .collect();
1350
1351 if non_empty_lines.is_empty() {
1352 return Err(FormatError::InvalidInput(
1353 "text must have at least one non-empty line (the identifier)".to_string(),
1354 ));
1355 }
1356
1357 let first_line = non_empty_lines[0].trim();
1360 let lino_text = if first_line.ends_with(':') {
1361 non_empty_lines.join("\n")
1362 } else {
1363 format!("{}:\n{}", first_line, non_empty_lines[1..].join("\n"))
1364 };
1365
1366 let parsed = parse_lino_to_links(&lino_text)
1368 .map_err(|e| FormatError::InvalidInput(format!("Parse error: {:?}", e)))?;
1369
1370 if parsed.is_empty() {
1371 return Err(FormatError::InvalidInput(
1372 "Failed to parse indented Links Notation".to_string(),
1373 ));
1374 }
1375
1376 let main_link = &parsed[0];
1378 let (result_id, values) = match main_link {
1379 LiNo::Link { id, values } => (id.clone().unwrap_or_default(), values),
1380 LiNo::Ref(id) => (id.clone(), &vec![]),
1381 };
1382
1383 let mut obj = HashMap::new();
1384
1385 for child in values {
1387 if let LiNo::Link {
1388 values: child_values,
1389 ..
1390 } = child
1391 {
1392 if child_values.len() == 2 {
1393 let key_ref = &child_values[0];
1394 let value_ref = &child_values[1];
1395
1396 let key = match key_ref {
1398 LiNo::Ref(k) => k.clone(),
1399 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1400 };
1401
1402 let value = match value_ref {
1404 LiNo::Ref(v) => v.clone(),
1405 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1406 };
1407
1408 obj.insert(key, value);
1409 }
1410 }
1411 }
1412
1413 Ok((result_id, obj))
1414 }
1415}
1416
1417#[cfg(test)]
1418mod tests {
1419 use super::*;
1420
1421 #[test]
1422 fn test_roundtrip_null() {
1423 let original = LinoValue::Null;
1424 let encoded = encode(&original);
1425 let decoded = decode(&encoded).unwrap();
1426 assert_eq!(decoded, original);
1427 }
1428
1429 #[test]
1430 fn test_roundtrip_bool() {
1431 for value in [true, false] {
1432 let original = LinoValue::Bool(value);
1433 let encoded = encode(&original);
1434 let decoded = decode(&encoded).unwrap();
1435 assert_eq!(decoded, original);
1436 }
1437 }
1438
1439 #[test]
1440 fn test_roundtrip_int() {
1441 let test_values: Vec<i64> = vec![0, 1, -1, 42, -42, 123456789, -123456789];
1442 for value in test_values {
1443 let original = LinoValue::Int(value);
1444 let encoded = encode(&original);
1445 let decoded = decode(&encoded).unwrap();
1446 assert_eq!(decoded.as_int(), Some(value));
1447 }
1448 }
1449
1450 #[test]
1451 fn test_roundtrip_float() {
1452 let test_values: Vec<f64> = vec![0.0, 1.0, -1.0, 3.14, -3.14, 0.123456789, -999.999];
1453 for value in test_values {
1454 let original = LinoValue::Float(value);
1455 let encoded = encode(&original);
1456 let decoded = decode(&encoded).unwrap();
1457 let decoded_f = decoded.as_float().unwrap();
1458 assert!((decoded_f - value).abs() < 0.0001);
1459 }
1460 }
1461
1462 #[test]
1463 fn test_float_special_values() {
1464 let inf = LinoValue::Float(f64::INFINITY);
1466 let encoded = encode(&inf);
1467 let decoded = decode(&encoded).unwrap();
1468 let decoded_f = decoded.as_float().unwrap();
1469 assert!(decoded_f.is_infinite());
1470 assert!(decoded_f.is_sign_positive());
1471
1472 let neg_inf = LinoValue::Float(f64::NEG_INFINITY);
1474 let encoded = encode(&neg_inf);
1475 let decoded = decode(&encoded).unwrap();
1476 let decoded_f = decoded.as_float().unwrap();
1477 assert!(decoded_f.is_infinite());
1478 assert!(decoded_f.is_sign_negative());
1479
1480 let nan = LinoValue::Float(f64::NAN);
1482 let encoded = encode(&nan);
1483 let decoded = decode(&encoded).unwrap();
1484 let decoded_f = decoded.as_float().unwrap();
1485 assert!(decoded_f.is_nan());
1486 }
1487
1488 #[test]
1489 fn test_roundtrip_string() {
1490 let test_values = [
1491 "",
1492 "hello",
1493 "hello world",
1494 "Hello, World!",
1495 "multi\nline\nstring",
1496 "tab\tseparated",
1497 "special chars: @#$%^&*()",
1498 ];
1499 for value in test_values {
1500 let original = LinoValue::String(value.to_string());
1501 let encoded = encode(&original);
1502 let decoded = decode(&encoded).unwrap();
1503 assert_eq!(decoded.as_str(), Some(value));
1504 }
1505 }
1506
1507 #[test]
1508 fn test_string_with_quotes() {
1509 let test_values = [
1510 "string with 'single quotes'",
1511 "string with \"double quotes\"",
1512 "string with \"both\" 'quotes'",
1513 ];
1514 for value in test_values {
1515 let original = LinoValue::String(value.to_string());
1516 let encoded = encode(&original);
1517 let decoded = decode(&encoded).unwrap();
1518 assert_eq!(decoded.as_str(), Some(value));
1519 }
1520 }
1521
1522 #[test]
1523 fn test_unicode_string() {
1524 let value = "unicode: 你好世界 🌍";
1525 let original = LinoValue::String(value.to_string());
1526 let encoded = encode(&original);
1527 let decoded = decode(&encoded).unwrap();
1528 assert_eq!(decoded.as_str(), Some(value));
1529 }
1530
1531 #[test]
1532 fn test_roundtrip_empty_array() {
1533 let original = LinoValue::Array(vec![]);
1534 let encoded = encode(&original);
1535 let decoded = decode(&encoded).unwrap();
1536 assert_eq!(decoded, original);
1537 }
1538
1539 #[test]
1540 fn test_roundtrip_simple_array() {
1541 let original = LinoValue::Array(vec![
1542 LinoValue::Int(1),
1543 LinoValue::Int(2),
1544 LinoValue::Int(3),
1545 ]);
1546 let encoded = encode(&original);
1547 let decoded = decode(&encoded).unwrap();
1548 assert_eq!(decoded, original);
1549 }
1550
1551 #[test]
1552 fn test_roundtrip_mixed_array() {
1553 let original = LinoValue::Array(vec![
1554 LinoValue::Int(1),
1555 LinoValue::String("hello".to_string()),
1556 LinoValue::Bool(true),
1557 LinoValue::Null,
1558 ]);
1559 let encoded = encode(&original);
1560 let decoded = decode(&encoded).unwrap();
1561 assert_eq!(decoded, original);
1562 }
1563
1564 #[test]
1565 fn test_nested_arrays() {
1566 let original = LinoValue::Array(vec![LinoValue::Array(vec![])]);
1567 let encoded = encode(&original);
1568 let decoded = decode(&encoded).unwrap();
1569 assert_eq!(decoded, original);
1570
1571 let original2 = LinoValue::Array(vec![
1572 LinoValue::Array(vec![LinoValue::Int(1), LinoValue::Int(2)]),
1573 LinoValue::Array(vec![LinoValue::Int(3), LinoValue::Int(4)]),
1574 ]);
1575 let encoded2 = encode(&original2);
1576 let decoded2 = decode(&encoded2).unwrap();
1577 assert_eq!(decoded2, original2);
1578 }
1579
1580 #[test]
1581 fn test_roundtrip_empty_object() {
1582 let original = LinoValue::Object(vec![]);
1583 let encoded = encode(&original);
1584 let decoded = decode(&encoded).unwrap();
1585 assert_eq!(decoded, original);
1586 }
1587
1588 #[test]
1589 fn test_roundtrip_simple_object() {
1590 let original = LinoValue::object([("a", LinoValue::Int(1)), ("b", LinoValue::Int(2))]);
1591 let encoded = encode(&original);
1592 let decoded = decode(&encoded).unwrap();
1593 assert_eq!(decoded, original);
1594 }
1595
1596 #[test]
1597 fn test_nested_objects() {
1598 let original = LinoValue::object([(
1599 "user",
1600 LinoValue::object([
1601 ("name", LinoValue::String("Alice".to_string())),
1602 (
1603 "address",
1604 LinoValue::object([
1605 ("city", LinoValue::String("NYC".to_string())),
1606 ("zip", LinoValue::String("10001".to_string())),
1607 ]),
1608 ),
1609 ]),
1610 )]);
1611 let encoded = encode(&original);
1612 let decoded = decode(&encoded).unwrap();
1613 assert_eq!(decoded, original);
1614 }
1615
1616 #[test]
1617 fn test_complex_structure() {
1618 let original = LinoValue::object([
1619 ("id", LinoValue::Int(123)),
1620 ("name", LinoValue::String("Test Object".to_string())),
1621 ("active", LinoValue::Bool(true)),
1622 (
1623 "tags",
1624 LinoValue::array([
1625 LinoValue::String("tag1".to_string()),
1626 LinoValue::String("tag2".to_string()),
1627 LinoValue::String("tag3".to_string()),
1628 ]),
1629 ),
1630 (
1631 "metadata",
1632 LinoValue::object([
1633 ("created", LinoValue::String("2025-01-01".to_string())),
1634 ("modified", LinoValue::Null),
1635 ("count", LinoValue::Int(42)),
1636 ]),
1637 ),
1638 (
1639 "items",
1640 LinoValue::array([
1641 LinoValue::object([
1642 ("id", LinoValue::Int(1)),
1643 ("value", LinoValue::String("first".to_string())),
1644 ]),
1645 LinoValue::object([
1646 ("id", LinoValue::Int(2)),
1647 ("value", LinoValue::String("second".to_string())),
1648 ]),
1649 ]),
1650 ),
1651 ]);
1652 let encoded = encode(&original);
1653 let decoded = decode(&encoded).unwrap();
1654 assert_eq!(decoded, original);
1655 }
1656
1657 #[test]
1658 fn test_list_of_dicts() {
1659 let original = LinoValue::array([
1660 LinoValue::object([
1661 ("name", LinoValue::String("Alice".to_string())),
1662 ("age", LinoValue::Int(30)),
1663 ]),
1664 LinoValue::object([
1665 ("name", LinoValue::String("Bob".to_string())),
1666 ("age", LinoValue::Int(25)),
1667 ]),
1668 ]);
1669 let encoded = encode(&original);
1670 let decoded = decode(&encoded).unwrap();
1671 assert_eq!(decoded, original);
1672 }
1673
1674 #[test]
1675 fn test_dict_of_lists() {
1676 let original = LinoValue::object([
1677 (
1678 "numbers",
1679 LinoValue::array([LinoValue::Int(1), LinoValue::Int(2), LinoValue::Int(3)]),
1680 ),
1681 (
1682 "strings",
1683 LinoValue::array([
1684 LinoValue::String("a".to_string()),
1685 LinoValue::String("b".to_string()),
1686 LinoValue::String("c".to_string()),
1687 ]),
1688 ),
1689 ]);
1690 let encoded = encode(&original);
1691 let decoded = decode(&encoded).unwrap();
1692 assert_eq!(decoded, original);
1693 }
1694}
1695
1696#[cfg(test)]
1697mod format_tests {
1698 use super::format::*;
1699 use std::collections::HashMap;
1700
1701 #[test]
1702 fn test_escape_reference_simple_string() {
1703 assert_eq!(escape_reference("hello"), "hello");
1704 assert_eq!(escape_reference("world"), "world");
1705 }
1706
1707 #[test]
1708 fn test_escape_reference_string_with_spaces() {
1709 let result = escape_reference("hello world");
1710 assert!(result.starts_with('\'') || result.starts_with('"'));
1711 assert!(result.contains("hello world"));
1712 }
1713
1714 #[test]
1715 fn test_escape_reference_string_with_single_quotes() {
1716 let result = escape_reference("it's");
1717 assert_eq!(result, "\"it's\"");
1718 }
1719
1720 #[test]
1721 fn test_escape_reference_string_with_double_quotes() {
1722 let result = escape_reference("he said \"hello\"");
1723 assert_eq!(result, "'he said \"hello\"'");
1724 }
1725
1726 #[test]
1727 fn test_unescape_reference_doubled_quotes() {
1728 assert_eq!(
1729 unescape_reference("he said \"\"hello\"\""),
1730 "he said \"hello\""
1731 );
1732 assert_eq!(unescape_reference("it''s"), "it's");
1733 }
1734
1735 #[test]
1736 fn test_format_indented_ordered_basic() {
1737 let pairs = [
1738 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1739 ("status", "executed"),
1740 ("command", "echo test"),
1741 ("exitCode", "0"),
1742 ];
1743 let result =
1744 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1745 let lines: Vec<&str> = result.lines().collect();
1746 assert_eq!(lines[0], "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1747 assert_eq!(lines[1], " uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"");
1748 assert_eq!(lines[2], " status \"executed\"");
1749 assert_eq!(lines[3], " command \"echo test\"");
1750 assert_eq!(lines[4], " exitCode \"0\"");
1751 }
1752
1753 #[test]
1754 fn test_format_indented_value_with_quotes() {
1755 let pairs = [("message", "He said \"hello\"")];
1757 let result = format_indented_ordered("test-id", &pairs, " ").unwrap();
1758 let lines: Vec<&str> = result.lines().collect();
1759 assert_eq!(lines[1], " message 'He said \"hello\"'");
1760 }
1761
1762 #[test]
1763 fn test_format_indented_requires_id() {
1764 let mut obj = HashMap::new();
1765 obj.insert("key".to_string(), "value".to_string());
1766 let result = format_indented("", &obj, " ");
1767 assert!(result.is_err());
1768 }
1769
1770 #[test]
1771 fn test_parse_indented_basic() {
1772 let text = "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\n uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"\n status \"executed\"\n exitCode \"0\"";
1773 let (id, obj) = parse_indented(text).unwrap();
1774 assert_eq!(id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1775 assert_eq!(
1776 obj.get("uuid"),
1777 Some(&"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019".to_string())
1778 );
1779 assert_eq!(obj.get("status"), Some(&"executed".to_string()));
1780 assert_eq!(obj.get("exitCode"), Some(&"0".to_string()));
1781 }
1782
1783 #[test]
1784 fn test_parse_indented_with_quotes() {
1785 let text = "test-id\n message 'He said \"hello\"'";
1787 let (id, obj) = parse_indented(text).unwrap();
1788 assert_eq!(id, "test-id");
1789 assert_eq!(obj.get("message"), Some(&"He said \"hello\"".to_string()));
1790 }
1791
1792 #[test]
1793 fn test_parse_indented_empty_lines_skipped() {
1794 let text = "test-id\n\n key \"value\"\n\n another \"value2\"";
1795 let (id, obj) = parse_indented(text).unwrap();
1796 assert_eq!(id, "test-id");
1797 assert_eq!(obj.get("key"), Some(&"value".to_string()));
1798 assert_eq!(obj.get("another"), Some(&"value2".to_string()));
1799 }
1800
1801 #[test]
1802 fn test_parse_indented_requires_text() {
1803 let result = parse_indented("");
1804 assert!(result.is_err());
1805 }
1806
1807 #[test]
1808 fn test_roundtrip_format_indented() {
1809 let pairs = [
1810 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1811 ("status", "executed"),
1812 ("command", "echo test"),
1813 ("exitCode", "0"),
1814 ];
1815 let formatted =
1816 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1817 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1818
1819 assert_eq!(parsed_id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1820 for (key, value) in pairs {
1821 assert_eq!(parsed_obj.get(key), Some(&value.to_string()));
1822 }
1823 }
1824
1825 #[test]
1826 fn test_roundtrip_with_quotes() {
1827 let pairs = [("message", "He said \"hello\"")];
1828 let formatted = format_indented_ordered("test-id", &pairs, " ").unwrap();
1829 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1830
1831 assert_eq!(parsed_id, "test-id");
1832 assert_eq!(
1833 parsed_obj.get("message"),
1834 Some(&"He said \"hello\"".to_string())
1835 );
1836 }
1837}