#![doc = include_str!("../docs.md")]
mod metar;
pub use metar::{Rule, SiftParser};
use csv::WriterBuilder;
use serde_json::Value;
use std::collections::{BTreeSet, HashMap};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ParseError {
#[error("JSON: {0}")]
Json(String),
#[error("Structure: {0}")]
Structure(String),
}
pub fn parse_json(s: &str) -> Result<Value, ParseError> {
serde_json::from_str(s).map_err(|e| ParseError::Json(e.to_string()))
}
pub fn convert_to_csv(v: &Value) -> Result<String, ParseError> {
let mut rows = Vec::<HashMap<String, String>>::new();
let mut keys = BTreeSet::new();
match v {
Value::Array(a) => {
for it in a {
let mut m = HashMap::new();
flatten(it, "".into(), &mut m)?;
for k in m.keys() {
keys.insert(k.clone()); }
rows.push(m);
}
}
Value::Object(_) => {
let mut m = HashMap::new();
flatten(v, "".into(), &mut m)?;
for k in m.keys() {
keys.insert(k.clone());
}
rows.push(m);
}
_ => return Err(ParseError::Structure("expect object or array".into())),
}
let hdr: Vec<String> = keys.into_iter().collect();
let mut wtr = WriterBuilder::new()
.has_headers(true)
.from_writer(Vec::new());
wtr.write_record(&hdr)
.map_err(|e| ParseError::Structure(e.to_string()))?;
for row in rows {
let record = hdr.iter().map(|col| {
row.get(col).map_or("", |v| v.as_str())
});
wtr.write_record(record)
.map_err(|e| ParseError::Structure(e.to_string()))?;
}
let buf = wtr
.into_inner()
.map_err(|e| ParseError::Structure(e.to_string()))?;
let out = String::from_utf8(buf).map_err(|e| ParseError::Structure(e.to_string()))?;
Ok(out)
}
const PREFIX_WITH_DETECTOR_NAME: bool = true;
fn flatten(v: &Value, prefix: String, out: &mut HashMap<String, String>) -> Result<(), ParseError> {
match v {
Value::Object(m) => {
for (k, vv) in m {
let key = if prefix.is_empty() {
k.clone()
} else {
format!("{prefix}.{k}")
};
flatten(vv, key, out)?;
}
}
Value::Array(a) => {
for (i, vv) in a.iter().enumerate() {
flatten(vv, format!("{}[{}]", prefix, i), out)?;
}
}
Value::String(s) => parse_scalar(prefix, s, out)?,
Value::Number(n) => {
out.insert(prefix, n.to_string());
}
Value::Bool(b) => {
out.insert(prefix, b.to_string());
}
Value::Null => {
out.insert(prefix, "".into());
}
}
Ok(())
}
fn parse_scalar(
prefix: String,
s: &str,
out: &mut HashMap<String, String>,
) -> Result<(), ParseError> {
let text = s.trim();
if text.is_empty() {
out.insert(prefix, String::new());
return Ok(());
}
if let Some(mut decoded) = metar::decode_metar(text) {
let det_name = "metar";
for (dk, dv) in decoded.drain() {
let col = if prefix.is_empty() {
if PREFIX_WITH_DETECTOR_NAME {
format!("{det_name}.{dk}")
} else {
dk
}
} else if PREFIX_WITH_DETECTOR_NAME {
format!("{prefix}.{det_name}.{dk}")
} else {
format!("{prefix}.{dk}")
};
out.insert(col, dv); }
return Ok(());
}
let tokens = metar::complex_key_value(text);
if tokens.is_empty() {
out.insert(prefix, text.to_string());
return Ok(());
}
if tokens.len() == 1 {
let t = tokens[0].trim();
if let Some(pat) = metar::holds_pattern_value(t) {
metar::apply_pattern(&prefix, t, pat, out);
} else {
out.insert(prefix, text.to_string());
}
return Ok(());
}
if !metar::all_tokens_code_like(&tokens) {
out.insert(prefix, text.to_string());
return Ok(());
}
let mut i = 0; for t in tokens {
let t = t.trim();
if t.is_empty() {
continue;
}
if let Some(pat) = metar::holds_pattern_value(t) {
metar::apply_pattern(&prefix, t, pat, out);
continue;
}
let col = if prefix.is_empty() {
format!("token_{i}")
} else {
format!("{prefix}.token_{i}")
};
out.insert(col, t.to_string());
i += 1;
}
Ok(())
}