1use std::str::FromStr;
2use serde::{Deserialize, Serialize};
3
4pub const MIN_WRAP_WIDTH: usize = 20;
5pub const DEFAULT_WRAP_WIDTH: usize = 80;
6pub(crate) const MIN_FOLD_CONTINUATION: usize = 10;
7
8#[non_exhaustive]
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16pub enum IndentGlyphStyle {
17 #[default]
20 Auto,
21 Fixed,
23 None,
25}
26
27impl FromStr for IndentGlyphStyle {
28 type Err = String;
29 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
30 match input {
31 "auto" => Ok(Self::Auto),
32 "fixed" => Ok(Self::Fixed),
33 "none" => Ok(Self::None),
34 _ => Err(format!(
35 "invalid indent glyph style '{input}' (expected one of: auto, fixed, none)"
36 )),
37 }
38 }
39}
40
41#[non_exhaustive]
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum IndentGlyphMarkerStyle {
45 #[default]
47 Compact,
48 Separate,
54 }
59
60#[derive(Clone, Copy, Debug, PartialEq)]
63#[allow(dead_code)]
64pub(crate) enum IndentGlyphMode {
65 IndentWeighted(f64),
67 ByteWeighted(f64),
71 Fixed,
73 None,
75}
76
77pub(crate) fn indent_glyph_mode(options: &RenderOptions) -> IndentGlyphMode {
78 match options.indent_glyph_style {
79 IndentGlyphStyle::Auto => IndentGlyphMode::IndentWeighted(0.2),
80 IndentGlyphStyle::Fixed => IndentGlyphMode::Fixed,
81 IndentGlyphStyle::None => IndentGlyphMode::None,
82 }
83}
84
85#[non_exhaustive]
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
91pub enum TableUnindentStyle {
92 Left,
95 #[default]
99 Auto,
100 Floating,
104 None,
107}
108
109impl FromStr for TableUnindentStyle {
110 type Err = String;
111 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
112 match input {
113 "left" => Ok(Self::Left),
114 "auto" => Ok(Self::Auto),
115 "floating" => Ok(Self::Floating),
116 "none" => Ok(Self::None),
117 _ => Err(format!(
118 "invalid table unindent style '{input}' (expected one of: left, auto, floating, none)"
119 )),
120 }
121 }
122}
123
124
125#[non_exhaustive]
126#[allow(dead_code)]
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(default)]
129struct ParseOptions {
130 start_indent: usize,
131}
132
133#[non_exhaustive]
137#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
138#[serde(default)]
139pub struct RenderOptions {
140 pub(crate) wrap_width: Option<usize>,
141 pub(crate) start_indent: usize,
142 pub(crate) force_markers: bool,
143 pub(crate) bare_strings: BareStyle,
144 pub(crate) bare_keys: BareStyle,
145 pub(crate) inline_objects: bool,
146 pub(crate) inline_arrays: bool,
147 pub(crate) string_array_style: StringArrayStyle,
148 pub(crate) number_fold_style: FoldStyle,
149 pub(crate) string_bare_fold_style: FoldStyle,
150 pub(crate) string_quoted_fold_style: FoldStyle,
151 pub(crate) string_multiline_fold_style: FoldStyle,
152 pub(crate) tables: bool,
153 pub(crate) table_fold: bool,
154 pub(crate) table_unindent_style: TableUnindentStyle,
155 pub(crate) indent_glyph_style: IndentGlyphStyle,
156 pub(crate) indent_glyph_marker_style: IndentGlyphMarkerStyle,
157 pub(crate) table_min_rows: usize,
158 pub(crate) table_min_columns: usize,
159 pub(crate) table_min_similarity: f32,
160 pub(crate) table_column_max_width: Option<usize>,
161 pub(crate) kv_pack_multiple: usize,
163 pub(crate) multiline_strings: bool,
164 pub(crate) multiline_style: MultilineStyle,
165 pub(crate) multiline_min_lines: usize,
166 pub(crate) multiline_max_lines: usize,
167}
168
169#[non_exhaustive]
175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
176pub enum FoldStyle {
177 #[default]
180 Auto,
181 Fixed,
184 None,
186}
187
188impl FromStr for FoldStyle {
189 type Err = String;
190
191 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
192 match input {
193 "auto" => Ok(Self::Auto),
194 "fixed" => Ok(Self::Fixed),
195 "none" => Ok(Self::None),
196 _ => Err(format!(
197 "invalid fold style '{input}' (expected one of: auto, fixed, none)"
198 )),
199 }
200 }
201}
202
203#[non_exhaustive]
223#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
224pub enum MultilineStyle {
225 Floating,
228 #[default]
230 Bold,
231 BoldFloating,
234 Transparent,
238 Light,
242 FoldingQuotes,
245}
246
247impl FromStr for MultilineStyle {
248 type Err = String;
249
250 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
251 match input {
252 "bold" => Ok(Self::Bold),
253 "floating" => Ok(Self::Floating),
254 "bold-floating" => Ok(Self::BoldFloating),
255 "transparent" => Ok(Self::Transparent),
256 "light" => Ok(Self::Light),
257 "folding-quotes" => Ok(Self::FoldingQuotes),
258 _ => Err(format!(
259 "invalid multiline style '{input}' (expected one of: bold, floating, bold-floating, transparent, light, folding-quotes)"
260 )),
261 }
262 }
263}
264
265#[non_exhaustive]
270#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
271pub enum BareStyle {
272 #[default]
273 Prefer,
274 None,
275}
276
277impl FromStr for BareStyle {
278 type Err = String;
279
280 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
281 match input {
282 "prefer" => Ok(Self::Prefer),
283 "none" => Ok(Self::None),
284 _ => Err(format!(
285 "invalid bare style '{input}' (expected one of: prefer, none)"
286 )),
287 }
288 }
289}
290
291#[non_exhaustive]
299#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
300pub enum StringArrayStyle {
301 Spaces,
302 PreferSpaces,
303 Comma,
304 #[default]
305 PreferComma,
306 None,
307}
308
309impl FromStr for StringArrayStyle {
310 type Err = String;
311
312 fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
313 match input {
314 "spaces" => Ok(Self::Spaces),
315 "prefer-spaces" => Ok(Self::PreferSpaces),
316 "comma" => Ok(Self::Comma),
317 "prefer-comma" => Ok(Self::PreferComma),
318 "none" => Ok(Self::None),
319 _ => Err(format!(
320 "invalid string array style '{input}' (expected one of: spaces, prefer-spaces, comma, prefer-comma, none)"
321 )),
322 }
323 }
324}
325
326impl RenderOptions {
327 pub fn canonical() -> Self {
330 Self {
331 inline_objects: false,
332 inline_arrays: false,
333 string_array_style: StringArrayStyle::None,
334 tables: false,
335 multiline_strings: false,
336 number_fold_style: FoldStyle::None,
337 string_bare_fold_style: FoldStyle::None,
338 string_quoted_fold_style: FoldStyle::None,
339 string_multiline_fold_style: FoldStyle::None,
340 indent_glyph_style: IndentGlyphStyle::None,
341 ..Self::default()
342 }
343 }
344
345 pub fn force_markers(mut self, force_markers: bool) -> Self {
350 self.force_markers = force_markers;
351 self
352 }
353
354 pub fn bare_strings(mut self, bare_strings: BareStyle) -> Self {
357 self.bare_strings = bare_strings;
358 self
359 }
360
361 pub fn bare_keys(mut self, bare_keys: BareStyle) -> Self {
364 self.bare_keys = bare_keys;
365 self
366 }
367
368 pub fn inline_objects(mut self, inline_objects: bool) -> Self {
370 self.inline_objects = inline_objects;
371 self
372 }
373
374 pub fn inline_arrays(mut self, inline_arrays: bool) -> Self {
376 self.inline_arrays = inline_arrays;
377 self
378 }
379
380 pub fn string_array_style(mut self, string_array_style: StringArrayStyle) -> Self {
383 self.string_array_style = string_array_style;
384 self
385 }
386
387 pub fn tables(mut self, tables: bool) -> Self {
390 self.tables = tables;
391 self
392 }
393
394 pub fn wrap_width(mut self, wrap_width: Option<usize>) -> Self {
398 self.wrap_width = wrap_width.map(|w| w.clamp(MIN_WRAP_WIDTH, usize::MAX));
399 self
400 }
401
402 pub fn wrap_width_checked(self, wrap_width: Option<usize>) -> std::result::Result<Self, String> {
406 if let Some(w) = wrap_width
407 && w < MIN_WRAP_WIDTH {
408 return Err(format!("wrap_width must be at least {MIN_WRAP_WIDTH}, got {w}"));
409 }
410 Ok(self.wrap_width(wrap_width))
411 }
412
413 pub fn table_min_rows(mut self, table_min_rows: usize) -> Self {
415 self.table_min_rows = table_min_rows;
416 self
417 }
418
419 pub fn table_min_columns(mut self, table_min_columns: usize) -> Self {
421 self.table_min_columns = table_min_columns;
422 self
423 }
424
425 pub fn table_min_similarity(mut self, v: f32) -> Self {
431 self.table_min_similarity = v;
432 self
433 }
434
435 pub fn table_column_max_width(mut self, table_column_max_width: Option<usize>) -> Self {
439 self.table_column_max_width = table_column_max_width;
440 self
441 }
442
443 pub fn kv_pack_multiple(mut self, v: usize) -> std::result::Result<Self, String> {
446 if !(1..=4).contains(&v) {
447 return Err(format!("kv_pack_multiple must be 1–4, got {v}"));
448 }
449 self.kv_pack_multiple = v;
450 Ok(self)
451 }
452
453 pub fn kv_pack_multiple_clamped(mut self, v: usize) -> Self {
456 self.kv_pack_multiple = v.clamp(1, 4);
457 self
458 }
459
460 pub fn fold(self, style: FoldStyle) -> Self {
462 self.number_fold_style(style)
463 .string_bare_fold_style(style)
464 .string_quoted_fold_style(style)
465 .string_multiline_fold_style(style)
466 }
467
468 pub fn number_fold_style(mut self, style: FoldStyle) -> Self {
471 self.number_fold_style = style;
472 self
473 }
474
475 pub fn string_bare_fold_style(mut self, style: FoldStyle) -> Self {
478 self.string_bare_fold_style = style;
479 self
480 }
481
482 pub fn string_quoted_fold_style(mut self, style: FoldStyle) -> Self {
485 self.string_quoted_fold_style = style;
486 self
487 }
488
489 pub fn string_multiline_fold_style(mut self, style: FoldStyle) -> Self {
494 self.string_multiline_fold_style = style;
495 self
496 }
497
498 pub fn table_fold(mut self, table_fold: bool) -> Self {
501 self.table_fold = table_fold;
502 self
503 }
504
505 pub fn table_unindent_style(mut self, style: TableUnindentStyle) -> Self {
508 self.table_unindent_style = style;
509 self
510 }
511
512 pub fn indent_glyph_style(mut self, style: IndentGlyphStyle) -> Self {
518 self.indent_glyph_style = style;
519 self
520 }
521
522 pub fn indent_glyph_marker_style(mut self, style: IndentGlyphMarkerStyle) -> Self {
525 self.indent_glyph_marker_style = style;
526 self
527 }
528
529 pub fn multiline_strings(mut self, multiline_strings: bool) -> Self {
532 self.multiline_strings = multiline_strings;
533 self
534 }
535
536 pub fn multiline_style(mut self, multiline_style: MultilineStyle) -> Self {
540 self.multiline_style = multiline_style;
541 self
542 }
543
544 pub fn multiline_min_lines(mut self, multiline_min_lines: usize) -> Self {
547 self.multiline_min_lines = multiline_min_lines;
548 self
549 }
550
551 pub fn multiline_max_lines(mut self, multiline_max_lines: usize) -> Self {
553 self.multiline_max_lines = multiline_max_lines;
554 self
555 }
556}
557
558impl Default for RenderOptions {
559 fn default() -> Self {
560 Self {
561 start_indent: 0,
562 force_markers: false,
563 bare_strings: BareStyle::Prefer,
564 bare_keys: BareStyle::Prefer,
565 inline_objects: true,
566 inline_arrays: true,
567 string_array_style: StringArrayStyle::PreferComma,
568 tables: true,
569 wrap_width: Some(DEFAULT_WRAP_WIDTH),
570 table_min_rows: 3,
571 table_min_columns: 3,
572 table_min_similarity: 0.8,
573 table_column_max_width: Some(40),
574 kv_pack_multiple: 2,
575 number_fold_style: FoldStyle::Auto,
576 string_bare_fold_style: FoldStyle::Auto,
577 string_quoted_fold_style: FoldStyle::Auto,
578 string_multiline_fold_style: FoldStyle::None,
579 table_fold: false,
580 table_unindent_style: TableUnindentStyle::Auto,
581 indent_glyph_style: IndentGlyphStyle::Auto,
582 indent_glyph_marker_style: IndentGlyphMarkerStyle::Compact,
583 multiline_strings: true,
584 multiline_style: MultilineStyle::Bold,
585 multiline_min_lines: 1,
586 multiline_max_lines: 10,
587 }
588 }
589}
590
591mod camel_de {
594 use serde::{Deserialize, Deserializer};
595
596 fn de_str<'de, D: Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
597 Option::<String>::deserialize(d)
598 }
599
600 macro_rules! camel_option_de {
601 ($fn_name:ident, $Enum:ty, $($camel:literal => $variant:expr),+ $(,)?) => {
602 pub fn $fn_name<'de, D: Deserializer<'de>>(d: D) -> Result<Option<$Enum>, D::Error> {
603 let Some(s) = de_str(d)? else { return Ok(None); };
604 match s.as_str() {
605 $($camel => return Ok(Some($variant)),)+
606 _ => {}
607 }
608 serde_json::from_value(serde_json::Value::String(s.clone()))
610 .map(Some)
611 .map_err(|_| serde::de::Error::unknown_variant(&s, &[$($camel),+]))
612 }
613 };
614 }
615
616 camel_option_de!(bare_style, super::BareStyle,
617 "prefer" => super::BareStyle::Prefer,
618 "none" => super::BareStyle::None,
619 );
620
621 camel_option_de!(fold_style, super::FoldStyle,
622 "auto" => super::FoldStyle::Auto,
623 "fixed" => super::FoldStyle::Fixed,
624 "none" => super::FoldStyle::None,
625 );
626
627 camel_option_de!(multiline_style, super::MultilineStyle,
628 "floating" => super::MultilineStyle::Floating,
629 "bold" => super::MultilineStyle::Bold,
630 "boldFloating" => super::MultilineStyle::BoldFloating,
631 "transparent" => super::MultilineStyle::Transparent,
632 "light" => super::MultilineStyle::Light,
633 "foldingQuotes" => super::MultilineStyle::FoldingQuotes,
634 );
635
636 camel_option_de!(table_unindent_style, super::TableUnindentStyle,
637 "left" => super::TableUnindentStyle::Left,
638 "auto" => super::TableUnindentStyle::Auto,
639 "floating" => super::TableUnindentStyle::Floating,
640 "none" => super::TableUnindentStyle::None,
641 );
642
643 camel_option_de!(indent_glyph_style, super::IndentGlyphStyle,
644 "auto" => super::IndentGlyphStyle::Auto,
645 "fixed" => super::IndentGlyphStyle::Fixed,
646 "none" => super::IndentGlyphStyle::None,
647 );
648
649 camel_option_de!(indent_glyph_marker_style, super::IndentGlyphMarkerStyle,
650 "compact" => super::IndentGlyphMarkerStyle::Compact,
651 "separate" => super::IndentGlyphMarkerStyle::Separate,
652 );
653
654 camel_option_de!(string_array_style, super::StringArrayStyle,
655 "spaces" => super::StringArrayStyle::Spaces,
656 "preferSpaces" => super::StringArrayStyle::PreferSpaces,
657 "comma" => super::StringArrayStyle::Comma,
658 "preferComma" => super::StringArrayStyle::PreferComma,
659 "none" => super::StringArrayStyle::None,
660 );
661}
662
663#[doc(hidden)]
666#[derive(Clone, Debug, Default, Deserialize)]
667#[serde(rename_all = "camelCase", default)]
668pub struct TjsonConfig {
669 pub(crate) canonical: bool,
670 pub(crate) force_markers: Option<bool>,
671 pub(crate) wrap_width: Option<usize>,
672 #[serde(deserialize_with = "camel_de::bare_style")]
673 pub(crate) bare_strings: Option<BareStyle>,
674 #[serde(deserialize_with = "camel_de::bare_style")]
675 pub(crate) bare_keys: Option<BareStyle>,
676 pub(crate) inline_objects: Option<bool>,
677 pub(crate) inline_arrays: Option<bool>,
678 pub(crate) multiline_strings: Option<bool>,
679 #[serde(deserialize_with = "camel_de::multiline_style")]
680 pub(crate) multiline_style: Option<MultilineStyle>,
681 pub(crate) multiline_min_lines: Option<usize>,
682 pub(crate) multiline_max_lines: Option<usize>,
683 pub(crate) tables: Option<bool>,
684 pub(crate) table_fold: Option<bool>,
685 #[serde(deserialize_with = "camel_de::table_unindent_style")]
686 pub(crate) table_unindent_style: Option<TableUnindentStyle>,
687 pub(crate) table_min_rows: Option<usize>,
688 pub(crate) table_min_columns: Option<usize>,
689 pub(crate) table_min_similarity: Option<f32>,
690 pub(crate) table_column_max_width: Option<usize>,
691 #[serde(deserialize_with = "camel_de::string_array_style")]
692 pub(crate) string_array_style: Option<StringArrayStyle>,
693 #[serde(deserialize_with = "camel_de::fold_style")]
694 pub(crate) fold: Option<FoldStyle>,
695 #[serde(deserialize_with = "camel_de::fold_style")]
696 pub(crate) number_fold_style: Option<FoldStyle>,
697 #[serde(deserialize_with = "camel_de::fold_style")]
698 pub(crate) string_bare_fold_style: Option<FoldStyle>,
699 #[serde(deserialize_with = "camel_de::fold_style")]
700 pub(crate) string_quoted_fold_style: Option<FoldStyle>,
701 #[serde(deserialize_with = "camel_de::fold_style")]
702 pub(crate) string_multiline_fold_style: Option<FoldStyle>,
703 #[serde(deserialize_with = "camel_de::indent_glyph_style")]
704 pub(crate) indent_glyph_style: Option<IndentGlyphStyle>,
705 #[serde(deserialize_with = "camel_de::indent_glyph_marker_style")]
706 pub(crate) indent_glyph_marker_style: Option<IndentGlyphMarkerStyle>,
707 pub(crate) kv_pack_multiple: Option<usize>,
708}
709
710impl From<TjsonConfig> for RenderOptions {
711 fn from(c: TjsonConfig) -> Self {
712 let mut opts = if c.canonical { RenderOptions::canonical() } else { RenderOptions::default() };
713 if let Some(v) = c.force_markers { opts = opts.force_markers(v); }
714 if let Some(w) = c.wrap_width { opts = opts.wrap_width(if w == 0 { None } else { Some(w) }); }
715 if let Some(v) = c.bare_strings { opts = opts.bare_strings(v); }
716 if let Some(v) = c.bare_keys { opts = opts.bare_keys(v); }
717 if let Some(v) = c.inline_objects { opts = opts.inline_objects(v); }
718 if let Some(v) = c.inline_arrays { opts = opts.inline_arrays(v); }
719 if let Some(v) = c.multiline_strings { opts = opts.multiline_strings(v); }
720 if let Some(v) = c.multiline_style { opts = opts.multiline_style(v); }
721 if let Some(v) = c.multiline_min_lines { opts = opts.multiline_min_lines(v); }
722 if let Some(v) = c.multiline_max_lines { opts = opts.multiline_max_lines(v); }
723 if let Some(v) = c.tables { opts = opts.tables(v); }
724 if let Some(v) = c.table_fold { opts = opts.table_fold(v); }
725 if let Some(v) = c.table_unindent_style { opts = opts.table_unindent_style(v); }
726 if let Some(v) = c.table_min_rows { opts = opts.table_min_rows(v); }
727 if let Some(v) = c.table_min_columns { opts = opts.table_min_columns(v); }
728 if let Some(v) = c.table_min_similarity { opts = opts.table_min_similarity(v); }
729 if let Some(v) = c.table_column_max_width { opts = opts.table_column_max_width(if v == 0 { None } else { Some(v) }); }
730 if let Some(v) = c.string_array_style { opts = opts.string_array_style(v); }
731 if let Some(v) = c.fold { opts = opts.fold(v); }
732 if let Some(v) = c.number_fold_style { opts = opts.number_fold_style(v); }
733 if let Some(v) = c.string_bare_fold_style { opts = opts.string_bare_fold_style(v); }
734 if let Some(v) = c.string_quoted_fold_style { opts = opts.string_quoted_fold_style(v); }
735 if let Some(v) = c.string_multiline_fold_style { opts = opts.string_multiline_fold_style(v); }
736 if let Some(v) = c.indent_glyph_style { opts = opts.indent_glyph_style(v); }
737 if let Some(v) = c.indent_glyph_marker_style { opts = opts.indent_glyph_marker_style(v); }
738 if let Some(v) = c.kv_pack_multiple { opts = opts.kv_pack_multiple_clamped(v); }
739 opts
740 }
741}
742