use std::io::{Read, stdin};
use std::path::{Path};
use std::fs::{read_to_string};
use std::process::exit;
use yaml_rust::{YamlLoader, Yaml, ScanError};
use regex::{Regex};
use boomack::client::json::*;
const KEY_VALUE_PAIR_PATTERN: &str = r"^([a-zA-Z0-9_-]+)\s*=\s*(.+)$";
const STDIN_INDICATOR: &str = "STDIN";
const MAX_FILEPATH_LENGTH: usize = 1023;
fn value_to_json_value(v: &str) -> JVal {
let vlo: Option<String> = if v.len() <= 5 { Some(v.to_lowercase()) } else { None };
let vc = vlo.as_deref().unwrap_or(v);
match vc {
"null" => JVal::Null,
"true" | "on" | "yes" => JVal::Bool(true),
"false" | "off" | "no" => JVal::Bool(false),
_ => f64_str_to_json(v).unwrap_or(JVal::String(String::from(v)))
}
}
fn key_value_pair(s: &str) -> Option<(String, JVal)>
{
let p = Regex::new(KEY_VALUE_PAIR_PATTERN).unwrap();
match p.captures(s) {
Some(caps) => Some((
String::from(&caps[1]),
value_to_json_value(&caps[2])
)),
None => None
}
}
pub fn parse_kvp_lines(s: &str) -> Option<JsonMap> {
let mut result = JsonMap::new();
for line in s.lines() {
if line.len() == 0 || line.starts_with("#") { continue; }
match key_value_pair(line) {
Some((k, v)) => { result.insert(k, v); },
None => {
eprintln!("Failed to parse key-value-pair: {}", line);
exit(1)
},
}
}
Some(result)
}
fn is_stdin_indicator(s: &str) -> bool {
s.len() == STDIN_INDICATOR.len() && s.to_ascii_uppercase() == STDIN_INDICATOR
}
fn yaml_to_json(yaml: Yaml) -> Option<JsonMap> {
fn y2j(y: Yaml) -> JVal {
match y {
Yaml::Null | Yaml::BadValue | Yaml::Alias(_) => JVal::Null,
Yaml::Boolean(v) => JVal::Bool(v),
Yaml::Real(v) => f64_str_to_json(&v).unwrap_or(JVal::Null),
Yaml::Integer(v) => i64_to_json(v).unwrap_or(JVal::Null),
Yaml::String(s) => JVal::String(s),
Yaml::Array(xs) => JVal::Array(xs.into_iter().map(y2j).collect()),
Yaml::Hash(h) => {
let mut map = JsonMap::new();
for (k, v) in h {
let ko = match k {
Yaml::String(s) => Some(s),
Yaml::Real(v) => Some(v),
Yaml::Integer(v) => Some(v.to_string()),
Yaml::Boolean(v) => Some(v.to_string()),
_ => None
};
if let Some(k_str) = ko {
map.insert(k_str, y2j(v));
}
}
JVal::Object(map)
}
}
}
let jv = y2j(yaml);
if let JVal::Object(map) = jv { Some(map) } else { None }
}
fn yaml_docs_to_json(yaml_docs: Vec<Yaml>) -> impl Iterator<Item=JsonMap>
{
yaml_docs.into_iter().filter_map(yaml_to_json)
}
pub fn parse_structure<F>(s: &str,
allow_stdin: bool, stdin_consumed: &mut bool,
parser: F
) -> JsonMap
where F: Fn(&str) -> Option<JsonMap>
{
if s.len() == 0 { return JsonMap::new(); }
let mut ext_input: Option<String> = None;
if allow_stdin && is_stdin_indicator(s) {
if !*stdin_consumed {
let mut stdin_str = String::new();
match stdin().read_to_string(&mut stdin_str) {
Err(err) => {
eprintln!("{:?}", err);
eprintln!("Failed to read from STDIN");
exit(1)
},
_ => {
ext_input = Some(stdin_str);
*stdin_consumed = true;
}
}
} else {
eprintln!("Can not read from STDIN, because STDIN was already consumed!");
exit(1)
}
} else if s.len() <= MAX_FILEPATH_LENGTH {
let path = Path::new(s);
if path.exists() {
match read_to_string(path) {
Ok(file_content) => {
ext_input = Some(file_content);
},
Err(err) => {
eprintln!("{:?}", err);
eprintln!("Failed to read file: {:?}", path);
exit(1)
}
};
}
}
fn is_just_a_string(yaml_docs: &Vec<Yaml>) -> bool {
yaml_docs.len() == 1 && matches!(yaml_docs[0], Yaml::String(_))
}
fn contains_bad_result(yaml_docs: &Vec<Yaml>) -> bool {
yaml_docs.iter().any(|doc| matches!(doc, Yaml::BadValue))
}
let input = ext_input.as_deref().unwrap_or(s);
let fallback_from_yaml = |err: Option<ScanError>| {
if let Some(parsed) = parser(input) {
return parsed
} else if let Some(err) = err {
eprintln!("{:?}", err);
}
eprintln!("Failed to parse YAML/JSON");
exit(1)
};
match YamlLoader::load_from_str(input) {
Ok(yaml_data) if contains_bad_result(&yaml_data) => {
eprintln!("Invalid YAML/JSON");
exit(1)
},
Ok(yaml_data) if is_just_a_string(&yaml_data) => fallback_from_yaml(None),
Ok(yaml_data) => merge_layers(yaml_docs_to_json(yaml_data)),
Err(err) => fallback_from_yaml(Some(err)),
}
}
pub fn load_structure(s: &str,
allow_stdin: bool, stdin_consumed: &mut bool
) -> JsonMap {
parse_structure(s, allow_stdin, stdin_consumed, |_| None)
}
pub fn merge_layers<T>(layers: T) -> JsonMap
where T : IntoIterator<Item=JsonMap>
{
let mut result = JsonMap::new();
for layer in layers {
for (k, v) in layer {
result.insert(k, v);
}
}
result
}