Skip to main content

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}