netidx-value 0.31.8

netidx value type
Documentation
use crate::{parser, Value};
use anyhow::{anyhow, Result};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use bytes::BytesMut;
use compact_str::CompactString;
use escaping::Escape;
use netidx_core::utils::pack;
use rust_decimal::Decimal;
use smallvec::smallvec;
use std::{
    cell::RefCell,
    fmt::{self, Write},
    ops::Deref,
};

struct DecimalFmt(Decimal);

impl fmt::Display for DecimalFmt {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // decimal, like float, must always end in '.', but the default
        // decimal printer does not do that, so we have to fix it
        thread_local! {
            static BUF: RefCell<CompactString> = RefCell::new(CompactString::new(""));
        }
        BUF.with_borrow_mut(|buf| {
            use std::fmt::Write;
            buf.clear();
            write!(buf, "{}", self.0)?;
            if buf.contains('.') {
                write!(f, "{buf}")
            } else {
                write!(f, "{buf}.")
            }
        })
    }
}

/// A value reference that formats without type tags
pub struct NakedValue<'a>(pub &'a Value);

impl<'a> Deref for NakedValue<'a> {
    type Target = Value;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<'a> fmt::Display for NakedValue<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt_naked(f)
    }
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.fmt_ext(f, &parser::VAL_ESC, true)
    }
}

pub fn printf(f: &mut impl Write, fmt: &str, args: &[Value]) -> Result<usize> {
    use compact_str::{format_compact, CompactString};
    use fish_printf::{printf_c_locale, Arg, ToArg};
    use rust_decimal::prelude::ToPrimitive;
    use smallvec::SmallVec;
    enum T<'a> {
        Arg(Arg<'a>),
        Index(usize),
    }
    let mut strings: SmallVec<[CompactString; 4]> = smallvec![];
    let mut fish_args: SmallVec<[T; 8]> = smallvec![];
    for v in args {
        fish_args.push(match v {
            Value::U8(v) => T::Arg(v.to_arg()),
            Value::I8(v) => T::Arg(v.to_arg()),
            Value::U16(v) => T::Arg(v.to_arg()),
            Value::I16(v) => T::Arg(v.to_arg()),
            Value::U32(v) | Value::V32(v) => T::Arg(v.to_arg()),
            Value::I32(v) | Value::Z32(v) => T::Arg(v.to_arg()),
            Value::U64(v) | Value::V64(v) => T::Arg(v.to_arg()),
            Value::I64(v) | Value::Z64(v) => T::Arg(v.to_arg()),
            Value::F32(v) => T::Arg(v.to_arg()),
            Value::F64(v) => T::Arg(v.to_arg()),
            Value::Decimal(v) => match v.to_f64() {
                Some(f) => T::Arg(f.to_arg()),
                None => {
                    strings.push(format_compact!("{v}"));
                    T::Index(strings.len() - 1)
                }
            },
            Value::DateTime(v) => {
                strings.push(format_compact!("{v}"));
                T::Index(strings.len() - 1)
            }
            Value::Duration(v) => {
                strings.push(format_compact!("{v:?}"));
                T::Index(strings.len() - 1)
            }
            Value::String(s) => T::Arg(s.to_arg()),
            Value::Bytes(b) => {
                strings.push(format_compact!("{}", BASE64.encode(b)));
                T::Index(strings.len() - 1)
            }
            Value::Bool(true) => T::Arg("true".to_arg()),
            Value::Bool(false) => T::Arg("false".to_arg()),
            Value::Null => T::Arg("null".to_arg()),
            v @ (Value::Error(_)
            | Value::Array(_)
            | Value::Map(_)
            | Value::Abstract(_)) => {
                strings.push(format_compact!("{v}"));
                T::Index(strings.len() - 1)
            }
        })
    }
    let mut fish_args: SmallVec<[Arg; 8]> = fish_args
        .into_iter()
        .map(|t| match t {
            T::Arg(a) => a,
            T::Index(i) => strings[i].to_arg(),
        })
        .collect();
    printf_c_locale(f, fmt, &mut fish_args).map_err(|e| anyhow!(format!("{e:?}")))
}

impl Value {
    pub fn to_string_naked(&self) -> String {
        format!("{}", NakedValue(self))
    }

