1use std::fmt::{self, Write};
2
3use crate::{error::Error, Obj, PartialVdf, Value, Vdf};
4
5fn multiple_char(c: char, amount: usize) -> String {
6 std::iter::repeat(c).take(amount).collect()
7}
8
9#[derive(Debug, Clone, Copy)]
10enum RenderType {
11 Escaped,
12 Raw,
13}
14
15fn find_invalid_raw_char(s: &str) -> Option<char> {
16 s.chars().find(|&c| c == '"').to_owned()
17}
18
19fn write_str(writer: &mut impl Write, s: &str, render_type: RenderType) -> fmt::Result {
20 writer.write_char('"')?;
21
22 match render_type {
23 RenderType::Escaped => {
24 for c in s.chars() {
25 match c {
26 '\n' => writer.write_str(r"\n"),
27 '\r' => writer.write_str(r"\r"),
28 '\t' => writer.write_str(r"\t"),
29 '\"' => writer.write_str(r#"\""#),
30 '\\' => writer.write_str(r"\\"),
31 reg => writer.write_char(reg),
32 }?
33 }
34 }
35 RenderType::Raw => writer.write_str(s)?,
36 }
37
38 writer.write_char('"')
39}
40
41fn write_pair(
42 writer: &mut impl Write,
43 num_indents: usize,
44 key: &str,
45 value: &Value<'_>,
46 render_type: RenderType,
47) -> fmt::Result {
48 writer.write_str(&multiple_char('\t', num_indents))?;
50 write_str(writer, key, render_type)?;
51
52 if value.is_str() {
54 writer.write_char('\t')?;
55 } else {
56 writer.write_char('\n')?;
57 }
58 value.write_indented(writer, num_indents, render_type)?;
59
60 writer.write_char('\n')
61}
62
63fn write_obj(
64 writer: &mut impl Write,
65 num_indents: usize,
66 obj: &Obj<'_>,
67 render_type: RenderType,
68) -> fmt::Result {
69 for (key, values) in obj.iter() {
70 for value in values {
71 write_pair(writer, num_indents, key, value, render_type)?;
72 }
73 }
74
75 Ok(())
76}
77
78impl<'a> fmt::Display for PartialVdf<'a> {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 self._render(f, RenderType::Raw)
81 }
82}
83
84impl<'a> PartialVdf<'a> {
85 pub fn render(&self, writer: &mut impl Write) -> crate::error::Result<()> {
87 self._render(writer, RenderType::Raw).map_err(Into::into)
88 }
89
90 pub fn render_raw(&self, writer: &mut impl Write) -> crate::error::Result<()> {
91 match self.find_invalid_raw_char() {
92 Some(invalid_char) => Err(Error::RawRenderError { invalid_char }),
93 None => self._render(writer, RenderType::Raw).map_err(Into::into),
94 }
95 }
96
97 fn _render(&self, writer: &mut impl Write, render_type: RenderType) -> fmt::Result {
98 for base in &self.bases {
99 writeln!(writer, "#base \"{}\"", base)?;
100 }
101
102 if !self.bases.is_empty() {
103 writer.write_char('\n')?;
104 }
105
106 write_pair(writer, 0, &self.key, &self.value, render_type)
107 }
108
109 fn find_invalid_raw_char(&self) -> Option<char> {
110 find_invalid_raw_char(&self.key).or_else(|| self.value.find_invalid_raw_char())
111 }
112}
113
114impl<'a> fmt::Display for Vdf<'a> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 self.write_indented(f, 0, RenderType::Escaped)
117 }
118}
119
120impl<'a> Vdf<'a> {
121 pub fn render(&self, writer: &mut impl Write) -> crate::error::Result<()> {
122 write!(writer, "{}", self).map_err(Into::into)
123 }
124
125 pub fn render_raw(&self, writer: &mut impl Write) -> crate::error::Result<()> {
126 match self.find_invalid_raw_char() {
127 Some(invalid_char) => Err(Error::RawRenderError { invalid_char }),
128 None => self
129 .write_indented(writer, 0, RenderType::Raw)
130 .map_err(Into::into),
131 }
132 }
133
134 fn find_invalid_raw_char(&self) -> Option<char> {
135 find_invalid_raw_char(&self.key).or_else(|| self.value.find_invalid_raw_char())
136 }
137
138 fn write_indented(
139 &self,
140 writer: &mut impl Write,
141 num_indents: usize,
142 render_type: RenderType,
143 ) -> fmt::Result {
144 write_pair(writer, num_indents, &self.key, &self.value, render_type)
145 }
146}
147
148impl<'a> fmt::Display for Value<'a> {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 self.write_indented(f, 0, RenderType::Escaped)
151 }
152}
153
154impl<'a> Value<'a> {
155 fn write_indented(
156 &self,
157 writer: &mut impl Write,
158 num_indents: usize,
159 render_type: RenderType,
160 ) -> fmt::Result {
161 match self {
163 Value::Str(s) => write_str(writer, s, render_type),
164 Value::Obj(obj) => {
165 writeln!(writer, "{}{{", multiple_char('\t', num_indents))?;
166 write_obj(writer, num_indents + 1, obj, render_type)?;
167 write!(writer, "{}}}", multiple_char('\t', num_indents))
168 }
169 }
170 }
171
172 fn find_invalid_raw_char(&self) -> Option<char> {
173 match self {
174 Self::Str(s) => find_invalid_raw_char(s),
175 Self::Obj(obj) => obj.iter().find_map(|(key, values)| {
176 find_invalid_raw_char(key)
177 .or_else(|| values.iter().find_map(Value::find_invalid_raw_char))
178 }),
179 }
180 }
181}