use alloc::string::String;
use crate::io::destinations::buffer::Buffer as BufferDestination;
use crate::nodes::node::{Node, Numeric};
use crate::stringify::default::stringify as yaml_stringify;
#[derive(Debug, Clone)]
pub struct FormatOptions {
pub indent: usize,
pub line_width: usize,
pub quote_style: QuoteStyle,
pub collection_style: CollectionStyle,
pub explicit_start: bool,
pub explicit_end: bool,
pub document_separator_lines: usize,
pub preserve_formatting: bool,
pub sort_keys: bool,
pub emit_null: bool,
pub flow_empty_collections: bool,
pub block_threshold: usize,
}
impl Default for FormatOptions {
fn default() -> Self {
Self {
indent: 2,
line_width: 80,
quote_style: QuoteStyle::Auto,
collection_style: CollectionStyle::Auto,
explicit_start: false,
explicit_end: false,
document_separator_lines: 1,
preserve_formatting: true,
sort_keys: false,
emit_null: true,
flow_empty_collections: true,
block_threshold: 3,
}
}
}
impl FormatOptions {
pub fn new() -> Self {
Self::default()
}
pub fn compact() -> Self {
Self {
indent: 2,
line_width: 0,
quote_style: QuoteStyle::Auto,
collection_style: CollectionStyle::Flow,
explicit_start: false,
explicit_end: false,
document_separator_lines: 0,
preserve_formatting: false,
sort_keys: false,
emit_null: false,
flow_empty_collections: true,
block_threshold: 100,
}
}
pub fn pretty() -> Self {
Self {
indent: 2,
line_width: 80,
quote_style: QuoteStyle::Auto,
collection_style: CollectionStyle::Block,
explicit_start: true,
explicit_end: false,
document_separator_lines: 1,
preserve_formatting: false,
sort_keys: true,
emit_null: true,
flow_empty_collections: true,
block_threshold: 3,
}
}
pub fn minimal() -> Self {
Self {
indent: 2,
line_width: 0,
quote_style: QuoteStyle::None,
collection_style: CollectionStyle::Flow,
explicit_start: false,
explicit_end: false,
document_separator_lines: 0,
preserve_formatting: false,
sort_keys: false,
emit_null: false,
flow_empty_collections: true,
block_threshold: 100,
}
}
pub fn with_indent(mut self, indent: usize) -> Self {
self.indent = indent;
self
}
pub fn with_line_width(mut self, width: usize) -> Self {
self.line_width = width;
self
}
pub fn with_quote_style(mut self, style: QuoteStyle) -> Self {
self.quote_style = style;
self
}
pub fn with_collection_style(mut self, style: CollectionStyle) -> Self {
self.collection_style = style;
self
}
pub fn with_explicit_markers(mut self, start: bool, end: bool) -> Self {
self.explicit_start = start;
self.explicit_end = end;
self
}
pub fn with_sorted_keys(mut self, sort: bool) -> Self {
self.sort_keys = sort;
self
}
pub fn with_block_threshold(mut self, threshold: usize) -> Self {
self.block_threshold = threshold;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuoteStyle {
Auto,
None,
Single,
Double,
AlwaysSingle,
AlwaysDouble,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CollectionStyle {
Auto,
Block,
Flow,
AlwaysBlock,
AlwaysFlow,
}
#[derive(Debug, Clone)]
pub struct FormatContext {
pub level: usize,
pub column: usize,
pub parent_style: CollectionStyle,
pub at_line_start: bool,
}
impl FormatContext {
pub fn new() -> Self {
Self {
level: 0,
column: 0,
parent_style: CollectionStyle::Block,
at_line_start: true,
}
}
pub fn indent(&mut self) {
self.level += 1;
}
pub fn dedent(&mut self) {
if self.level > 0 {
self.level -= 1;
}
}
pub fn indent_str(&self, options: &FormatOptions) -> String {
" ".repeat(self.level * options.indent)
}
pub fn advance(&mut self, count: usize) {
self.column += count;
self.at_line_start = false;
}
pub fn newline(&mut self) {
self.column = 0;
self.at_line_start = true;
}
}
impl Default for FormatContext {
fn default() -> Self {
Self::new()
}
}
pub fn numeric_to_string_lossy(num: &Numeric) -> String {
num.to_string_lossy()
}
pub fn node_to_string_lossy(node: &Node) -> String {
match node {
Node::Str(s, _, _) => s.clone(),
Node::Number(num) => numeric_to_string_lossy(num),
Node::Boolean(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
Node::None => "null".to_string(),
Node::Comment(c) => c.clone(),
Node::Alias(name) => name.clone(),
_ => {
let mut buf = BufferDestination::new();
if yaml_stringify(node, &mut buf).is_ok() {
buf.to_string()
} else {
format!("{:?}", node)
}
}
}
}
pub fn node_to_key_like_string(node: &Node) -> String {
match node {
Node::None => String::new(),
_ => node_to_string_lossy(node),
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_format_options_defaults() {
let opts = FormatOptions::default();
assert_eq!(opts.indent, 2);
assert_eq!(opts.line_width, 80);
assert_eq!(opts.quote_style, QuoteStyle::Auto);
assert_eq!(opts.collection_style, CollectionStyle::Auto);
assert!(!opts.explicit_start);
assert!(!opts.explicit_end);
assert_eq!(opts.document_separator_lines, 1);
assert!(opts.preserve_formatting);
assert!(!opts.sort_keys);
assert!(opts.emit_null);
assert!(opts.flow_empty_collections);
assert_eq!(opts.block_threshold, 3);
}
#[test]
fn test_format_options_builder() {
let opts = FormatOptions::new()
.with_indent(3)
.with_line_width(100)
.with_sorted_keys(true)
.with_explicit_markers(true, false)
.with_block_threshold(5);
assert_eq!(opts.indent, 3);
assert_eq!(opts.line_width, 100);
assert!(opts.sort_keys);
assert!(opts.explicit_start);
assert!(!opts.explicit_end);
assert_eq!(opts.block_threshold, 5);
}
#[test]
fn test_format_context_newline_and_column() {
let mut ctx = FormatContext::new();
ctx.advance(2);
assert_eq!(ctx.column, 2);
ctx.newline();
assert_eq!(ctx.column, 0);
assert!(ctx.at_line_start);
}
#[test]
fn test_format_context_indent_and_dedent() {
let mut ctx = FormatContext::new();
ctx.indent();
ctx.indent();
assert_eq!(ctx.level, 2);
ctx.dedent();
assert_eq!(ctx.level, 1);
ctx.dedent();
assert_eq!(ctx.level, 0);
}
#[test]
fn test_format_options_explicit_markers() {
let opts = FormatOptions::new().with_explicit_markers(true, true);
assert!(opts.explicit_start);
assert!(opts.explicit_end);
}
use super::*;
#[test]
fn test_default_options() {
let opts = FormatOptions::default();
assert_eq!(opts.indent, 2);
assert_eq!(opts.line_width, 80);
assert!(!opts.explicit_start);
assert!(opts.preserve_formatting);
}
#[test]
fn test_compact_options() {
let opts = FormatOptions::compact();
assert_eq!(opts.line_width, 0);
assert_eq!(opts.collection_style, CollectionStyle::Flow);
assert!(!opts.emit_null);
assert!(!opts.preserve_formatting);
}
#[test]
fn test_pretty_options() {
let opts = FormatOptions::pretty();
assert!(opts.explicit_start);
assert!(opts.sort_keys);
assert_eq!(opts.collection_style, CollectionStyle::Block);
}
#[test]
fn test_builder_pattern() {
let opts = FormatOptions::new()
.with_indent(4)
.with_line_width(120)
.with_sorted_keys(true)
.with_explicit_markers(true, true);
assert_eq!(opts.indent, 4);
assert_eq!(opts.line_width, 120);
assert!(opts.sort_keys);
assert!(opts.explicit_start);
assert!(opts.explicit_end);
}
#[test]
fn test_format_context() {
let mut ctx = FormatContext::new();
assert_eq!(ctx.level, 0);
assert!(ctx.at_line_start);
ctx.indent();
assert_eq!(ctx.level, 1);
ctx.advance(5);
assert_eq!(ctx.column, 5);
assert!(!ctx.at_line_start);
ctx.newline();
assert_eq!(ctx.column, 0);
assert!(ctx.at_line_start);
ctx.dedent();
assert_eq!(ctx.level, 0);
}
#[test]
fn test_indent_str() {
let opts = FormatOptions::new();
let mut ctx = FormatContext::new();
assert_eq!(ctx.indent_str(&opts), "");
ctx.indent();
assert_eq!(ctx.indent_str(&opts), " ");
ctx.indent();
assert_eq!(ctx.indent_str(&opts), " ");
}
#[test]
fn test_indent_str_custom() {
let opts = FormatOptions::new().with_indent(4);
let mut ctx = FormatContext::new();
ctx.indent();
assert_eq!(ctx.indent_str(&opts), " ");
ctx.indent();
assert_eq!(ctx.indent_str(&opts), " ");
}
}