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