use stakker::LogVisitor;
use std::fmt;
use std::fmt::Arguments;
use std::fmt::Write;
pub struct KvSingleLine<'a> {
kvscan: &'a dyn Fn(&mut dyn LogVisitor),
prefix: &'static str,
suffix: &'static str,
}
impl<'a> KvSingleLine<'a> {
pub fn new(
kvscan: &'a dyn Fn(&mut dyn LogVisitor),
prefix: &'static str,
suffix: &'static str,
) -> Self {
Self {
kvscan,
prefix,
suffix,
}
}
}
impl<'a> fmt::Display for KvSingleLine<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut visitor = Visitor::new(f, self.prefix);
(self.kvscan)(&mut visitor);
if visitor.error {
Err(fmt::Error)
} else if visitor.empty {
Ok(()) } else {
f.write_str(self.suffix)
}
}
}
#[inline]
fn is_reserved(ch: char) -> bool {
ch <= ' '
|| ch == '"'
|| ch == '='
|| ch == '\\'
|| ch == '['
|| ch == ']'
|| ch == '{'
|| ch == '}'
}
#[inline]
fn push_str_val(f: &mut fmt::Formatter<'_>, val: &str) -> fmt::Result {
if val.find(is_reserved).is_some() {
f.write_char('"')?;
for ch in val.chars() {
if ch < ' ' || ch == '"' || ch == '\\' {
write!(f, "\\{:02X}", ch as u8)?;
} else {
f.write_char(ch)?;
}
}
f.write_char('"')?;
} else {
f.write_str(val)?;
}
Ok(())
}
macro_rules! catch {
($self:ident, $call:expr) => {{
if $call.is_err() {
$self.error = true;
}
}};
}
struct Visitor<'a, 'b: 'a> {
fmt: &'a mut fmt::Formatter<'b>,
fmtbuf: String,
prefix: &'static str, empty: bool,
error: bool,
}
impl<'a, 'b> Visitor<'a, 'b> {
fn new(fmt: &'a mut fmt::Formatter<'b>, prefix: &'static str) -> Self {
Self {
fmt,
fmtbuf: String::new(),
prefix,
empty: true,
error: false,
}
}
fn push_key(&mut self, key: Option<&str>, sep: Option<char>) {
catch!(self, self.fmt.write_str(self.prefix));
self.empty = false;
self.prefix = " ";
if let Some(key) = key {
if key.is_empty() {
catch!(self, self.fmt.write_str("\\20"));
} else {
for ch in key.chars() {
if is_reserved(ch) {
catch!(self, write!(self.fmt, "\\{:02X}", ch as u8));
} else {
catch!(self, self.fmt.write_char(ch));
}
}
}
if let Some(sep) = sep {
catch!(self, self.fmt.write_char(sep));
}
}
}
}
impl<'a, 'b> LogVisitor for Visitor<'a, 'b> {
fn kv_u64(&mut self, key: Option<&str>, val: u64) {
self.push_key(key, Some('='));
catch!(self, write!(self.fmt, "{}", val));
}
fn kv_i64(&mut self, key: Option<&str>, val: i64) {
self.push_key(key, Some('='));
catch!(self, write!(self.fmt, "{}", val));
}
fn kv_f64(&mut self, key: Option<&str>, val: f64) {
self.push_key(key, Some('='));
catch!(self, write!(self.fmt, "{}", val));
}
fn kv_bool(&mut self, key: Option<&str>, val: bool) {
self.push_key(key, Some('='));
catch!(self, write!(self.fmt, "{}", val));
}
fn kv_null(&mut self, key: Option<&str>) {
self.push_key(key, None);
}
fn kv_str(&mut self, key: Option<&str>, val: &str) {
self.push_key(key, Some('='));
catch!(self, push_str_val(self.fmt, val));
}
fn kv_fmt(&mut self, key: Option<&str>, val: &Arguments<'_>) {
self.push_key(key, Some('='));
if self.fmtbuf.capacity() == 0 {
self.fmtbuf = String::with_capacity(1024);
}
self.fmtbuf.clear();
catch!(self, write!(self.fmtbuf, "{}", val));
catch!(self, push_str_val(self.fmt, &self.fmtbuf));
}
fn kv_map(&mut self, key: Option<&str>) {
self.push_key(key, None);
catch!(self, self.fmt.write_str("{"));
self.prefix = "";
}
fn kv_mapend(&mut self, _: Option<&str>) {
catch!(self, self.fmt.write_str("}"));
self.prefix = " ";
}
fn kv_arr(&mut self, key: Option<&str>) {
self.push_key(key, None);
catch!(self, self.fmt.write_str("["));
self.prefix = "";
}
fn kv_arrend(&mut self, _: Option<&str>) {
catch!(self, self.fmt.write_str("]"));
self.prefix = " ";
}
}
#[cfg(test)]
mod test {
use super::{KvSingleLine, LogVisitor};
use std::fmt::Write;
fn kvscan(lv: &mut dyn LogVisitor) {
lv.kv_u64(Some("u64"), 123456789);
lv.kv_i64(Some("i64"), -123456789);
lv.kv_f64(Some("f64"), 12345.6789);
lv.kv_bool(Some("b0"), false);
lv.kv_bool(Some("b1"), true);
lv.kv_null(Some("null"));
lv.kv_str(Some("str"), "ABCDEFGHIJ");
lv.kv_str(Some("str_ctrl"), "ABC\tDEF");
lv.kv_str(Some("str_quote"), "ABC\"DEF\"GHI");
lv.kv_str(Some("str_bsl"), "ABC\\DEF\\GHI");
lv.kv_fmt(Some("fmt"), &format_args!("{}{}{}", "ABC", 123, "DEF"));
lv.kv_map(Some("map"));
lv.kv_u64(Some("map_u64"), 987654321);
lv.kv_str(Some("map_str"), "JIHGFEDCBA");
lv.kv_map(Some("map_nested"));
lv.kv_bool(Some("map_nested_bool"), false);
lv.kv_mapend(Some("map_nested"));
lv.kv_mapend(Some("map"));
lv.kv_map(Some("map_empty"));
lv.kv_mapend(Some("map_empty"));
lv.kv_arr(Some("arr"));
lv.kv_u64(None, 987654321);
lv.kv_str(None, "JIHGFEDCBA");
lv.kv_arr(None);
lv.kv_bool(None, true);
lv.kv_arrend(None);
lv.kv_arrend(Some("arr"));
lv.kv_arr(Some("arr_empty"));
lv.kv_arrend(Some("arr_empty"));
}
fn append(
s: &mut String,
kvscan: &dyn Fn(&mut dyn LogVisitor),
prefix: &'static str,
suffix: &'static str,
) {
write!(s, "{}", KvSingleLine::new(kvscan, prefix, suffix)).unwrap();
}
#[test]
fn test() {
let mut buf = "dummy=1".to_string();
append(&mut buf, &kvscan, " ", "");
println!("{}", buf);
assert_eq!(buf, "dummy=1 u64=123456789 i64=-123456789 f64=12345.6789 b0=false b1=true null str=ABCDEFGHIJ str_ctrl=\"ABC\\09DEF\" str_quote=\"ABC\\22DEF\\22GHI\" str_bsl=\"ABC\\5CDEF\\5CGHI\" fmt=ABC123DEF map{map_u64=987654321 map_str=JIHGFEDCBA map_nested{map_nested_bool=false}} map_empty{} arr[987654321 JIHGFEDCBA [true]] arr_empty[]");
}
}