1#![allow(clippy::tabs_in_doc_comments)]
29
30use indexmap::IndexMap;
31use serde_yaml::{from_str, Result, Value};
32
33pub fn parse(yaml: &str) -> Result<String> {
55 let yaml: IndexMap<Value, Value> = from_str(yaml)?;
56 let mut lua = String::from("{\n");
57
58 for (key, value) in yaml {
59 lua.push_str(&walk(Some(&key), &value, 1));
60 }
61
62 lua.push('}');
63
64 Ok(lua)
65}
66
67fn walk(key: Option<&Value>, value: &Value, depth: usize) -> String {
68 let mut lua = String::new();
69
70 lua.push_str(&get_indent(depth));
71
72 if let Some(key) = key {
73 match key {
74 Value::String(s) => {
75 lua.push_str(&format!("[\"{}\"] = ", escape_string(s)));
76 }
77 Value::Number(n) => {
78 lua.push_str(&format!("[{}] = ", n));
79 }
80 Value::Bool(b) => {
81 lua.push_str(&format!("[{}] = ", b));
82 }
83 _ => return String::new(),
84 };
85 }
86
87 match value {
88 Value::String(s) => lua.push_str(&format!("\"{}\"", &escape_string(s))),
89 Value::Number(n) => lua.push_str(&n.to_string()),
90 Value::Bool(b) => lua.push_str(&b.to_string()),
91 Value::Null => lua.push_str("nil"),
92 Value::Sequence(s) => {
93 lua.push_str("{\n");
94
95 for v in s {
96 lua.push_str(&walk(None, v, depth + 1));
97 }
98
99 lua.push_str(&get_indent(depth));
100 lua.push('}');
101 }
102 Value::Mapping(m) => {
103 lua.push_str("{\n");
104
105 for (k, v) in m {
106 lua.push_str(&walk(Some(k), v, depth + 1));
107 }
108
109 lua.push_str(&get_indent(depth));
110 lua.push('}');
111 }
112 Value::Tagged(t) => {
113 lua.push_str("{\n");
114
115 lua.push_str(&get_indent(depth + 1));
116 lua.push_str(&format!(
117 "[\"{}\"] = {}",
118 t.tag.to_string().strip_prefix('!').unwrap(),
119 &walk(None, &t.value, depth + 1)
120 .strip_prefix(&"\t".repeat(depth + 1))
121 .unwrap()
122 ));
123
124 lua.push_str(&get_indent(depth));
125 lua.push('}');
126 }
127 }
128
129 lua.push_str(",\n");
130
131 lua
132}
133
134fn get_indent(depth: usize) -> String {
135 let mut indent = String::new();
136
137 for _ in 0..depth {
138 indent.push('\t');
139 }
140
141 indent
142}
143
144fn escape_string(string: &str) -> String {
145 let mut chars = string.chars();
146
147 while let Some(char) = chars.next() {
148 if char == '\\' {
149 if let Some(next_char) = chars.next() {
150 match next_char {
151 'n' | 't' | 'r' | '\\' | '"' => {}
152 _ => {
153 return string.escape_default().to_string();
154 }
155 }
156 } else {
157 return string.escape_default().to_string();
158 }
159 } else {
160 match char {
161 '\n' | '\t' | '\r' | '"' => {
162 return string.escape_default().to_string();
163 }
164 _ => {}
165 }
166 }
167 }
168
169 string.to_owned()
170}
171
172#[cfg(test)]
173mod test {
174 #[test]
175 fn all_values() {
176 use crate::parse;
177
178 let yaml = r#"
179string: str
180int: 420
181float: 4.2
182bool: true
183nil: null
184array:
185 - string
186 - 12345
187 - false
188 - k: v
189object:
190 key: value"#;
191
192 let lua = r#"{
193 ["string"] = "str",
194 ["int"] = 420,
195 ["float"] = 4.2,
196 ["bool"] = true,
197 ["nil"] = nil,
198 ["array"] = {
199 "string",
200 12345,
201 false,
202 {
203 ["k"] = "v",
204 },
205 },
206 ["object"] = {
207 ["key"] = "value",
208 },
209}"#;
210
211 assert_eq!(parse(yaml).unwrap(), lua);
212 }
213
214 #[test]
215 fn tagged_value() {
216 use crate::parse;
217
218 let yaml = r#"test: !SomeTag {x: 5}"#;
219
220 let lua = r#"{
221 ["test"] = {
222 ["SomeTag"] = {
223 ["x"] = 5,
224 },
225 },
226}"#;
227
228 assert_eq!(parse(yaml).unwrap(), lua);
229 }
230
231 #[test]
232 fn malformed_strings() {
233 use crate::parse;
234
235 let yaml = r#"
2361: ..\n..
2372: ..\t..
2383: ..\r..
2394: ..\\..
2405: ..\"..
2416: "..\n.."
2427: "..\t.."
2438: "..\r.."
2449: "..\\.."
24510: "..\"..""#;
246
247 let lua = r#"{
248 [1] = "..\n..",
249 [2] = "..\t..",
250 [3] = "..\r..",
251 [4] = "..\\..",
252 [5] = "..\"..",
253 [6] = "..\n..",
254 [7] = "..\t..",
255 [8] = "..\r..",
256 [9] = "..\\..",
257 [10] = "..\"..",
258}"#;
259
260 assert_eq!(parse(yaml).unwrap(), lua);
261 }
262}