use crate::{
assets::get_l10n_text,
conversion::{
convert::convert_data_format,
fmt::{auto_detect_unknown_fmt, get_src_format},
get_static_stdin_data, is_src_stdin,
serialisation::deser_yaml,
ConvFmt,
},
highlight::HighLightRes,
set::get_set_text,
table::set_header,
};
use anyhow::Result;
use comfy_table::Table;
use getset::Getters;
use glossa::GetText;
use log::info;
use owo_colors::OwoColorize;
use std::{fs, path::Path};
use serde_yaml::Value;
#[derive(Getters, Debug)]
#[getset(get = "pub with_prefix")]
pub struct ConvertedContents<'f> {
pub(crate) contents: String,
pub(crate) fmt: &'f str,
}
impl<'f> ConvertedContents<'f> {
fn new(contents: String, fmt: &'f str) -> Self {
Self { contents, fmt }
}
}
pub fn get_config_file<'a>(
key: &str,
src: &'a Path,
src_format: Option<&'a String>,
high_light_resource: Option<&HighLightRes>,
table_style: &str,
) -> Result<ConvertedContents<'a>> {
let mut cfg_str = String::with_capacity(256);
let is_stdin = is_src_stdin(src);
let src_fmt = match src_format {
Some(s) if is_stdin && s != "bson" => {
cfg_str = get_static_stdin_data().to_owned();
s
}
Some(s) => {
info!("src-fmt: {}", s);
s
}
_ if is_stdin => {
let data = get_static_stdin_data();
cfg_str = data.to_owned();
auto_detect_unknown_fmt(data)
}
_ => match get_src_format(src) {
x if x.is_empty() => {
cfg_str = fs::read_to_string(src)?;
auto_detect_unknown_fmt(&cfg_str)
}
x => x,
},
};
#[cfg(feature = "bson")]
if src_fmt == "bson" {
cfg_str = "bson".to_owned();
};
if cfg_str.is_empty() {
cfg_str = fs::read_to_string(src)?;
}
let mut table = Table::new();
set_header(
&mut table,
&[
key,
get_set_text("type").unwrap_or("Type"),
"Value",
],
table_style,
);
log::info!("{}: {}", get_l10n_text().get("get", "src-fmt")?, src_fmt);
let mut walk = |s| -> Result<()> {
if walk_cfg(&deser_yaml(s)?, &mut table, key) {
println!("{table}")
}
Ok(())
};
let conv = || ConvFmt::new(src, src_fmt, "yaml", Path::new(""), false);
let show_highlight_and_save =
|b| convert_data_format(conv(), b, high_light_resource);
let just_show_high_light = || show_highlight_and_save(false).map(|_| ());
let conv_contents = || ConvertedContents::new(cfg_str, src_fmt);
match src_fmt {
"yaml" => {
if key.trim().is_empty() {
just_show_high_light()?;
return Ok(conv_contents());
}
let res = conv_contents();
walk(res.get_contents())?;
Ok(res)
}
_ => {
if key.trim().is_empty() {
log::info!("{}: yaml", get_l10n_text().get("get", "dst-fmt")?);
just_show_high_light()?;
return Ok(conv_contents());
}
cfg_str = show_highlight_and_save(true)?.unwrap_or_else(|| {
panic!("Text content inside {} not obtained", src.display())
});
walk(&cfg_str)?;
Ok(ConvertedContents::new(cfg_str, src_fmt))
}
}
}
fn ron_value_to_string(v: &Value) -> String {
match v {
Value::String(s) => s.into(),
Value::Bool(s) => s.to_string(),
Value::Mapping(s) => format!("{:?}", s),
Value::Number(s) => s.to_string(),
Value::Null => "null".to_owned(),
Value::Sequence(s) => format!("{:?}", s),
Value::Tagged(s) => format!("{:?}", s),
}
}
fn get_ron_value_type_name(v: &Value) -> &str {
match v {
Value::String(_) => "str",
Value::Bool(_) => "bool",
Value::Number(s) => match s {
n if n.is_u64() => "u64",
n if n.is_i64() => "i64",
n if n.is_nan() => "NaN",
n if n.is_infinite() => "Inf",
_ => "f64",
},
Value::Sequence(_) => "array",
Value::Mapping(_) => "map",
Value::Tagged(_) => "enum",
_ => "Null",
}
}
pub fn walk_cfg(value: &Value, table: &mut Table, query: &str) -> bool {
use std::collections::VecDeque;
let mut deque: VecDeque<(String, &Value)> = VecDeque::with_capacity(128);
deque.push_back((String::new(), value));
let query = query.trim_start_matches(['.', ' ']);
let prefix = Some(query);
let mut key_dot = String::with_capacity(16);
let empty = |s: &str| s.is_empty();
while let Some((key, v)) = deque.pop_front() {
match v {
Value::Mapping(table) => {
for (k, v) in table.iter() {
key_dot = ron_value_to_string(k);
let new_key = match key.trim() {
t if empty(t) && key_dot.contains('.') => {
format!(r#"'{}'"#, key_dot)
}
t if empty(t) => key_dot.to_owned(),
_ if key_dot.contains('.') => {
format!(r#"{}.'{}'"#, key, key_dot)
}
_ => format!(r#"{}.{}"#, key, key_dot),
};
deque.push_back((new_key, v));
}
}
Value::Sequence(array) => {
for (i, v) in array.iter().enumerate() {
let new_key = format!("{}.{}", key, i);
deque.push_back((new_key, v));
}
}
_ => {
if !query_key_and_push_table(
&key,
query,
&mut key_dot,
prefix,
v,
table,
) {
return false;
}
}
}
}
matches!(table.row(0), Some(_))
}
fn query_key_and_push_table(
key: &str,
query: &str,
key_dot: &mut String,
prefix: Option<&str>,
v: &Value,
table: &mut Table,
) -> bool {
if !key.starts_with(query) {
return true;
}
*key_dot = format!("{query}.");
let new_prefix = match key {
k if k.starts_with(&*key_dot) => Some(key_dot.as_str()),
_ => prefix,
};
let v_str = ron_value_to_string(v);
let v_type = get_ron_value_type_name(v);
match &new_prefix {
Some(s) if s == &key => {
info!(
"{}: {:?}",
get_set_text("type")
.unwrap_or("Type")
.cyan(),
v_type.purple()
);
info!("{}: {:?}", "value".cyan(), v_str.blue());
println!("{}", v_str);
return false;
}
Some(s) => {
log::trace!("key: {}, s: {}", key, s);
let k = if s.contains('.') {
key.trim_start_matches(s)
} else {
table.set_header([
".",
get_set_text("type").unwrap_or("Type"),
"Value",
]);
key
};
table.add_row([k.trim_start_matches('.'), v_type, &v_str]);
}
_ => {
table.add_row([key, v_type, &v_str]);
}
}
true
}