1use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
52use links_notation::{LiNo, parse_lino_to_links};
53use std::collections::{HashMap, HashSet};
54use std::fmt;
55
56pub mod debug;
57pub mod readable;
58
59pub use readable::{BASE64_MARKER, DEFAULT_INDENT, ESCAPED_MARKER, OBJECT_MARKER};
60
61mod type_ids {
63 pub const NULL: &str = "null";
64 pub const BOOL: &str = "bool";
65 pub const INT: &str = "int";
66 pub const FLOAT: &str = "float";
67 pub const STR: &str = "str";
68 pub const ARRAY: &str = "array";
69 pub const OBJECT: &str = "object";
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum CodecError {
75 ParseError(String),
77 DecodeError(String),
79 UnknownType(String),
81}
82
83impl fmt::Display for CodecError {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 CodecError::ParseError(msg) => write!(f, "Parse error: {}", msg),
87 CodecError::DecodeError(msg) => write!(f, "Decode error: {}", msg),
88 CodecError::UnknownType(t) => write!(f, "Unknown type marker: {}", t),
89 }
90 }
91}
92
93impl std::error::Error for CodecError {}
94
95#[derive(Debug, Clone)]
100pub enum LinoValue {
101 Null,
103 Bool(bool),
105 Int(i64),
107 Float(f64),
109 String(String),
111 Array(Vec<LinoValue>),
113 Object(Vec<(String, LinoValue)>),
115}
116
117impl PartialEq for LinoValue {
118 fn eq(&self, other: &Self) -> bool {
119 match (self, other) {
120 (LinoValue::Null, LinoValue::Null) => true,
121 (LinoValue::Bool(a), LinoValue::Bool(b)) => a == b,
122 (LinoValue::Int(a), LinoValue::Int(b)) => a == b,
123 (LinoValue::Float(a), LinoValue::Float(b)) => {
124 if a.is_nan() && b.is_nan() {
126 true
127 } else {
128 a == b
129 }
130 }
131 (LinoValue::String(a), LinoValue::String(b)) => a == b,
132 (LinoValue::Array(a), LinoValue::Array(b)) => a == b,
133 (LinoValue::Object(a), LinoValue::Object(b)) => {
134 if a.len() != b.len() {
136 return false;
137 }
138 let a_map: HashMap<&str, &LinoValue> =
140 a.iter().map(|(k, v)| (k.as_str(), v)).collect();
141 let b_map: HashMap<&str, &LinoValue> =
142 b.iter().map(|(k, v)| (k.as_str(), v)).collect();
143 a_map == b_map
144 }
145 _ => false,
146 }
147 }
148}
149
150impl LinoValue {
151 pub fn object<I, K, V>(iter: I) -> Self
153 where
154 I: IntoIterator<Item = (K, V)>,
155 K: Into<String>,
156 V: Into<LinoValue>,
157 {
158 LinoValue::Object(
159 iter.into_iter()
160 .map(|(k, v)| (k.into(), v.into()))
161 .collect(),
162 )
163 }
164
165 pub fn array<I, V>(iter: I) -> Self
167 where
168 I: IntoIterator<Item = V>,
169 V: Into<LinoValue>,
170 {
171 LinoValue::Array(iter.into_iter().map(|v| v.into()).collect())
172 }
173
174 pub fn is_null(&self) -> bool {
176 matches!(self, LinoValue::Null)
177 }
178
179 pub fn as_bool(&self) -> Option<bool> {
181 match self {
182 LinoValue::Bool(b) => Some(*b),
183 _ => None,
184 }
185 }
186
187 pub fn as_int(&self) -> Option<i64> {
189 match self {
190 LinoValue::Int(i) => Some(*i),
191 _ => None,
192 }
193 }
194
195 pub fn as_float(&self) -> Option<f64> {
197 match self {
198 LinoValue::Float(f) => Some(*f),
199 LinoValue::Int(i) => Some(*i as f64),
200 _ => None,
201 }
202 }
203
204 pub fn as_str(&self) -> Option<&str> {
206 match self {
207 LinoValue::String(s) => Some(s),
208 _ => None,
209 }
210 }
211
212 pub fn as_array(&self) -> Option<&Vec<LinoValue>> {
214 match self {
215 LinoValue::Array(a) => Some(a),
216 _ => None,
217 }
218 }
219
220 pub fn as_object(&self) -> Option<&Vec<(String, LinoValue)>> {
222 match self {
223 LinoValue::Object(o) => Some(o),
224 _ => None,
225 }
226 }
227
228 pub fn get(&self, key: &str) -> Option<&LinoValue> {
230 match self {
231 LinoValue::Object(o) => o.iter().find(|(k, _)| k == key).map(|(_, v)| v),
232 _ => None,
233 }
234 }
235
236 pub fn get_index(&self, index: usize) -> Option<&LinoValue> {
238 match self {
239 LinoValue::Array(a) => a.get(index),
240 _ => None,
241 }
242 }
243}
244
245impl From<()> for LinoValue {
247 fn from(_: ()) -> Self {
248 LinoValue::Null
249 }
250}
251
252impl From<bool> for LinoValue {
253 fn from(b: bool) -> Self {
254 LinoValue::Bool(b)
255 }
256}
257
258impl From<i32> for LinoValue {
259 fn from(i: i32) -> Self {
260 LinoValue::Int(i as i64)
261 }
262}
263
264impl From<i64> for LinoValue {
265 fn from(i: i64) -> Self {
266 LinoValue::Int(i)
267 }
268}
269
270impl From<f64> for LinoValue {
271 fn from(f: f64) -> Self {
272 LinoValue::Float(f)
273 }
274}
275
276impl From<&str> for LinoValue {
277 fn from(s: &str) -> Self {
278 LinoValue::String(s.to_string())
279 }
280}
281
282impl From<String> for LinoValue {
283 fn from(s: String) -> Self {
284 LinoValue::String(s)
285 }
286}
287
288impl<T: Into<LinoValue>> From<Vec<T>> for LinoValue {
289 fn from(v: Vec<T>) -> Self {
290 LinoValue::Array(v.into_iter().map(|x| x.into()).collect())
291 }
292}
293
294impl<T: Into<LinoValue>> From<Option<T>> for LinoValue {
295 fn from(opt: Option<T>) -> Self {
296 match opt {
297 Some(v) => v.into(),
298 None => LinoValue::Null,
299 }
300 }
301}
302
303pub struct ObjectCodec {
309 encode_counter: usize,
311 encode_memo: HashMap<String, String>,
313 needs_id: HashSet<String>,
315 all_definitions: Vec<(String, LiNo<String>)>,
317 decode_memo: HashMap<String, LinoValue>,
319 all_links: Vec<LiNo<String>>,
321}
322
323impl Default for ObjectCodec {
324 fn default() -> Self {
325 Self::new()
326 }
327}
328
329impl ObjectCodec {
330 pub fn new() -> Self {
332 ObjectCodec {
333 encode_counter: 0,
334 encode_memo: HashMap::new(),
335 needs_id: HashSet::new(),
336 all_definitions: Vec::new(),
337 decode_memo: HashMap::new(),
338 all_links: Vec::new(),
339 }
340 }
341
342 fn reset_encode_state(&mut self) {
344 self.encode_counter = 0;
345 self.encode_memo.clear();
346 self.needs_id.clear();
347 self.all_definitions.clear();
348 }
349
350 fn reset_decode_state(&mut self) {
352 self.decode_memo.clear();
353 self.all_links.clear();
354 }
355
356 fn make_link(&self, parts: &[&str]) -> LiNo<String> {
358 let values: Vec<LiNo<String>> = parts.iter().map(|p| LiNo::Ref(p.to_string())).collect();
359 LiNo::Link { id: None, values }
360 }
361
362 fn make_ref(&self, id: &str) -> LiNo<String> {
364 LiNo::Ref(id.to_string())
365 }
366
367 fn object_key(&self, value: &LinoValue) -> String {
369 format!("{:p}", value)
371 }
372
373 fn find_objects_needing_ids(&mut self, value: &LinoValue, seen: &mut HashMap<String, bool>) {
375 match value {
377 LinoValue::Array(arr) => {
378 let key = self.object_key(value);
379
380 if seen.contains_key(&key) {
381 self.needs_id.insert(key);
383 return;
384 }
385
386 seen.insert(key, true);
387
388 for item in arr {
389 self.find_objects_needing_ids(item, seen);
390 }
391 }
392 LinoValue::Object(obj) => {
393 let key = self.object_key(value);
394
395 if seen.contains_key(&key) {
396 self.needs_id.insert(key);
398 return;
399 }
400
401 seen.insert(key, true);
402
403 for (_, v) in obj {
404 self.find_objects_needing_ids(v, seen);
405 }
406 }
407 _ => {}
408 }
409 }
410
411 pub fn encode(&mut self, value: &LinoValue) -> String {
425 readable::encode(value, DEFAULT_INDENT)
426 }
427
428 pub fn encode_with_indent(&mut self, value: &LinoValue, indent: &str) -> String {
435 readable::encode(value, indent)
436 }
437
438 pub fn encode_line(&mut self, value: &LinoValue) -> String {
453 readable::encode_line(value)
454 }
455
456 pub fn encode_compact(&mut self, value: &LinoValue) -> String {
470 self.reset_encode_state();
471
472 let mut seen = HashMap::new();
474 self.find_objects_needing_ids(value, &mut seen);
475
476 let mut visited = HashSet::new();
478 let main_link = self.encode_value(value, &mut visited, 0);
479
480 if !self.all_definitions.is_empty() {
482 let mut all_links = vec![main_link];
483
484 for (ref_id, link) in &self.all_definitions {
486 let main_id = match &all_links[0] {
487 LiNo::Link { id: Some(id), .. } => Some(id.clone()),
488 _ => None,
489 };
490 if main_id.as_ref() != Some(ref_id) {
491 all_links.push(link.clone());
492 }
493 }
494
495 all_links
497 .iter()
498 .map(Self::format_link)
499 .collect::<Vec<_>>()
500 .join("\n")
501 } else {
502 Self::format_link(&main_link)
503 }
504 }
505
506 pub fn encode_obfuscated(&mut self, value: &LinoValue) -> String {
511 self.encode_compact(value)
512 }
513
514 fn format_link(link: &LiNo<String>) -> String {
516 match link {
517 LiNo::Ref(s) => s.clone(),
518 LiNo::Link { id, values } => {
519 let inner: Vec<String> = values.iter().map(Self::format_link).collect();
520
521 if let Some(link_id) = id {
522 if inner.is_empty() {
523 format!("({}:)", link_id)
524 } else {
525 format!("({}: {})", link_id, inner.join(" "))
526 }
527 } else if inner.is_empty() {
528 "()".to_string()
529 } else {
530 format!("({})", inner.join(" "))
531 }
532 }
533 }
534 }
535
536 fn encode_value(
538 &mut self,
539 value: &LinoValue,
540 visited: &mut HashSet<String>,
541 depth: usize,
542 ) -> LiNo<String> {
543 match value {
544 LinoValue::Null => self.make_link(&[type_ids::NULL]),
545
546 LinoValue::Bool(b) => {
547 if *b {
548 self.make_link(&[type_ids::BOOL, "true"])
549 } else {
550 self.make_link(&[type_ids::BOOL, "false"])
551 }
552 }
553
554 LinoValue::Int(i) => self.make_link(&[type_ids::INT, &i.to_string()]),
555
556 LinoValue::Float(f) => {
557 if f.is_nan() {
558 self.make_link(&[type_ids::FLOAT, "NaN"])
559 } else if f.is_infinite() {
560 if f.is_sign_positive() {
561 self.make_link(&[type_ids::FLOAT, "Infinity"])
562 } else {
563 self.make_link(&[type_ids::FLOAT, "-Infinity"])
564 }
565 } else {
566 self.make_link(&[type_ids::FLOAT, &f.to_string()])
567 }
568 }
569
570 LinoValue::String(s) => {
571 let b64_encoded = BASE64.encode(s.as_bytes());
572 self.make_link(&[type_ids::STR, &b64_encoded])
573 }
574
575 LinoValue::Array(arr) => {
576 let obj_key = self.object_key(value);
577
578 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
580 return self.make_ref(&ref_id);
581 }
582
583 let needs_id = self.needs_id.contains(&obj_key);
585
586 if needs_id {
587 if visited.contains(&obj_key) {
589 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
591 return self.make_ref(ref_id);
592 }
593 }
594
595 let ref_id = format!("obj_{}", self.encode_counter);
597 self.encode_counter += 1;
598 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
599 visited.insert(obj_key.clone());
600
601 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
603 for item in arr {
604 let item_link = self.encode_value(item, visited, depth + 1);
605 parts.push(item_link);
606 }
607
608 let definition = LiNo::Link {
609 id: Some(ref_id.clone()),
610 values: parts,
611 };
612
613 if depth > 0 {
615 self.all_definitions.push((ref_id.clone(), definition));
616 return self.make_ref(&ref_id);
617 }
618
619 definition
620 } else {
621 let mut parts: Vec<LiNo<String>> = vec![LiNo::Ref(type_ids::ARRAY.to_string())];
623 for item in arr {
624 let item_link = self.encode_value(item, visited, depth + 1);
625 parts.push(item_link);
626 }
627 LiNo::Link {
628 id: None,
629 values: parts,
630 }
631 }
632 }
633
634 LinoValue::Object(obj) => {
635 let obj_key = self.object_key(value);
636
637 if let Some(ref_id) = self.encode_memo.get(&obj_key).cloned() {
639 return self.make_ref(&ref_id);
640 }
641
642 let needs_id = self.needs_id.contains(&obj_key);
644
645 if needs_id {
646 if visited.contains(&obj_key) {
648 if let Some(ref_id) = self.encode_memo.get(&obj_key) {
650 return self.make_ref(ref_id);
651 }
652 }
653
654 let ref_id = format!("obj_{}", self.encode_counter);
656 self.encode_counter += 1;
657 self.encode_memo.insert(obj_key.clone(), ref_id.clone());
658 visited.insert(obj_key.clone());
659
660 let mut parts: Vec<LiNo<String>> =
662 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
663 for (k, v) in obj {
664 let key_link =
665 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
666 let value_link = self.encode_value(v, visited, depth + 1);
667 let pair = LiNo::Link {
668 id: None,
669 values: vec![key_link, value_link],
670 };
671 parts.push(pair);
672 }
673
674 let definition = LiNo::Link {
675 id: Some(ref_id.clone()),
676 values: parts,
677 };
678
679 if depth > 0 {
681 self.all_definitions.push((ref_id.clone(), definition));
682 return self.make_ref(&ref_id);
683 }
684
685 definition
686 } else {
687 let mut parts: Vec<LiNo<String>> =
689 vec![LiNo::Ref(type_ids::OBJECT.to_string())];
690 for (k, v) in obj {
691 let key_link =
692 self.encode_value(&LinoValue::String(k.clone()), visited, depth + 1);
693 let value_link = self.encode_value(v, visited, depth + 1);
694 let pair = LiNo::Link {
695 id: None,
696 values: vec![key_link, value_link],
697 };
698 parts.push(pair);
699 }
700 LiNo::Link {
701 id: None,
702 values: parts,
703 }
704 }
705 }
706 }
707 }
708
709 pub fn decode(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
722 if notation.trim().is_empty() {
723 return Ok(LinoValue::Null);
724 }
725
726 if is_compact_notation(notation) {
727 crate::debug::trace("codec.decode", || "compact notation detected".to_string());
728 return self.decode_compact(notation);
729 }
730
731 crate::debug::trace("codec.decode", || "readable notation detected".to_string());
732 readable::decode(notation)
733 }
734
735 pub fn decode_line(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
749 readable::decode_line(notation)
750 }
751
752 pub fn decode_compact(&mut self, notation: &str) -> Result<LinoValue, CodecError> {
762 self.reset_decode_state();
763
764 let links = parse_lino_to_links(notation)
765 .map_err(|e| CodecError::ParseError(format!("{:?}", e)))?;
766
767 if links.is_empty() {
768 return Ok(LinoValue::Null);
769 }
770
771 if links.len() > 1 {
773 self.all_links = links.clone();
774 }
775
776 self.decode_link(&links[0])
778 }
779
780 fn decode_link(&mut self, link: &LiNo<String>) -> Result<LinoValue, CodecError> {
782 match link {
783 LiNo::Ref(id) => {
784 if let Some(value) = self.decode_memo.get(id) {
786 return Ok(value.clone());
787 }
788
789 if id.starts_with("obj_") && !self.all_links.is_empty() {
791 for other_link in self.all_links.clone() {
793 if let LiNo::Link {
794 id: Some(link_id), ..
795 } = &other_link
796 {
797 if link_id == id {
798 return self.decode_link(&other_link);
799 }
800 }
801 }
802 let result = LinoValue::Array(vec![]);
804 self.decode_memo.insert(id.clone(), result.clone());
805 return Ok(result);
806 }
807
808 match id.as_str() {
810 type_ids::NULL => return Ok(LinoValue::Null),
811 type_ids::ARRAY => return Ok(LinoValue::Array(vec![])),
812 type_ids::OBJECT => return Ok(LinoValue::Object(vec![])),
813 type_ids::STR => return Ok(LinoValue::String(String::new())),
814 _ => {}
815 }
816
817 Ok(LinoValue::String(id.clone()))
819 }
820
821 LiNo::Link { id, values } => {
822 let self_ref_id = id.as_ref().filter(|i| i.starts_with("obj_"));
824 if let Some(ref_id) = self_ref_id {
825 if let Some(value) = self.decode_memo.get(ref_id) {
826 return Ok(value.clone());
827 }
828 }
829
830 if values.is_empty() {
831 return Ok(LinoValue::Null);
832 }
833
834 let type_marker = match &values[0] {
836 LiNo::Ref(t) => t.as_str(),
837 LiNo::Link { .. } => return Ok(LinoValue::Null),
838 };
839
840 match type_marker {
841 type_ids::NULL => Ok(LinoValue::Null),
842
843 type_ids::BOOL => {
844 if values.len() > 1 {
845 if let LiNo::Ref(val) = &values[1] {
846 return Ok(LinoValue::Bool(val.eq_ignore_ascii_case("true")));
847 }
848 }
849 Ok(LinoValue::Bool(false))
850 }
851
852 type_ids::INT => {
853 if values.len() > 1 {
854 if let LiNo::Ref(val) = &values[1] {
855 if let Ok(i) = val.parse::<i64>() {
856 return Ok(LinoValue::Int(i));
857 }
858 }
859 }
860 Ok(LinoValue::Int(0))
861 }
862
863 type_ids::FLOAT => {
864 if values.len() > 1 {
865 if let LiNo::Ref(val) = &values[1] {
866 return match val.as_str() {
867 "NaN" => Ok(LinoValue::Float(f64::NAN)),
868 "Infinity" => Ok(LinoValue::Float(f64::INFINITY)),
869 "-Infinity" => Ok(LinoValue::Float(f64::NEG_INFINITY)),
870 s => {
871 if let Ok(f) = s.parse::<f64>() {
872 Ok(LinoValue::Float(f))
873 } else {
874 Ok(LinoValue::Float(0.0))
875 }
876 }
877 };
878 }
879 }
880 Ok(LinoValue::Float(0.0))
881 }
882
883 type_ids::STR => {
884 if values.len() > 1 {
885 if let LiNo::Ref(b64_str) = &values[1] {
886 if let Ok(bytes) = BASE64.decode(b64_str) {
887 if let Ok(s) = String::from_utf8(bytes) {
888 return Ok(LinoValue::String(s));
889 }
890 }
891 return Ok(LinoValue::String(b64_str.clone()));
893 }
894 }
895 Ok(LinoValue::String(String::new()))
896 }
897
898 type_ids::ARRAY => {
899 let result_array = LinoValue::Array(vec![]);
901 if let Some(ref_id) = self_ref_id {
902 self.decode_memo.insert(ref_id.clone(), result_array);
903 }
904
905 let mut items = Vec::new();
907 for item_link in values.iter().skip(1) {
908 let decoded = self.decode_link(item_link)?;
909 items.push(decoded);
910 }
911
912 let result = LinoValue::Array(items);
913
914 if let Some(ref_id) = self_ref_id {
916 self.decode_memo.insert(ref_id.clone(), result.clone());
917 }
918
919 Ok(result)
920 }
921
922 type_ids::OBJECT => {
923 let result_object = LinoValue::Object(vec![]);
925 if let Some(ref_id) = self_ref_id {
926 self.decode_memo.insert(ref_id.clone(), result_object);
927 }
928
929 let mut obj = Vec::new();
931 for pair_link in values.iter().skip(1) {
932 if let LiNo::Link { values: pair, .. } = pair_link {
933 if pair.len() >= 2 {
934 let key = self.decode_link(&pair[0])?;
935 let value = self.decode_link(&pair[1])?;
936
937 if let LinoValue::String(k) = key {
939 obj.push((k, value));
940 }
941 }
942 }
943 }
944
945 let result = LinoValue::Object(obj);
946
947 if let Some(ref_id) = self_ref_id {
949 self.decode_memo.insert(ref_id.clone(), result.clone());
950 }
951
952 Ok(result)
953 }
954
955 unknown => Err(CodecError::UnknownType(unknown.to_string())),
956 }
957 }
958 }
959 }
960}
961
962const COMPACT_TYPE_MARKERS: [&str; 10] = [
974 type_ids::NULL,
975 "None",
976 type_ids::BOOL,
977 type_ids::INT,
978 type_ids::FLOAT,
979 type_ids::STR,
980 type_ids::ARRAY,
981 "list",
982 type_ids::OBJECT,
983 "dict",
984];
985
986const EMPTY_BODY_MARKERS: [&str; 2] = [type_ids::NULL, "None"];
989
990fn is_compact_notation(notation: &str) -> bool {
991 let Some(first_line) = notation.lines().map(str::trim).find(|l| !l.is_empty()) else {
992 return false;
993 };
994
995 let Some(rest) = first_line.strip_prefix('(') else {
996 return false;
997 };
998
999 let (mut marker, mut rest) = split_token(rest.trim_start());
1002
1003 if let Some(id) = marker.strip_suffix(':') {
1005 if !id.starts_with("obj_") {
1006 return false;
1007 }
1008 (marker, rest) = split_token(rest.trim_start());
1009 }
1010
1011 if !COMPACT_TYPE_MARKERS.contains(&marker) {
1012 return false;
1013 }
1014
1015 if EMPTY_BODY_MARKERS.contains(&marker) {
1018 return rest.trim_start().starts_with(')');
1019 }
1020
1021 true
1022}
1023
1024fn split_token(input: &str) -> (&str, &str) {
1027 let end = input
1028 .find(|c: char| c.is_whitespace() || c == '(' || c == ')')
1029 .unwrap_or(input.len());
1030 input.split_at(end)
1031}
1032
1033thread_local! {
1035 static DEFAULT_CODEC: std::cell::RefCell<ObjectCodec> = std::cell::RefCell::new(ObjectCodec::new());
1036}
1037
1038pub fn encode(value: &LinoValue) -> String {
1064 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode(value))
1065}
1066
1067pub fn encode_with_indent(value: &LinoValue, indent: &str) -> String {
1083 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_with_indent(value, indent))
1084}
1085
1086pub fn encode_line(value: &LinoValue) -> String {
1117 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_line(value))
1118}
1119
1120pub fn decode_line(notation: &str) -> Result<LinoValue, CodecError> {
1155 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode_line(notation))
1156}
1157
1158pub fn encode_compact(value: &LinoValue) -> String {
1184 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_compact(value))
1185}
1186
1187pub fn encode_obfuscated(value: &LinoValue) -> String {
1191 DEFAULT_CODEC.with(|codec| codec.borrow_mut().encode_obfuscated(value))
1192}
1193
1194pub fn decode(notation: &str) -> Result<LinoValue, CodecError> {
1217 DEFAULT_CODEC.with(|codec| codec.borrow_mut().decode(notation))
1218}
1219
1220pub mod format {
1222 use super::{LiNo, parse_lino_to_links};
1223 use std::collections::HashMap;
1224
1225 #[derive(Debug, Clone, PartialEq, Eq)]
1227 pub enum FormatError {
1228 MissingField(String),
1230 InvalidInput(String),
1232 }
1233
1234 impl std::fmt::Display for FormatError {
1235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1236 match self {
1237 FormatError::MissingField(field) => write!(f, "Missing required field: {}", field),
1238 FormatError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
1239 }
1240 }
1241 }
1242
1243 impl std::error::Error for FormatError {}
1244
1245 pub fn escape_reference(value: &str) -> String {
1257 let needs_escaping = value.chars().any(|c| {
1259 c.is_whitespace() || c == '(' || c == ')' || c == '\'' || c == '"' || c == ':'
1260 }) || value.contains('\n');
1261
1262 if !needs_escaping {
1263 return value.to_string();
1264 }
1265
1266 let has_single = value.contains('\'');
1267 let has_double = value.contains('"');
1268
1269 if has_single && !has_double {
1271 return format!("\"{}\"", value);
1272 }
1273
1274 if has_double && !has_single {
1276 return format!("'{}'", value);
1277 }
1278
1279 if has_single && has_double {
1281 let single_count = value.chars().filter(|&c| c == '\'').count();
1282 let double_count = value.chars().filter(|&c| c == '"').count();
1283
1284 if double_count < single_count {
1285 let escaped = value.replace('"', "\"\"");
1287 return format!("\"{}\"", escaped);
1288 }
1289 let escaped = value.replace('\'', "''");
1291 return format!("'{}'", escaped);
1292 }
1293
1294 format!("'{}'", value)
1296 }
1297
1298 pub fn unescape_reference(s: &str) -> String {
1310 s.replace("\"\"", "\"").replace("''", "'")
1311 }
1312
1313 fn format_indented_value(value: &str) -> String {
1318 let has_single = value.contains('\'');
1319 let has_double = value.contains('"');
1320
1321 if has_double && !has_single {
1323 return format!("'{}'", value);
1324 }
1325
1326 if has_single && !has_double {
1328 return format!("\"{}\"", value);
1329 }
1330
1331 if has_single && has_double {
1333 let escaped = value.replace('\'', "''");
1334 return format!("'{}'", escaped);
1335 }
1336
1337 format!("\"{}\"", value)
1339 }
1340
1341 pub fn format_indented<S: ::std::hash::BuildHasher>(
1376 id: &str,
1377 obj: &HashMap<String, String, S>,
1378 indent: &str,
1379 ) -> Result<String, FormatError> {
1380 if id.is_empty() {
1381 return Err(FormatError::MissingField("id".to_string()));
1382 }
1383
1384 let mut lines = vec![id.to_string()];
1385
1386 for (key, value) in obj {
1387 let escaped_key = escape_reference(key);
1388 let formatted_value = format_indented_value(value);
1389 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1390 }
1391
1392 Ok(lines.join("\n"))
1393 }
1394
1395 pub fn format_indented_ordered(
1410 id: &str,
1411 pairs: &[(&str, &str)],
1412 indent: &str,
1413 ) -> Result<String, FormatError> {
1414 if id.is_empty() {
1415 return Err(FormatError::MissingField("id".to_string()));
1416 }
1417
1418 let mut lines = vec![id.to_string()];
1419
1420 for (key, value) in pairs {
1421 let escaped_key = escape_reference(key);
1422 let formatted_value = format_indented_value(value);
1423 lines.push(format!("{}{} {}", indent, escaped_key, formatted_value));
1424 }
1425
1426 Ok(lines.join("\n"))
1427 }
1428
1429 pub fn parse_indented(text: &str) -> Result<(String, HashMap<String, String>), FormatError> {
1469 if text.is_empty() {
1470 return Err(FormatError::InvalidInput(
1471 "text is required for parse_indented".to_string(),
1472 ));
1473 }
1474
1475 let lines: Vec<&str> = text.lines().collect();
1476 if lines.is_empty() {
1477 return Err(FormatError::InvalidInput(
1478 "text must have at least one line (the identifier)".to_string(),
1479 ));
1480 }
1481
1482 let non_empty_lines: Vec<&str> = lines
1485 .iter()
1486 .filter(|l| !l.trim().is_empty())
1487 .copied()
1488 .collect();
1489
1490 if non_empty_lines.is_empty() {
1491 return Err(FormatError::InvalidInput(
1492 "text must have at least one non-empty line (the identifier)".to_string(),
1493 ));
1494 }
1495
1496 let first_line = non_empty_lines[0].trim();
1499 let lino_text = if first_line.ends_with(':') {
1500 non_empty_lines.join("\n")
1501 } else {
1502 format!("{}:\n{}", first_line, non_empty_lines[1..].join("\n"))
1503 };
1504
1505 let parsed = parse_lino_to_links(&lino_text)
1507 .map_err(|e| FormatError::InvalidInput(format!("Parse error: {:?}", e)))?;
1508
1509 if parsed.is_empty() {
1510 return Err(FormatError::InvalidInput(
1511 "Failed to parse indented Links Notation".to_string(),
1512 ));
1513 }
1514
1515 let main_link = &parsed[0];
1517 let (result_id, values) = match main_link {
1518 LiNo::Link { id, values } => (id.clone().unwrap_or_default(), values),
1519 LiNo::Ref(id) => (id.clone(), &vec![]),
1520 };
1521
1522 let mut obj = HashMap::new();
1523
1524 for child in values {
1526 if let LiNo::Link {
1527 values: child_values,
1528 ..
1529 } = child
1530 {
1531 if child_values.len() == 2 {
1532 let key_ref = &child_values[0];
1533 let value_ref = &child_values[1];
1534
1535 let key = match key_ref {
1537 LiNo::Ref(k) => k.clone(),
1538 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1539 };
1540
1541 let value = match value_ref {
1543 LiNo::Ref(v) => v.clone(),
1544 LiNo::Link { id, .. } => id.clone().unwrap_or_default(),
1545 };
1546
1547 obj.insert(key, value);
1548 }
1549 }
1550 }
1551
1552 Ok((result_id, obj))
1553 }
1554}
1555
1556#[cfg(test)]
1557mod tests {
1558 use super::*;
1559
1560 #[test]
1561 fn test_roundtrip_null() {
1562 let original = LinoValue::Null;
1563 let encoded = encode(&original);
1564 let decoded = decode(&encoded).unwrap();
1565 assert_eq!(decoded, original);
1566 }
1567
1568 #[test]
1569 fn test_roundtrip_bool() {
1570 for value in [true, false] {
1571 let original = LinoValue::Bool(value);
1572 let encoded = encode(&original);
1573 let decoded = decode(&encoded).unwrap();
1574 assert_eq!(decoded, original);
1575 }
1576 }
1577
1578 #[test]
1579 fn test_roundtrip_int() {
1580 let test_values: Vec<i64> = vec![0, 1, -1, 42, -42, 123456789, -123456789];
1581 for value in test_values {
1582 let original = LinoValue::Int(value);
1583 let encoded = encode(&original);
1584 let decoded = decode(&encoded).unwrap();
1585 assert_eq!(decoded.as_int(), Some(value));
1586 }
1587 }
1588
1589 #[test]
1590 fn test_roundtrip_float() {
1591 let test_values: Vec<f64> = vec![0.0, 1.0, -1.0, 3.14, -3.14, 0.123456789, -999.999];
1592 for value in test_values {
1593 let original = LinoValue::Float(value);
1594 let encoded = encode(&original);
1595 let decoded = decode(&encoded).unwrap();
1596 let decoded_f = decoded.as_float().unwrap();
1597 assert!((decoded_f - value).abs() < 0.0001);
1598 }
1599 }
1600
1601 #[test]
1602 fn test_float_special_values() {
1603 let inf = LinoValue::Float(f64::INFINITY);
1605 let encoded = encode(&inf);
1606 let decoded = decode(&encoded).unwrap();
1607 let decoded_f = decoded.as_float().unwrap();
1608 assert!(decoded_f.is_infinite());
1609 assert!(decoded_f.is_sign_positive());
1610
1611 let neg_inf = LinoValue::Float(f64::NEG_INFINITY);
1613 let encoded = encode(&neg_inf);
1614 let decoded = decode(&encoded).unwrap();
1615 let decoded_f = decoded.as_float().unwrap();
1616 assert!(decoded_f.is_infinite());
1617 assert!(decoded_f.is_sign_negative());
1618
1619 let nan = LinoValue::Float(f64::NAN);
1621 let encoded = encode(&nan);
1622 let decoded = decode(&encoded).unwrap();
1623 let decoded_f = decoded.as_float().unwrap();
1624 assert!(decoded_f.is_nan());
1625 }
1626
1627 #[test]
1628 fn test_roundtrip_string() {
1629 let test_values = [
1630 "",
1631 "hello",
1632 "hello world",
1633 "Hello, World!",
1634 "multi\nline\nstring",
1635 "tab\tseparated",
1636 "special chars: @#$%^&*()",
1637 ];
1638 for value in test_values {
1639 let original = LinoValue::String(value.to_string());
1640 let encoded = encode(&original);
1641 let decoded = decode(&encoded).unwrap();
1642 assert_eq!(decoded.as_str(), Some(value));
1643 }
1644 }
1645
1646 #[test]
1647 fn test_string_with_quotes() {
1648 let test_values = [
1649 "string with 'single quotes'",
1650 "string with \"double quotes\"",
1651 "string with \"both\" 'quotes'",
1652 ];
1653 for value in test_values {
1654 let original = LinoValue::String(value.to_string());
1655 let encoded = encode(&original);
1656 let decoded = decode(&encoded).unwrap();
1657 assert_eq!(decoded.as_str(), Some(value));
1658 }
1659 }
1660
1661 #[test]
1662 fn test_unicode_string() {
1663 let value = "unicode: 你好世界 🌍";
1664 let original = LinoValue::String(value.to_string());
1665 let encoded = encode(&original);
1666 let decoded = decode(&encoded).unwrap();
1667 assert_eq!(decoded.as_str(), Some(value));
1668 }
1669
1670 #[test]
1671 fn test_roundtrip_empty_array() {
1672 let original = LinoValue::Array(vec![]);
1673 let encoded = encode(&original);
1674 let decoded = decode(&encoded).unwrap();
1675 assert_eq!(decoded, original);
1676 }
1677
1678 #[test]
1679 fn test_roundtrip_simple_array() {
1680 let original = LinoValue::Array(vec![
1681 LinoValue::Int(1),
1682 LinoValue::Int(2),
1683 LinoValue::Int(3),
1684 ]);
1685 let encoded = encode(&original);
1686 let decoded = decode(&encoded).unwrap();
1687 assert_eq!(decoded, original);
1688 }
1689
1690 #[test]
1691 fn test_roundtrip_mixed_array() {
1692 let original = LinoValue::Array(vec![
1693 LinoValue::Int(1),
1694 LinoValue::String("hello".to_string()),
1695 LinoValue::Bool(true),
1696 LinoValue::Null,
1697 ]);
1698 let encoded = encode(&original);
1699 let decoded = decode(&encoded).unwrap();
1700 assert_eq!(decoded, original);
1701 }
1702
1703 #[test]
1704 fn test_nested_arrays() {
1705 let original = LinoValue::Array(vec![LinoValue::Array(vec![])]);
1706 let encoded = encode(&original);
1707 let decoded = decode(&encoded).unwrap();
1708 assert_eq!(decoded, original);
1709
1710 let original2 = LinoValue::Array(vec![
1711 LinoValue::Array(vec![LinoValue::Int(1), LinoValue::Int(2)]),
1712 LinoValue::Array(vec![LinoValue::Int(3), LinoValue::Int(4)]),
1713 ]);
1714 let encoded2 = encode(&original2);
1715 let decoded2 = decode(&encoded2).unwrap();
1716 assert_eq!(decoded2, original2);
1717 }
1718
1719 #[test]
1720 fn test_roundtrip_empty_object() {
1721 let original = LinoValue::Object(vec![]);
1722 let encoded = encode(&original);
1723 let decoded = decode(&encoded).unwrap();
1724 assert_eq!(decoded, original);
1725 }
1726
1727 #[test]
1728 fn test_roundtrip_simple_object() {
1729 let original = LinoValue::object([("a", LinoValue::Int(1)), ("b", LinoValue::Int(2))]);
1730 let encoded = encode(&original);
1731 let decoded = decode(&encoded).unwrap();
1732 assert_eq!(decoded, original);
1733 }
1734
1735 #[test]
1736 fn test_nested_objects() {
1737 let original = LinoValue::object([(
1738 "user",
1739 LinoValue::object([
1740 ("name", LinoValue::String("Alice".to_string())),
1741 (
1742 "address",
1743 LinoValue::object([
1744 ("city", LinoValue::String("NYC".to_string())),
1745 ("zip", LinoValue::String("10001".to_string())),
1746 ]),
1747 ),
1748 ]),
1749 )]);
1750 let encoded = encode(&original);
1751 let decoded = decode(&encoded).unwrap();
1752 assert_eq!(decoded, original);
1753 }
1754
1755 #[test]
1756 fn test_complex_structure() {
1757 let original = LinoValue::object([
1758 ("id", LinoValue::Int(123)),
1759 ("name", LinoValue::String("Test Object".to_string())),
1760 ("active", LinoValue::Bool(true)),
1761 (
1762 "tags",
1763 LinoValue::array([
1764 LinoValue::String("tag1".to_string()),
1765 LinoValue::String("tag2".to_string()),
1766 LinoValue::String("tag3".to_string()),
1767 ]),
1768 ),
1769 (
1770 "metadata",
1771 LinoValue::object([
1772 ("created", LinoValue::String("2025-01-01".to_string())),
1773 ("modified", LinoValue::Null),
1774 ("count", LinoValue::Int(42)),
1775 ]),
1776 ),
1777 (
1778 "items",
1779 LinoValue::array([
1780 LinoValue::object([
1781 ("id", LinoValue::Int(1)),
1782 ("value", LinoValue::String("first".to_string())),
1783 ]),
1784 LinoValue::object([
1785 ("id", LinoValue::Int(2)),
1786 ("value", LinoValue::String("second".to_string())),
1787 ]),
1788 ]),
1789 ),
1790 ]);
1791 let encoded = encode(&original);
1792 let decoded = decode(&encoded).unwrap();
1793 assert_eq!(decoded, original);
1794 }
1795
1796 #[test]
1797 fn test_list_of_dicts() {
1798 let original = LinoValue::array([
1799 LinoValue::object([
1800 ("name", LinoValue::String("Alice".to_string())),
1801 ("age", LinoValue::Int(30)),
1802 ]),
1803 LinoValue::object([
1804 ("name", LinoValue::String("Bob".to_string())),
1805 ("age", LinoValue::Int(25)),
1806 ]),
1807 ]);
1808 let encoded = encode(&original);
1809 let decoded = decode(&encoded).unwrap();
1810 assert_eq!(decoded, original);
1811 }
1812
1813 #[test]
1814 fn test_dict_of_lists() {
1815 let original = LinoValue::object([
1816 (
1817 "numbers",
1818 LinoValue::array([LinoValue::Int(1), LinoValue::Int(2), LinoValue::Int(3)]),
1819 ),
1820 (
1821 "strings",
1822 LinoValue::array([
1823 LinoValue::String("a".to_string()),
1824 LinoValue::String("b".to_string()),
1825 LinoValue::String("c".to_string()),
1826 ]),
1827 ),
1828 ]);
1829 let encoded = encode(&original);
1830 let decoded = decode(&encoded).unwrap();
1831 assert_eq!(decoded, original);
1832 }
1833}
1834
1835#[cfg(test)]
1836mod format_tests {
1837 use super::format::*;
1838 use std::collections::HashMap;
1839
1840 #[test]
1841 fn test_escape_reference_simple_string() {
1842 assert_eq!(escape_reference("hello"), "hello");
1843 assert_eq!(escape_reference("world"), "world");
1844 }
1845
1846 #[test]
1847 fn test_escape_reference_string_with_spaces() {
1848 let result = escape_reference("hello world");
1849 assert!(result.starts_with('\'') || result.starts_with('"'));
1850 assert!(result.contains("hello world"));
1851 }
1852
1853 #[test]
1854 fn test_escape_reference_string_with_single_quotes() {
1855 let result = escape_reference("it's");
1856 assert_eq!(result, "\"it's\"");
1857 }
1858
1859 #[test]
1860 fn test_escape_reference_string_with_double_quotes() {
1861 let result = escape_reference("he said \"hello\"");
1862 assert_eq!(result, "'he said \"hello\"'");
1863 }
1864
1865 #[test]
1866 fn test_unescape_reference_doubled_quotes() {
1867 assert_eq!(
1868 unescape_reference("he said \"\"hello\"\""),
1869 "he said \"hello\""
1870 );
1871 assert_eq!(unescape_reference("it''s"), "it's");
1872 }
1873
1874 #[test]
1875 fn test_format_indented_ordered_basic() {
1876 let pairs = [
1877 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1878 ("status", "executed"),
1879 ("command", "echo test"),
1880 ("exitCode", "0"),
1881 ];
1882 let result =
1883 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1884 let lines: Vec<&str> = result.lines().collect();
1885 assert_eq!(lines[0], "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1886 assert_eq!(lines[1], " uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"");
1887 assert_eq!(lines[2], " status \"executed\"");
1888 assert_eq!(lines[3], " command \"echo test\"");
1889 assert_eq!(lines[4], " exitCode \"0\"");
1890 }
1891
1892 #[test]
1893 fn test_format_indented_value_with_quotes() {
1894 let pairs = [("message", "He said \"hello\"")];
1896 let result = format_indented_ordered("test-id", &pairs, " ").unwrap();
1897 let lines: Vec<&str> = result.lines().collect();
1898 assert_eq!(lines[1], " message 'He said \"hello\"'");
1899 }
1900
1901 #[test]
1902 fn test_format_indented_requires_id() {
1903 let mut obj = HashMap::new();
1904 obj.insert("key".to_string(), "value".to_string());
1905 let result = format_indented("", &obj, " ");
1906 assert!(result.is_err());
1907 }
1908
1909 #[test]
1910 fn test_parse_indented_basic() {
1911 let text = "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\n uuid \"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019\"\n status \"executed\"\n exitCode \"0\"";
1912 let (id, obj) = parse_indented(text).unwrap();
1913 assert_eq!(id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1914 assert_eq!(
1915 obj.get("uuid"),
1916 Some(&"6dcf4c1b-ff3f-482c-95ab-711ea7d1b019".to_string())
1917 );
1918 assert_eq!(obj.get("status"), Some(&"executed".to_string()));
1919 assert_eq!(obj.get("exitCode"), Some(&"0".to_string()));
1920 }
1921
1922 #[test]
1923 fn test_parse_indented_with_quotes() {
1924 let text = "test-id\n message 'He said \"hello\"'";
1926 let (id, obj) = parse_indented(text).unwrap();
1927 assert_eq!(id, "test-id");
1928 assert_eq!(obj.get("message"), Some(&"He said \"hello\"".to_string()));
1929 }
1930
1931 #[test]
1932 fn test_parse_indented_empty_lines_skipped() {
1933 let text = "test-id\n\n key \"value\"\n\n another \"value2\"";
1934 let (id, obj) = parse_indented(text).unwrap();
1935 assert_eq!(id, "test-id");
1936 assert_eq!(obj.get("key"), Some(&"value".to_string()));
1937 assert_eq!(obj.get("another"), Some(&"value2".to_string()));
1938 }
1939
1940 #[test]
1941 fn test_parse_indented_requires_text() {
1942 let result = parse_indented("");
1943 assert!(result.is_err());
1944 }
1945
1946 #[test]
1947 fn test_roundtrip_format_indented() {
1948 let pairs = [
1949 ("uuid", "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019"),
1950 ("status", "executed"),
1951 ("command", "echo test"),
1952 ("exitCode", "0"),
1953 ];
1954 let formatted =
1955 format_indented_ordered("6dcf4c1b-ff3f-482c-95ab-711ea7d1b019", &pairs, " ").unwrap();
1956 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1957
1958 assert_eq!(parsed_id, "6dcf4c1b-ff3f-482c-95ab-711ea7d1b019");
1959 for (key, value) in pairs {
1960 assert_eq!(parsed_obj.get(key), Some(&value.to_string()));
1961 }
1962 }
1963
1964 #[test]
1965 fn test_roundtrip_with_quotes() {
1966 let pairs = [("message", "He said \"hello\"")];
1967 let formatted = format_indented_ordered("test-id", &pairs, " ").unwrap();
1968 let (parsed_id, parsed_obj) = parse_indented(&formatted).unwrap();
1969
1970 assert_eq!(parsed_id, "test-id");
1971 assert_eq!(
1972 parsed_obj.get("message"),
1973 Some(&"He said \"hello\"".to_string())
1974 );
1975 }
1976}