ssv/engine/fluent_writer.rs
1//! Writes SSV using a fluent interface. Automatically inserts delimiters.
2
3use std::io::Write;
4
5use crate::engine::domain::Domain;
6use crate::engine::LineBreak;
7
8use super::domain::{BytesDomain, DomainStringSlice};
9use super::options::Options;
10use super::{WriteError, WriteResult};
11
12/// Has a fluent interface to write SSV to a byte writer.
13#[doc = generic_item_warning_doc!("FluentWriter")]
14/// Spacing and line-breaks are automatically written as required.
15///
16/// The underlying byte writer is flushed when the [`FluentWriter`] is dropped.
17/// Eventual flush errors will be ignored. Prefer to explicitly call the
18/// [`finish`](FluentWriter::finish) method instead of letting the
19/// [`FluentWriter`] being dropped.
20///
21/// # Example
22///
23/// ```
24/// use ssv::chars::FluentWriter;
25/// let mut output = Vec::new();
26///
27/// let fluent_writer = FluentWriter::new(&mut output);
28///
29/// fluent_writer
30/// .write_value("value")?
31/// .write_value("another value")? // automatic spacing
32/// .write_line_break()?
33/// .write_value("finalvalue")?
34/// .finish()?;
35/// # Ok::<(), ssv::chars::WriteError>(())
36/// ```
37#[derive(Debug)]
38pub struct FluentWriter<D: Domain, W: Write> {
39 inner: W,
40 state: State,
41 options: Options<D>,
42}
43
44impl<D: Domain, W: Write> FluentWriter<D, W> {
45 /// Creates an instance that writes SSV to the given byte writer.
46 pub fn new(inner: W) -> Self {
47 FluentWriter {
48 inner,
49 state: State::LineBegin,
50 options: Options::new(),
51 }
52 }
53
54 /// Writes a value.
55 ///
56 /// The value is enclosed in quotes if required (check the [rules](crate#rules))
57 /// or if the [`always_quoted`](FluentWriter::always_quoted) option is `true`.
58 ///
59 /// If the last wroten item was another value, then the
60 /// [default spacing](FluentWriter::default_spacing) is automatically written
61 /// before this value.
62 ///
63 /// If the last wroten item was a comment, then the
64 /// [default line-break](FluentWriter::default_line_break) is automatically
65 /// written before this value.
66 pub fn write_value(self, value: &D::StringSlice) -> WriteResult<Self> {
67 self.write_value_raw(value, false)
68 }
69
70 /// Writes a value enclosed in quotes.
71 ///
72 /// If the last wroten item was another value, then the
73 /// [default spacing](FluentWriter::default_spacing) is automatically written
74 /// before this value.
75 ///
76 /// If the last wroten item was a comment, then the
77 /// [default line-break](FluentWriter::default_line_break) is automatically
78 /// written before this value.
79 pub fn write_quoted_value(self, value: &D::StringSlice) -> WriteResult<Self> {
80 self.write_value_raw(value, true)
81 }
82
83 fn write_value_raw(mut self, value: &D::StringSlice, quoted: bool) -> WriteResult<Self> {
84 let mut this = match self.state {
85 State::Value => {
86 Self::write_spacing_raw(
87 &mut self.inner,
88 self.options.default_spacing().as_bytes(),
89 &mut self.state,
90 )?;
91 self
92 }
93 State::Comment => self.write_line_break()?,
94 _ => self,
95 };
96
97 let prepared_value = PreparedValue::from(value.as_bytes());
98 let quoted = quoted
99 || prepared_value.must_be_quoted
100 || this.always_quoted()
101 || (this.state == State::LineBegin
102 && prepared_value.bytes.first() == Some(&BytesDomain::HASH));
103
104 if quoted {
105 this.write(&[BytesDomain::QUOTE])?;
106 }
107 this.write(&prepared_value.bytes)?;
108 if quoted {
109 this.write(&[BytesDomain::QUOTE])?;
110 }
111
112 this.state = State::Value;
113 Ok(this)
114 }
115
116 /// Writes the specified spacing.
117 ///
118 /// If the last wroten item was a comment, then the
119 /// [default line-break](FluentWriter::default_line_break) is automatically
120 /// written before this value.
121 pub fn write_spacing(self, spacing: &D::StringSlice) -> WriteResult<Self> {
122 if !D::is_valid_spacing(spacing) {
123 return Err(WriteError::InvalidSpacing);
124 }
125
126 let mut this = match self.state {
127 State::Comment => self.write_line_break()?,
128 _ => self,
129 };
130
131 Self::write_spacing_raw(&mut this.inner, spacing.as_bytes(), &mut this.state)?;
132 Ok(this)
133 }
134
135 fn write_spacing_raw(
136 writer: &mut W,
137 spacing_bytes: &[u8],
138 state: &mut State,
139 ) -> WriteResult<()> {
140 Self::write_raw(writer, spacing_bytes)?;
141 *state = State::Spacing;
142 Ok(())
143 }
144
145 /// Writes the [default line-break](FluentWriter::default_line_break).
146 pub fn write_line_break(self) -> WriteResult<Self> {
147 let line_break = self.default_line_break();
148 self.write_this_line_break(line_break)
149 }
150
151 /// Writes the specified line-break.
152 pub fn write_this_line_break(mut self, line_break: LineBreak) -> WriteResult<Self> {
153 let bytes: &[u8] = match line_break {
154 LineBreak::Lf => &[BytesDomain::LF],
155 LineBreak::CrLf => &[BytesDomain::CR, BytesDomain::LF],
156 };
157 self.write(bytes)?;
158
159 self.state = State::LineBegin;
160 Ok(self)
161 }
162
163 /// Writes the comment.
164 ///
165 /// The HASH sign (`#`) is written before the comment.
166 ///
167 /// If the last wroten item was a value, spacing, or another comment, then
168 /// the [default line-break](FluentWriter::default_line_break) is automatically
169 /// written before the HASH sign and the comment.
170 pub fn write_comment(self, comment: &D::StringSlice) -> WriteResult<Self> {
171 let mut this = match self.state {
172 State::Value | State::Spacing | State::Comment => self.write_line_break()?,
173 _ => self,
174 };
175
176 this.write(&[BytesDomain::HASH])?;
177 this.write(comment.as_bytes())?;
178
179 this.state = State::Comment;
180 Ok(this)
181 }
182
183 fn write(&mut self, bytes: &[u8]) -> WriteResult<()> {
184 Self::write_raw(&mut self.inner, bytes)
185 }
186
187 fn write_raw(writer: &mut W, bytes: &[u8]) -> WriteResult<()> {
188 writer.write_all(bytes)?;
189 Ok(())
190 }
191
192 /// Finalizes the object by flushing the underlying byte writer.
193 ///
194 /// Prefer to explicitly call this method instead of letting the [`FluentWriter`]
195 /// being dropped.
196 pub fn finish(mut self) -> WriteResult<()> {
197 self.inner.flush()?;
198 Ok(())
199 }
200
201 /// Returns the default spacing used by the methods
202 /// [`write_value`](FluentWriter::write_value) and
203 /// [`write_quoted_value`](FluentWriter::write_quoted_value).
204 ///
205 /// This is the same as `self.options().default_spacing()`.
206 pub fn default_spacing(&self) -> &D::StringSlice {
207 self.options.default_spacing()
208 }
209
210 /// Sets the [default spacing](FluentWriter::default_spacing).
211 ///
212 /// This has the same effect as `self.options_mut().set_default_spacing(spacing)`.
213 pub fn set_default_spacing(mut self, spacing: D::String) -> WriteResult<Self> {
214 self.options.set_default_spacing(spacing)?;
215 Ok(self)
216 }
217
218 /// Returns the default line-break used by the methods
219 /// [`write_value`](FluentWriter::write_value),
220 /// [`write_quoted_value`](FluentWriter::write_quoted_value),
221 /// [`write_spacing`](FluentWriter::write_spacing),
222 /// [`write_line_break`](FluentWriter::write_line_break) and
223 /// [`write_comment`](FluentWriter::write_comment).
224 ///
225 /// This is the same as `self.options().default_line_break()`.
226 pub fn default_line_break(&self) -> LineBreak {
227 self.options.default_line_break()
228 }
229
230 /// Sets the [default line-break](FluentWriter::default_line_break).
231 ///
232 /// This has the same effect as `self.options_mut().set_default_line_break(line_break)`.
233 pub fn set_default_line_break(mut self, line_break: LineBreak) -> Self {
234 self.options.set_default_line_break(line_break);
235 self
236 }
237
238 /// Returns whether the [write_value](FluentWriter::write_value) should
239 /// automatically quote the values.
240 ///
241 /// This is the same as `self.options().always_quoted()`.
242 pub fn always_quoted(&self) -> bool {
243 self.options.always_quoted()
244 }
245
246 /// Sets whether the [write_value](FluentWriter::write_value) should
247 /// automatically quote the values.
248 ///
249 /// This is the same as `self.options_mut().set_always_quoted(always_quoted)`.
250 pub fn set_always_quoted(mut self, always_quoted: bool) -> Self {
251 self.options.set_always_quoted(always_quoted);
252 self
253 }
254
255 /// Returns a reference to the associated [`Options`] object.
256 pub fn options(&self) -> &Options<D> {
257 &self.options
258 }
259
260 /// Returns a mutable reference to the associated [`Options`] object.
261 pub fn options_mut(&mut self) -> &mut Options<D> {
262 &mut self.options
263 }
264
265 /// Replaces the associated [`Options`] object.
266 pub fn set_options(mut self, options: Options<D>) -> WriteResult<Self> {
267 self.options = options;
268 Ok(self)
269 }
270}
271
272struct PreparedValue {
273 bytes: Vec<u8>,
274 must_be_quoted: bool,
275}
276
277impl PreparedValue {
278 fn from(original_bytes: &[u8]) -> PreparedValue {
279 let mut only_quotes = true;
280 let mut spacing_or_line_break = false;
281
282 let mut bytes = Vec::new();
283 for byte in original_bytes {
284 bytes.push(*byte);
285
286 if *byte == BytesDomain::QUOTE {
287 bytes.push(*byte);
288 } else {
289 only_quotes = false;
290
291 if *byte == BytesDomain::LF || BytesDomain::is_spacing_element(*byte) {
292 spacing_or_line_break = true;
293 }
294 }
295 }
296
297 PreparedValue {
298 bytes,
299 must_be_quoted: only_quotes || spacing_or_line_break,
300 }
301 }
302}
303
304#[derive(PartialEq, Eq, Debug)]
305enum State {
306 Value,
307 Spacing,
308 LineBegin,
309 Comment,
310}