use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Default,
Compact,
Csv,
Flat,
Ini,
Json,
Xml,
}
impl OutputFormat {
pub fn as_str(&self) -> &'static str {
match self {
Self::Default => "default",
Self::Compact => "compact",
Self::Csv => "csv",
Self::Flat => "flat",
Self::Ini => "ini",
Self::Json => "json",
Self::Xml => "xml",
}
}
pub fn supports_nested(&self) -> bool {
matches!(self, Self::Json | Self::Xml)
}
pub fn is_human_readable(&self) -> bool {
matches!(self, Self::Default | Self::Ini)
}
pub fn is_machine_parseable(&self) -> bool {
matches!(self, Self::Json | Self::Xml | Self::Csv | Self::Flat)
}
}
impl Default for OutputFormat {
fn default() -> Self {
Self::Json
}
}
impl fmt::Display for OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Default)]
pub struct WriterOptions {
pub string_validation: Option<StringValidation>,
pub string_validation_replacement: Option<String>,
pub compact: bool,
pub fully_qualified: bool,
pub xsd_strict: bool,
pub nokey: bool,
pub noprint_wrappers: bool,
pub item_sep: Option<char>,
pub escape: Option<EscapeMode>,
pub print_section: bool,
pub sep_char: Option<char>,
pub hierarchical: bool,
}
impl WriterOptions {
pub fn new() -> Self {
Self::default()
}
pub fn string_validation(mut self, mode: StringValidation) -> Self {
self.string_validation = Some(mode);
self
}
pub fn string_validation_replacement(mut self, replacement: impl Into<String>) -> Self {
self.string_validation_replacement = Some(replacement.into());
self
}
pub fn compact(mut self, enable: bool) -> Self {
self.compact = enable;
self
}
pub fn fully_qualified(mut self, enable: bool) -> Self {
self.fully_qualified = enable;
self
}
pub fn xsd_strict(mut self, enable: bool) -> Self {
self.xsd_strict = enable;
if enable {
self.fully_qualified = true;
}
self
}
pub fn nokey(mut self, enable: bool) -> Self {
self.nokey = enable;
self
}
pub fn noprint_wrappers(mut self, enable: bool) -> Self {
self.noprint_wrappers = enable;
self
}
pub fn item_sep(mut self, sep: char) -> Self {
self.item_sep = Some(sep);
self
}
pub fn escape(mut self, mode: EscapeMode) -> Self {
self.escape = Some(mode);
self
}
pub fn print_section(mut self, enable: bool) -> Self {
self.print_section = enable;
self
}
pub fn sep_char(mut self, sep: char) -> Self {
self.sep_char = Some(sep);
self
}
pub fn hierarchical(mut self, enable: bool) -> Self {
self.hierarchical = enable;
self
}
pub fn build_args(&self, format: OutputFormat) -> Vec<String> {
let mut args = Vec::new();
let prefix = format.as_str();
if let Some(ref validation) = self.string_validation {
args.push(format!("{}:string_validation={}", prefix, validation.as_str()));
}
if let Some(ref replacement) = self.string_validation_replacement {
args.push(format!("{}:string_validation_replacement={}", prefix, replacement));
}
match format {
OutputFormat::Json => {
if self.compact {
args.push(format!("{}:compact=1", prefix));
}
}
OutputFormat::Xml => {
if self.fully_qualified {
args.push(format!("{}:fully_qualified=1", prefix));
}
if self.xsd_strict {
args.push(format!("{}:xsd_strict=1", prefix));
}
}
OutputFormat::Default => {
if self.nokey {
args.push(format!("{}:nokey=1", prefix));
}
if self.noprint_wrappers {
args.push(format!("{}:noprint_wrappers=1", prefix));
}
}
OutputFormat::Compact | OutputFormat::Csv => {
if self.nokey {
args.push(format!("{}:nokey=1", prefix));
}
if let Some(sep) = self.item_sep {
args.push(format!("{}:item_sep={}", prefix, sep));
}
if let Some(ref escape) = self.escape {
args.push(format!("{}:escape={}", prefix, escape.as_str()));
}
if self.print_section {
args.push(format!("{}:print_section=1", prefix));
}
}
OutputFormat::Flat => {
if let Some(sep) = self.sep_char {
args.push(format!("{}:sep_char={}", prefix, sep));
}
if self.hierarchical {
args.push(format!("{}:hierarchical=1", prefix));
}
}
OutputFormat::Ini => {
if self.hierarchical {
args.push(format!("{}:hierarchical=1", prefix));
}
}
}
args
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringValidation {
Fail,
Ignore,
Replace,
}
impl StringValidation {
pub fn as_str(&self) -> &'static str {
match self {
Self::Fail => "fail",
Self::Ignore => "ignore",
Self::Replace => "replace",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EscapeMode {
C,
Csv,
None,
}
impl EscapeMode {
pub fn as_str(&self) -> &'static str {
match self {
Self::C => "c",
Self::Csv => "csv",
Self::None => "none",
}
}
}
pub mod presets {
use super::*;
pub fn json_api() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Json,
WriterOptions::new()
.compact(true)
.string_validation(StringValidation::Replace)
.string_validation_replacement(""),
)
}
pub fn json_pretty() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Json,
WriterOptions::new()
.compact(false)
.string_validation(StringValidation::Replace),
)
}
pub fn xml_strict() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Xml,
WriterOptions::new()
.xsd_strict(true)
.string_validation(StringValidation::Fail),
)
}
pub fn csv_excel() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Csv,
WriterOptions::new()
.item_sep(',')
.escape(EscapeMode::Csv)
.nokey(true),
)
}
pub fn flat_shell() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Flat,
WriterOptions::new()
.sep_char('_')
.hierarchical(true),
)
}
pub fn ini_config() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Ini,
WriterOptions::new().hierarchical(true),
)
}
pub fn compact_log() -> (OutputFormat, WriterOptions) {
(
OutputFormat::Compact,
WriterOptions::new()
.item_sep('|')
.print_section(true)
.escape(EscapeMode::C),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_format() {
assert_eq!(OutputFormat::Json.as_str(), "json");
assert!(OutputFormat::Json.supports_nested());
assert!(OutputFormat::Json.is_machine_parseable());
assert!(!OutputFormat::Json.is_human_readable());
}
#[test]
fn test_writer_options() {
let opts = WriterOptions::new()
.compact(true)
.string_validation(StringValidation::Replace)
.string_validation_replacement("?");
let args = opts.build_args(OutputFormat::Json);
assert!(args.iter().any(|arg| arg.contains("compact=1")));
assert!(args.iter().any(|arg| arg.contains("string_validation=replace")));
}
#[test]
fn test_presets() {
let (format, opts) = presets::json_api();
assert_eq!(format, OutputFormat::Json);
assert!(opts.compact);
let (format, opts) = presets::xml_strict();
assert_eq!(format, OutputFormat::Xml);
assert!(opts.xsd_strict);
assert!(opts.fully_qualified);
}
}