keyvalues_parser/text/
render.rs

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    // Write the indented key
49    writer.write_str(&multiple_char('\t', num_indents))?;
50    write_str(writer, key, render_type)?;
51
52    // Followed by the value
53    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    // TODO: do we really want to return a crate error here? It will always be a formatting error
86    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        // Only `Obj` gets indented
162        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}