oneyaml_core/emit.rs
1//! Output settings.
2
3/// Which quote character the emitter prefers for strings that need quoting.
4#[non_exhaustive]
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
6pub enum QuoteStyle {
7 /// Single quotes; double quotes only when escapes are required (the
8 /// default).
9 #[default]
10 Single,
11 /// Always double quotes.
12 Double,
13}
14
15/// Overall output layout.
16#[non_exhaustive]
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
18pub enum Layout {
19 /// oneyaml's own layout: the defaults.
20 #[default]
21 Default,
22 /// Reproduce `serde_yaml`'s output layout, best effort (checked against
23 /// `serde_yaml_ng`). Set by [`Compat::serde_yaml()`](crate::Compat::serde_yaml).
24 ///
25 /// **Known divergences** (each a deliberate choice: correctness first; the `oracles` crate's `serde_yaml_compat` test
26 /// keeps this list exact):
27 ///
28 /// 1. *Nested enums*: `serde_yaml` refuses a newtype variant around
29 /// another variant ("serializing nested enums in YAML is not
30 /// supported yet"); oneyaml writes the outer one as
31 /// `Name: !Inner ...`, which reads back.
32 /// 2. *Enum around a scalar*: `serde_yaml` writes a newtype variant
33 /// around a number, bool, or null as `!Name 1`; a custom-tagged
34 /// scalar always reads back as a string, so oneyaml writes
35 /// `Name: 1`, which both libraries read back as the variant.
36 /// 3. *Root indentation indicator*: a root string whose first line
37 /// starts with a space or a line break needs a block indentation
38 /// indicator; libyaml counts it from 0 (`|2`) where YAML 1.2 counts
39 /// from -1 (ยง8.1.1.1), so readers disagree and oneyaml
40 /// double-quotes it instead.
41 /// 4. *U+2028/U+2029*: libyaml writes them as line breaks inside single
42 /// quotes, which YAML 1.2 readers read as other text; oneyaml
43 /// double-quotes them as `\L`/`\P`.
44 /// 5. *`char`*: `serde_yaml` always single-quotes a `char`; oneyaml's
45 /// data model keeps a char as a string, quoted only when needed.
46 /// 6. *`f32` layout*: `serde_yaml` lays out an `f32` with ryu's f32
47 /// thresholds (`1e14`); oneyaml keeps floats as `f64` and uses the
48 /// f64 thresholds (`100000000000000.0`): same digits, same value.
49 SerdeYaml,
50}
51
52/// Output settings for the emitter and serializer.
53///
54/// Consumed by the serializer (`to_string_with`, `to_writer_with`, and
55/// `Serializer::with_settings` in `oneyaml`). For an edited `Document`
56/// they apply only to new nodes where style inference has no context.
57/// Any `indent` value is accepted here; the serializer refuses
58/// `indent(0)` (it cannot express nesting) and anything above
59/// [`Emit::MAX_INDENT`] with `Y1012` (never a panic or an
60/// allocation blow-up for any value).
61///
62/// Defaults: 2-space indent, single quotes, never wrap, sequences indented
63/// under their key, oneyaml's own layout.
64///
65/// # Examples
66///
67/// ```
68/// use oneyaml_core::{Emit, Layout, QuoteStyle};
69///
70/// let e = Emit::new()
71/// .indent(4)
72/// .quote_style(QuoteStyle::Double)
73/// .line_width(None)
74/// .indent_sequences(false)
75/// .layout(Layout::Default);
76/// assert_eq!(e.get_indent(), 4);
77/// assert_eq!(e.get_quote_style(), QuoteStyle::Double);
78/// assert_eq!(e.get_line_width(), None); // never wrap (wrapping is not implemented: `Some` is Y1017)
79/// assert!(!e.get_indent_sequences());
80/// assert_eq!(e.get_layout(), Layout::Default);
81/// ```
82// Value type, private fields, chainable `const fn` setters.
83#[derive(Clone, Debug, PartialEq, Eq, Hash)]
84pub struct Emit {
85 indent: usize,
86 quote_style: QuoteStyle,
87 line_width: Option<usize>,
88 indent_sequences: bool,
89 layout: Layout,
90}
91
92impl Emit {
93 /// The widest indentation the serializer accepts (`Y1012` above it).
94 ///
95 /// Block output repeats the indentation once per nesting level on every
96 /// line, so the width multiplies output size; the bound keeps any
97 /// setting from turning into an allocation blow-up.
98 pub const MAX_INDENT: usize = 64;
99
100 /// The defaults. Same as [`Emit::default()`], usable in `const`.
101 #[must_use]
102 pub const fn new() -> Self {
103 Self {
104 indent: 2,
105 quote_style: QuoteStyle::Single,
106 line_width: None,
107 indent_sequences: true,
108 layout: Layout::Default,
109 }
110 }
111
112 /// Spaces per indentation level (default 2). The serializer accepts
113 /// `1..=`[`Emit::MAX_INDENT`]; other values are `Y1012` there.
114 #[must_use]
115 pub const fn indent(mut self, spaces: usize) -> Self {
116 self.indent = spaces;
117 self
118 }
119
120 /// Preferred quote style (default [`QuoteStyle::Single`]).
121 #[must_use]
122 pub const fn quote_style(mut self, style: QuoteStyle) -> Self {
123 self.quote_style = style;
124 self
125 }
126
127 /// Wrap long plain and quoted scalars at `width` columns; `None` (the
128 /// default) never wraps.
129 ///
130 /// Wrapping is not implemented yet: the serializer refuses `Some(_)`
131 /// with `Y1017` rather than ignoring it silently.
132 #[must_use]
133 pub const fn line_width(mut self, width: Option<usize>) -> Self {
134 self.line_width = width;
135 self
136 }
137
138 /// Indent a block sequence under its mapping key (`key:\n - a`, the
139 /// default) or not (`key:\n- a`).
140 #[must_use]
141 pub const fn indent_sequences(mut self, on: bool) -> Self {
142 self.indent_sequences = on;
143 self
144 }
145
146 /// Output layout (default [`Layout::Default`]).
147 #[must_use]
148 pub const fn layout(mut self, layout: Layout) -> Self {
149 self.layout = layout;
150 self
151 }
152
153 /// The indentation width.
154 #[must_use]
155 pub const fn get_indent(&self) -> usize {
156 self.indent
157 }
158
159 /// The preferred quote style.
160 #[must_use]
161 pub const fn get_quote_style(&self) -> QuoteStyle {
162 self.quote_style
163 }
164
165 /// The wrap width; `None` never wraps.
166 #[must_use]
167 pub const fn get_line_width(&self) -> Option<usize> {
168 self.line_width
169 }
170
171 /// Whether block sequences are indented under their key.
172 #[must_use]
173 pub const fn get_indent_sequences(&self) -> bool {
174 self.indent_sequences
175 }
176
177 /// The output layout.
178 #[must_use]
179 pub const fn get_layout(&self) -> Layout {
180 self.layout
181 }
182}
183
184impl Default for Emit {
185 fn default() -> Self {
186 Self::new()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn defaults_and_setters() {
196 let d = Emit::default();
197 assert_eq!(d, Emit::new());
198 assert_eq!(
199 (
200 d.get_indent(),
201 d.get_quote_style(),
202 d.get_line_width(),
203 d.get_indent_sequences(),
204 d.get_layout()
205 ),
206 (2, QuoteStyle::Single, None, true, Layout::Default)
207 );
208 const E: Emit = Emit::new()
209 .indent(4)
210 .quote_style(QuoteStyle::Double)
211 .line_width(Some(80))
212 .indent_sequences(false)
213 .layout(Layout::SerdeYaml);
214 assert_eq!(
215 (
216 E.get_indent(),
217 E.get_quote_style(),
218 E.get_line_width(),
219 E.get_indent_sequences(),
220 E.get_layout()
221 ),
222 (4, QuoteStyle::Double, Some(80), false, Layout::SerdeYaml)
223 );
224 assert_eq!(QuoteStyle::default(), QuoteStyle::Single);
225 assert_eq!(Layout::default(), Layout::Default);
226 }
227}