1use std::collections::HashMap;
2
3mod parser;
4pub mod serde;
5#[cfg(test)]
6pub mod standard_tests;
7
8pub use parser::{
9 parse_document_root, parse_empty_dict, parse_empty_list, parse_huml, parse_inline_dict,
10 parse_inline_list, parse_scalar, IResult, ParseError, HUML_VERSION,
11};
12
13#[derive(Debug, Clone, PartialEq)]
14pub enum HumlValue {
15 String(String),
16 Number(HumlNumber),
17 Boolean(bool),
18 Null,
19 List(Vec<HumlValue>),
20 Dict(HashMap<String, HumlValue>),
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum HumlNumber {
25 Integer(i64),
26 Float(f64),
27 Nan,
28 Infinity(bool), }
30
31#[derive(Debug, Clone, PartialEq)]
32pub struct HumlDocument {
33 pub version: Option<String>,
34 pub root: HumlValue,
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn parses_simple_scalar_document() {
43 let (_, doc) = parse_huml("\"hello\"").expect("should parse");
44 assert_eq!(doc.root, HumlValue::String("hello".into()));
45 }
46
47 #[test]
48 fn parses_inline_list() {
49 if let HumlValue::List(values) = parse_inline_list("1, 2, 3").unwrap().1 {
50 assert_eq!(values.len(), 3);
51 } else {
52 panic!("expected list");
53 }
54 }
55
56 #[test]
57 fn parses_multiline_dict_document() {
58 let input = r#"
59key1: "value"
60key2::
61 nested: 1
62"#;
63 let (_, doc) = parse_huml(input).expect("should parse");
64 if let HumlValue::Dict(map) = doc.root {
65 assert!(map.contains_key("key1"));
66 assert!(map.contains_key("key2"));
67 } else {
68 panic!("expected dict");
69 }
70 }
71
72 #[test]
73 fn multiline_string_preserves_dedented() {
74 let input = r#"text: """
75 line one
76 line two
77 line three
78""""#;
79 let (_, doc) = parse_huml(input).expect("should parse");
80 if let HumlValue::Dict(map) = doc.root {
81 if let Some(HumlValue::String(s)) = map.get("text") {
82 assert_eq!(s, "line one\nline two\nline three");
83 } else {
84 panic!("expected string value");
85 }
86 } else {
87 panic!("expected dict");
88 }
89 }
90
91 #[test]
92 fn multiline_string_preserves_extra_spaces() {
93 let input = r#"text: """
94 line with extra spaces
95 line with minimal spaces
96 line with many spaces
97""""#;
98 let (_, doc) = parse_huml(input).expect("should parse");
99 if let HumlValue::Dict(map) = doc.root {
100 if let Some(HumlValue::String(s)) = map.get("text") {
101 assert_eq!(
102 s,
103 " line with extra spaces\nline with minimal spaces\n line with many spaces"
104 );
105 } else {
106 panic!("expected string value");
107 }
108 } else {
109 panic!("expected dict");
110 }
111 }
112
113 #[test]
114 fn multiline_string_with_empty_lines() {
115 let input = r#"text: """
116 first line
117
118 third line
119""""#;
120 let (_, doc) = parse_huml(input).expect("should parse");
121 if let HumlValue::Dict(map) = doc.root {
122 if let Some(HumlValue::String(s)) = map.get("text") {
123 assert_eq!(s, "first line\n\nthird line");
124 } else {
125 panic!("expected string value");
126 }
127 } else {
128 panic!("expected dict");
129 }
130 }
131
132 #[test]
133 fn multiline_string_minimal_indent() {
134 let input = r#"x: """
135first
136second
137""""#;
138 let (_, doc) = parse_huml(input).expect("should parse");
139 if let HumlValue::Dict(map) = doc.root {
140 if let Some(HumlValue::String(s)) = map.get("x") {
141 assert_eq!(s, "first\nsecond");
142 } else {
143 panic!("expected string value");
144 }
145 } else {
146 panic!("expected dict");
147 }
148 }
149
150 #[test]
151 fn backticks_multiline_string_rejected() {
152 let input = r#"text: ```
153 line one
154```"#;
155 assert!(parse_huml(input).is_err());
156 }
157
158 #[test]
159 fn multiline_string_in_list() {
160 let input = r#"items::
161 - """
162 line one
163 line two
164 """
165 - "regular string""#;
166 let (_, doc) = parse_huml(input).expect("should parse");
167 if let HumlValue::Dict(map) = doc.root {
168 if let Some(HumlValue::List(items)) = map.get("items") {
169 assert_eq!(items.len(), 2);
170 if let HumlValue::String(s) = &items[0] {
171 assert_eq!(s, "line one\nline two");
172 } else {
173 panic!("expected string in list");
174 }
175 } else {
176 panic!("expected list");
177 }
178 } else {
179 panic!("expected dict");
180 }
181 }
182
183 #[test]
184 fn duplicate_key_error_before_malformed_value() {
185 let input = r#"
187key: "first"
188key: [this is malformed
189"#;
190 let result = parse_huml(input);
191 assert!(result.is_err());
192 let err_msg = result.unwrap_err().to_string();
193 assert!(err_msg.contains("duplicate key"));
195 }
196}