    pub fn fmt_naked(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::U8(v) => write!(f, "{}", v),
            Value::I8(v) => write!(f, "{}", v),
            Value::U16(v) => write!(f, "{}", v),
            Value::I16(v) => write!(f, "{}", v),
            Value::U32(v) | Value::V32(v) => write!(f, "{}", v),
            Value::I32(v) | Value::Z32(v) => write!(f, "{}", v),
            Value::U64(v) | Value::V64(v) => write!(f, "{}", v),
            Value::I64(v) | Value::Z64(v) => write!(f, "{}", v),
            Value::F32(v) => write!(f, "{}", v),
            Value::F64(v) => write!(f, "{}", v),
            Value::Decimal(v) => write!(f, "{}", DecimalFmt(**v)),
            Value::DateTime(v) => write!(f, "{}", v),
            Value::Duration(v) => {
                let v = v.as_secs_f64();
                if v.fract() == 0. {
                    write!(f, "{}.s", v)
                } else {
                    write!(f, "{}s", v)
                }
            }
            Value::String(s) => write!(f, "\"{}\"", parser::VAL_ESC.escape(s)),
            Value::Bytes(b) => write!(f, "{}", BASE64.encode(b)),
            Value::Bool(true) => write!(f, "true"),
            Value::Bool(false) => write!(f, "false"),
            Value::Null => write!(f, "null"),
            v @ (Value::Error(_)
            | Value::Array(_)
            | Value::Map(_)
            | Value::Abstract(_)) => write!(f, "{}", v),
        }
    }

    pub fn fmt_notyp(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.fmt_ext(f, &parser::VAL_ESC, false)
    }

    pub fn fmt_ext(
        &self,
        f: &mut fmt::Formatter<'_>,
        esc: &Escape,
        types: bool,
    ) -> fmt::Result {
        match self {
            Value::U8(v) => {
                if types {
                    write!(f, "u8:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::I8(v) => {
                if types {
                    write!(f, "i8:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::U16(v) => {
                if types {
                    write!(f, "u16:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::I16(v) => {
                if types {
                    write!(f, "i16:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::U32(v) => {
                if types {
                    write!(f, "u32:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::V32(v) => {
                if types {
                    write!(f, "v32:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::I32(v) => {
                if types {
                    write!(f, "i32:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::Z32(v) => {
                if types {
                    write!(f, "z32:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::U64(v) => {
                if types {
                    write!(f, "u64:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::V64(v) => {
                if types {
                    write!(f, "v64:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::I64(v) => {
                if types {
                    write!(f, "i64:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::Z64(v) => {
                if types {
                    write!(f, "z64:{}", v)
                } else {
                    write!(f, "{}", v)
                }
            }
            Value::F32(v) => {
                let pfx = if types { "f32:" } else { "" };
                if v.fract() == 0. {
                    write!(f, "{}{}.", pfx, v)
                } else {
                    write!(f, "{}{}", pfx, v)
                }
            }
            Value::F64(v) => {
                let pfx = if types { "f64:" } else { "" };
                if v.fract() == 0. {
                    write!(f, "{}{}.", pfx, v)
                } else {
                    write!(f, "{}{}", pfx, v)
                }
            }
            Value::Decimal(v) => {
                if types {
                    write!(f, "decimal:{}", DecimalFmt(**v))
                } else {
                    write!(f, "{}", DecimalFmt(**v))
                }
            }
            Value::DateTime(v) => {
                if types {
                    write!(f, r#"datetime:"{}""#, v)
                } else {
                    write!(f, r#""{}""#, v)
                }
            }
            Value::Duration(v) => {
                let pfx = if types { "duration:" } else { "" };
                let v = v.as_secs_f64();
                if v.fract() == 0. {
                    write!(f, r#"{}{}.s"#, pfx, v)
                } else {
                    write!(f, r#"{}{}s"#, pfx, v)
                }
            }
            Value::String(s) => {
                write!(f, r#""{}""#, esc.escape(&*s))
            }
            Value::Bytes(b) => {
                let pfx = if types || b.is_empty() { "bytes:" } else { "" };
                if b.is_empty() {
                    write!(f, "{}==", pfx)
                } else {
                    write!(f, "{}{}", pfx, BASE64.encode(&*b))
                }
            }
            Value::Bool(true) => write!(f, "true"),
            Value::Bool(false) => write!(f, "false"),
            Value::Null => write!(f, "null"),
            Value::Error(v) => match &**v {
                Value::String(s) => {
                    write!(f, r#"error:"{}""#, esc.escape(&*s))
                }
                v => {
                    write!(f, r#"error:{v}"#)
                }
            },
            Value::Array(elts) => {
                write!(f, "[")?;
                for (i, v) in elts.iter().enumerate() {
                    if i < elts.len() - 1 {
                        v.fmt_ext(f, esc, types)?;
                        write!(f, ", ")?
                    } else {
                        v.fmt_ext(f, esc, types)?
                    }
                }
                write!(f, "]")
            }
            Value::Map(m) => {
                write!(f, "{{")?;
                for (i, (k, v)) in m.into_iter().enumerate() {
                    k.fmt_ext(f, esc, types)?;
                    write!(f, " => ")?;
                    v.fmt_ext(f, esc, types)?;
                    if i < m.len() - 1 {
                        write!(f, ", ")?
                    }
                }
                write!(f, "}}")
            }
            Value::Abstract(a) => {
                let bytes = pack(a).unwrap_or_else(|_| BytesMut::new());
                write!(f, "abstract:{}", BASE64.encode(&bytes))
            }
        }
    }
}