use std::str::FromStr;
use serde::{Deserialize, Serialize};
pub const MIN_WRAP_WIDTH: usize = 20;
pub const DEFAULT_WRAP_WIDTH: usize = 80;
pub(crate) const MIN_FOLD_CONTINUATION: usize = 10;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum IndentGlyphStyle {
#[default]
Auto,
Fixed,
None,
}
impl FromStr for IndentGlyphStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"auto" => Ok(Self::Auto),
"fixed" => Ok(Self::Fixed),
"none" => Ok(Self::None),
_ => Err(format!(
"invalid indent glyph style '{input}' (expected one of: auto, fixed, none)"
)),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum IndentGlyphMarkerStyle {
#[default]
Compact,
Separate,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(dead_code)]
pub(crate) enum IndentGlyphMode {
IndentWeighted(f64),
ByteWeighted(f64),
Fixed,
None,
}
pub(crate) fn indent_glyph_mode(options: &RenderOptions) -> IndentGlyphMode {
match options.indent_glyph_style {
IndentGlyphStyle::Auto => IndentGlyphMode::IndentWeighted(0.2),
IndentGlyphStyle::Fixed => IndentGlyphMode::Fixed,
IndentGlyphStyle::None => IndentGlyphMode::None,
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum TableUnindentStyle {
Left,
#[default]
Auto,
Floating,
None,
}
impl FromStr for TableUnindentStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"left" => Ok(Self::Left),
"auto" => Ok(Self::Auto),
"floating" => Ok(Self::Floating),
"none" => Ok(Self::None),
_ => Err(format!(
"invalid table unindent style '{input}' (expected one of: left, auto, floating, none)"
)),
}
}
}
#[non_exhaustive]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
struct ParseOptions {
start_indent: usize,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct RenderOptions {
pub(crate) wrap_width: Option<usize>,
pub(crate) start_indent: usize,
pub(crate) force_markers: bool,
pub(crate) bare_strings: BareStyle,
pub(crate) bare_keys: BareStyle,
pub(crate) inline_objects: bool,
pub(crate) inline_arrays: bool,
pub(crate) string_array_style: StringArrayStyle,
pub(crate) number_fold_style: FoldStyle,
pub(crate) string_bare_fold_style: FoldStyle,
pub(crate) string_quoted_fold_style: FoldStyle,
pub(crate) string_multiline_fold_style: FoldStyle,
pub(crate) tables: bool,
pub(crate) table_fold: bool,
pub(crate) table_unindent_style: TableUnindentStyle,
pub(crate) indent_glyph_style: IndentGlyphStyle,
pub(crate) indent_glyph_marker_style: IndentGlyphMarkerStyle,
pub(crate) table_min_rows: usize,
pub(crate) table_min_columns: usize,
pub(crate) table_min_similarity: f32,
pub(crate) table_column_max_width: Option<usize>,
pub(crate) kv_pack_multiple: usize,
pub(crate) multiline_strings: bool,
pub(crate) multiline_style: MultilineStyle,
pub(crate) multiline_min_lines: usize,
pub(crate) multiline_max_lines: usize,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum FoldStyle {
#[default]
Auto,
Fixed,
None,
}
impl FromStr for FoldStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"auto" => Ok(Self::Auto),
"fixed" => Ok(Self::Fixed),
"none" => Ok(Self::None),
_ => Err(format!(
"invalid fold style '{input}' (expected one of: auto, fixed, none)"
)),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum MultilineStyle {
Floating,
#[default]
Bold,
BoldFloating,
Transparent,
Light,
FoldingQuotes,
}
impl FromStr for MultilineStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"bold" => Ok(Self::Bold),
"floating" => Ok(Self::Floating),
"bold-floating" => Ok(Self::BoldFloating),
"transparent" => Ok(Self::Transparent),
"light" => Ok(Self::Light),
"folding-quotes" => Ok(Self::FoldingQuotes),
_ => Err(format!(
"invalid multiline style '{input}' (expected one of: bold, floating, bold-floating, transparent, light, folding-quotes)"
)),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum BareStyle {
#[default]
Prefer,
None,
}
impl FromStr for BareStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"prefer" => Ok(Self::Prefer),
"none" => Ok(Self::None),
_ => Err(format!(
"invalid bare style '{input}' (expected one of: prefer, none)"
)),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum StringArrayStyle {
Spaces,
PreferSpaces,
Comma,
#[default]
PreferComma,
None,
}
impl FromStr for StringArrayStyle {
type Err = String;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
match input {
"spaces" => Ok(Self::Spaces),
"prefer-spaces" => Ok(Self::PreferSpaces),
"comma" => Ok(Self::Comma),
"prefer-comma" => Ok(Self::PreferComma),
"none" => Ok(Self::None),
_ => Err(format!(
"invalid string array style '{input}' (expected one of: spaces, prefer-spaces, comma, prefer-comma, none)"
)),
}
}
}
impl RenderOptions {
pub fn canonical() -> Self {
Self {
inline_objects: false,
inline_arrays: false,
string_array_style: StringArrayStyle::None,
tables: false,
multiline_strings: false,
number_fold_style: FoldStyle::None,
string_bare_fold_style: FoldStyle::None,
string_quoted_fold_style: FoldStyle::None,
string_multiline_fold_style: FoldStyle::None,
indent_glyph_style: IndentGlyphStyle::None,
..Self::default()
}
}
pub fn force_markers(mut self, force_markers: bool) -> Self {
self.force_markers = force_markers;
self
}
pub fn bare_strings(mut self, bare_strings: BareStyle) -> Self {
self.bare_strings = bare_strings;
self
}
pub fn bare_keys(mut self, bare_keys: BareStyle) -> Self {
self.bare_keys = bare_keys;
self
}
pub fn inline_objects(mut self, inline_objects: bool) -> Self {
self.inline_objects = inline_objects;
self
}
pub fn inline_arrays(mut self, inline_arrays: bool) -> Self {
self.inline_arrays = inline_arrays;
self
}
pub fn string_array_style(mut self, string_array_style: StringArrayStyle) -> Self {
self.string_array_style = string_array_style;
self
}
pub fn tables(mut self, tables: bool) -> Self {
self.tables = tables;
self
}
pub fn wrap_width(mut self, wrap_width: Option<usize>) -> Self {
self.wrap_width = wrap_width.map(|w| w.clamp(MIN_WRAP_WIDTH, usize::MAX));
self
}
pub fn wrap_width_checked(self, wrap_width: Option<usize>) -> std::result::Result<Self, String> {
if let Some(w) = wrap_width
&& w < MIN_WRAP_WIDTH {
return Err(format!("wrap_width must be at least {MIN_WRAP_WIDTH}, got {w}"));
}
Ok(self.wrap_width(wrap_width))
}
pub fn table_min_rows(mut self, table_min_rows: usize) -> Self {
self.table_min_rows = table_min_rows;
self
}
pub fn table_min_columns(mut self, table_min_columns: usize) -> Self {
self.table_min_columns = table_min_columns;
self
}
pub fn table_min_similarity(mut self, v: f32) -> Self {
self.table_min_similarity = v;
self
}
pub fn table_column_max_width(mut self, table_column_max_width: Option<usize>) -> Self {
self.table_column_max_width = table_column_max_width;
self
}
pub fn kv_pack_multiple(mut self, v: usize) -> std::result::Result<Self, String> {
if !(1..=4).contains(&v) {
return Err(format!("kv_pack_multiple must be 1–4, got {v}"));
}
self.kv_pack_multiple = v;
Ok(self)
}
pub fn kv_pack_multiple_clamped(mut self, v: usize) -> Self {
self.kv_pack_multiple = v.clamp(1, 4);
self
}
pub fn fold(self, style: FoldStyle) -> Self {
self.number_fold_style(style)
.string_bare_fold_style(style)
.string_quoted_fold_style(style)
.string_multiline_fold_style(style)
}
pub fn number_fold_style(mut self, style: FoldStyle) -> Self {
self.number_fold_style = style;
self
}
pub fn string_bare_fold_style(mut self, style: FoldStyle) -> Self {
self.string_bare_fold_style = style;
self
}
pub fn string_quoted_fold_style(mut self, style: FoldStyle) -> Self {
self.string_quoted_fold_style = style;
self
}
pub fn string_multiline_fold_style(mut self, style: FoldStyle) -> Self {
self.string_multiline_fold_style = style;
self
}
pub fn table_fold(mut self, table_fold: bool) -> Self {
self.table_fold = table_fold;
self
}
pub fn table_unindent_style(mut self, style: TableUnindentStyle) -> Self {
self.table_unindent_style = style;
self
}
pub fn indent_glyph_style(mut self, style: IndentGlyphStyle) -> Self {
self.indent_glyph_style = style;
self
}
pub fn indent_glyph_marker_style(mut self, style: IndentGlyphMarkerStyle) -> Self {
self.indent_glyph_marker_style = style;
self
}
pub fn multiline_strings(mut self, multiline_strings: bool) -> Self {
self.multiline_strings = multiline_strings;
self
}
pub fn multiline_style(mut self, multiline_style: MultilineStyle) -> Self {
self.multiline_style = multiline_style;
self
}
pub fn multiline_min_lines(mut self, multiline_min_lines: usize) -> Self {
self.multiline_min_lines = multiline_min_lines;
self
}
pub fn multiline_max_lines(mut self, multiline_max_lines: usize) -> Self {
self.multiline_max_lines = multiline_max_lines;
self
}
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
start_indent: 0,
force_markers: false,
bare_strings: BareStyle::Prefer,
bare_keys: BareStyle::Prefer,
inline_objects: true,
inline_arrays: true,
string_array_style: StringArrayStyle::PreferComma,
tables: true,
wrap_width: Some(DEFAULT_WRAP_WIDTH),
table_min_rows: 3,
table_min_columns: 3,
table_min_similarity: 0.8,
table_column_max_width: Some(40),
kv_pack_multiple: 2,
number_fold_style: FoldStyle::Auto,
string_bare_fold_style: FoldStyle::Auto,
string_quoted_fold_style: FoldStyle::Auto,
string_multiline_fold_style: FoldStyle::None,
table_fold: false,
table_unindent_style: TableUnindentStyle::Auto,
indent_glyph_style: IndentGlyphStyle::Auto,
indent_glyph_marker_style: IndentGlyphMarkerStyle::Compact,
multiline_strings: true,
multiline_style: MultilineStyle::Bold,
multiline_min_lines: 1,
multiline_max_lines: 10,
}
}
}
mod camel_de {
use serde::{Deserialize, Deserializer};
fn de_str<'de, D: Deserializer<'de>>(d: D) -> Result<Option<String>, D::Error> {
Option::<String>::deserialize(d)
}
macro_rules! camel_option_de {
($fn_name:ident, $Enum:ty, $($camel:literal => $variant:expr),+ $(,)?) => {
pub fn $fn_name<'de, D: Deserializer<'de>>(d: D) -> Result<Option<$Enum>, D::Error> {
let Some(s) = de_str(d)? else { return Ok(None); };
match s.as_str() {
$($camel => return Ok(Some($variant)),)+
_ => {}
}
serde_json::from_value(serde_json::Value::String(s.clone()))
.map(Some)
.map_err(|_| serde::de::Error::unknown_variant(&s, &[$($camel),+]))
}
};
}
camel_option_de!(bare_style, super::BareStyle,
"prefer" => super::BareStyle::Prefer,
"none" => super::BareStyle::None,
);
camel_option_de!(fold_style, super::FoldStyle,
"auto" => super::FoldStyle::Auto,
"fixed" => super::FoldStyle::Fixed,
"none" => super::FoldStyle::None,
);
camel_option_de!(multiline_style, super::MultilineStyle,
"floating" => super::MultilineStyle::Floating,
"bold" => super::MultilineStyle::Bold,
"boldFloating" => super::MultilineStyle::BoldFloating,
"transparent" => super::MultilineStyle::Transparent,
"light" => super::MultilineStyle::Light,
"foldingQuotes" => super::MultilineStyle::FoldingQuotes,
);
camel_option_de!(table_unindent_style, super::TableUnindentStyle,
"left" => super::TableUnindentStyle::Left,
"auto" => super::TableUnindentStyle::Auto,
"floating" => super::TableUnindentStyle::Floating,
"none" => super::TableUnindentStyle::None,
);
camel_option_de!(indent_glyph_style, super::IndentGlyphStyle,
"auto" => super::IndentGlyphStyle::Auto,
"fixed" => super::IndentGlyphStyle::Fixed,
"none" => super::IndentGlyphStyle::None,
);
camel_option_de!(indent_glyph_marker_style, super::IndentGlyphMarkerStyle,
"compact" => super::IndentGlyphMarkerStyle::Compact,
"separate" => super::IndentGlyphMarkerStyle::Separate,
);
camel_option_de!(string_array_style, super::StringArrayStyle,
"spaces" => super::StringArrayStyle::Spaces,
"preferSpaces" => super::StringArrayStyle::PreferSpaces,
"comma" => super::StringArrayStyle::Comma,
"preferComma" => super::StringArrayStyle::PreferComma,
"none" => super::StringArrayStyle::None,
);
}
#[doc(hidden)]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct TjsonConfig {
pub(crate) canonical: bool,
pub(crate) force_markers: Option<bool>,
pub(crate) wrap_width: Option<usize>,
#[serde(deserialize_with = "camel_de::bare_style")]
pub(crate) bare_strings: Option<BareStyle>,
#[serde(deserialize_with = "camel_de::bare_style")]
pub(crate) bare_keys: Option<BareStyle>,
pub(crate) inline_objects: Option<bool>,
pub(crate) inline_arrays: Option<bool>,
pub(crate) multiline_strings: Option<bool>,
#[serde(deserialize_with = "camel_de::multiline_style")]
pub(crate) multiline_style: Option<MultilineStyle>,
pub(crate) multiline_min_lines: Option<usize>,
pub(crate) multiline_max_lines: Option<usize>,
pub(crate) tables: Option<bool>,
pub(crate) table_fold: Option<bool>,
#[serde(deserialize_with = "camel_de::table_unindent_style")]
pub(crate) table_unindent_style: Option<TableUnindentStyle>,
pub(crate) table_min_rows: Option<usize>,
pub(crate) table_min_columns: Option<usize>,
pub(crate) table_min_similarity: Option<f32>,
pub(crate) table_column_max_width: Option<usize>,
#[serde(deserialize_with = "camel_de::string_array_style")]
pub(crate) string_array_style: Option<StringArrayStyle>,
#[serde(deserialize_with = "camel_de::fold_style")]
pub(crate) fold: Option<FoldStyle>,
#[serde(deserialize_with = "camel_de::fold_style")]
pub(crate) number_fold_style: Option<FoldStyle>,
#[serde(deserialize_with = "camel_de::fold_style")]
pub(crate) string_bare_fold_style: Option<FoldStyle>,
#[serde(deserialize_with = "camel_de::fold_style")]
pub(crate) string_quoted_fold_style: Option<FoldStyle>,
#[serde(deserialize_with = "camel_de::fold_style")]
pub(crate) string_multiline_fold_style: Option<FoldStyle>,
#[serde(deserialize_with = "camel_de::indent_glyph_style")]
pub(crate) indent_glyph_style: Option<IndentGlyphStyle>,
#[serde(deserialize_with = "camel_de::indent_glyph_marker_style")]
pub(crate) indent_glyph_marker_style: Option<IndentGlyphMarkerStyle>,
pub(crate) kv_pack_multiple: Option<usize>,
}
impl From<TjsonConfig> for RenderOptions {
fn from(c: TjsonConfig) -> Self {
let mut opts = if c.canonical { RenderOptions::canonical() } else { RenderOptions::default() };
if let Some(v) = c.force_markers { opts = opts.force_markers(v); }
if let Some(w) = c.wrap_width { opts = opts.wrap_width(if w == 0 { None } else { Some(w) }); }
if let Some(v) = c.bare_strings { opts = opts.bare_strings(v); }
if let Some(v) = c.bare_keys { opts = opts.bare_keys(v); }
if let Some(v) = c.inline_objects { opts = opts.inline_objects(v); }
if let Some(v) = c.inline_arrays { opts = opts.inline_arrays(v); }
if let Some(v) = c.multiline_strings { opts = opts.multiline_strings(v); }
if let Some(v) = c.multiline_style { opts = opts.multiline_style(v); }
if let Some(v) = c.multiline_min_lines { opts = opts.multiline_min_lines(v); }
if let Some(v) = c.multiline_max_lines { opts = opts.multiline_max_lines(v); }
if let Some(v) = c.tables { opts = opts.tables(v); }
if let Some(v) = c.table_fold { opts = opts.table_fold(v); }
if let Some(v) = c.table_unindent_style { opts = opts.table_unindent_style(v); }
if let Some(v) = c.table_min_rows { opts = opts.table_min_rows(v); }
if let Some(v) = c.table_min_columns { opts = opts.table_min_columns(v); }
if let Some(v) = c.table_min_similarity { opts = opts.table_min_similarity(v); }
if let Some(v) = c.table_column_max_width { opts = opts.table_column_max_width(if v == 0 { None } else { Some(v) }); }
if let Some(v) = c.string_array_style { opts = opts.string_array_style(v); }
if let Some(v) = c.fold { opts = opts.fold(v); }
if let Some(v) = c.number_fold_style { opts = opts.number_fold_style(v); }
if let Some(v) = c.string_bare_fold_style { opts = opts.string_bare_fold_style(v); }
if let Some(v) = c.string_quoted_fold_style { opts = opts.string_quoted_fold_style(v); }
if let Some(v) = c.string_multiline_fold_style { opts = opts.string_multiline_fold_style(v); }
if let Some(v) = c.indent_glyph_style { opts = opts.indent_glyph_style(v); }
if let Some(v) = c.indent_glyph_marker_style { opts = opts.indent_glyph_marker_style(v); }
if let Some(v) = c.kv_pack_multiple { opts = opts.kv_pack_multiple_clamped(v); }
opts
}
}