use std::collections::BTreeMap;
use std::fmt;
use crate::color::Color;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Number(f64),
String(String),
Color(Color),
Array(Vec<Value>),
Object(BTreeMap<String, Value>),
Image {
name: String,
available: bool,
},
Formatted(Vec<FormatSection>),
NumberArray(Vec<f64>),
ColorArray(Vec<Color>),
Padding([f64; 4]),
Projection(Projection),
Collator {
case_sensitive: bool,
diacritic_sensitive: bool,
locale: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum Projection {
Named(String),
Transition {
from: String,
to: String,
transition: f64,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct FormatSection {
pub text: String,
pub image: Option<(String, bool)>,
pub scale: Option<f64>,
pub font_stack: Option<String>,
pub text_color: Option<Color>,
pub vertical_align: Option<String>,
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Color(_) => "color",
Value::Array(_) => "array",
Value::Object(_) => "object",
Value::Image { .. } => "resolvedImage",
Value::Formatted(_) => "formatted",
Value::NumberArray(_) => "numberArray",
Value::ColorArray(_) => "colorArray",
Value::Padding(_) => "padding",
Value::Projection(_) => "projectionDefinition",
Value::Collator { .. } => "collator",
}
}
pub fn as_number(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => *n != 0.0 && !n.is_nan(),
Value::String(s) => !s.is_empty(),
_ => true,
}
}
pub fn from_json(json: &serde_json::Value) -> Value {
match json {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(b) => Value::Bool(*b),
serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(f64::NAN)),
serde_json::Value::String(s) => Value::String(s.clone()),
serde_json::Value::Array(a) => Value::Array(a.iter().map(Value::from_json).collect()),
serde_json::Value::Object(o) => Value::Object(
o.iter()
.map(|(k, v)| (k.clone(), Value::from_json(v)))
.collect(),
),
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Null => write!(f, ""),
Value::Bool(b) => write!(f, "{b}"),
Value::Number(n) => write!(f, "{}", format_number(*n)),
Value::String(s) => write!(f, "{s}"),
Value::Color(c) => write!(f, "{c}"),
Value::Array(a) => {
let parts: Vec<String> = a.iter().map(|v| v.to_string()).collect();
write!(f, "{}", parts.join(","))
}
Value::Object(_) => write!(f, "{self:?}"),
Value::Image { name, .. } => write!(f, "{name}"),
Value::Formatted(sections) => {
for s in sections {
write!(f, "{}", s.text)?;
}
Ok(())
}
Value::NumberArray(v) => {
let parts: Vec<String> = v.iter().map(|n| format_number(*n)).collect();
write!(f, "{}", parts.join(","))
}
Value::ColorArray(v) => {
let parts: Vec<String> = v.iter().map(|c| c.to_string()).collect();
write!(f, "{}", parts.join(","))
}
Value::Padding(v) => {
let parts: Vec<String> = v.iter().map(|n| format_number(*n)).collect();
write!(f, "{}", parts.join(","))
}
Value::Projection(Projection::Named(s)) => write!(f, "{s}"),
Value::Projection(_) => write!(f, "{self:?}"),
Value::Collator { .. } => write!(f, "collator"),
}
}
}
pub fn format_number(n: f64) -> String {
if n == n.trunc() && n.is_finite() && n.abs() < 1e21 {
format!("{}", n as i64)
} else {
format!("{n}")
}
}