yaml2lua/
lib.rs

1//! # yaml2lua
2//!
3//! Convert YAML to Lua table
4//!
5//! ## Example:
6//! ```rust
7//! use yaml2lua::parse;
8//!
9//! let yaml = r#"
10//! string: yaml2lua
11//! int: 420
12//! bool: true
13//! nil: null
14//! "#;
15//!
16//! let lua = parse(yaml).unwrap();
17//! // Output:
18//! // {
19//! //   ["string"] = "yaml2lua",
20//! //   ["int"] = 420,
21//! //   ["bool"] = true,
22//! //   ["nil"] = nil,
23//! // }
24//! ```
25//!
26//! Made with <3 by Dervex
27
28#![allow(clippy::tabs_in_doc_comments)]
29
30use indexmap::IndexMap;
31use serde_yaml::{from_str, Result, Value};
32
33/// Parse YAML string into a Lua table
34///
35/// ```rust
36/// use yaml2lua::parse;
37///
38/// let yaml = r#"
39/// string: abc
40/// int: 123
41/// bool: true
42/// nil: null
43/// "#;
44///
45/// let lua = r#"{
46/// 	["string"] = "abc",
47/// 	["int"] = 123,
48/// 	["bool"] = true,
49/// 	["nil"] = nil,
50/// }"#;
51///
52/// assert_eq!(parse(yaml).unwrap(), lua);
53/// ```
54pub 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}