use crate::{
assets::get_l10n_text,
conversion::{
convert::convert_data_format,
get_conv_dft, get_dst, get_dst_fmt, get_static_stdin_data, is_src_stdin,
serialisation::{deser_toml_to_yaml, deser_yaml, ser_toml},
ConvFmt,
},
get::{get_config_file, walk_cfg, ConvertedContents},
highlight::{output::get_syntax_highlight, HighLightRes},
table::set_header,
};
use anyhow::Result;
use getset::{Getters, MutGetters};
use glossa::GetText;
use log::{debug, info, warn};
use owo_colors::OwoColorize;
use std::{
borrow::Cow,
fs, io,
path::{Path, PathBuf},
};
pub use toml_edit::{
Array, ArrayOfTables, Datetime, InlineTable, Table as TomlTable, Value,
};
use toml_edit::{Document, Item};
fn get_item_mut<'doc, 's, I: IntoIterator<Item = &'s str>>(
doc: &'doc mut Document,
keys: I,
) -> &'doc mut Item {
let mut tmp = doc.as_item_mut(); let mut is_arr = false;
for key in keys.into_iter() {
if key.is_empty() {
return tmp;
}
tmp = match is_arr {
true => tmp.get_mut(
key.parse::<usize>() .expect("Not a Arr"),
),
_ => tmp.get_mut(key),
}
.unwrap_or_else(|| panic!("Failed to get: {}", key));
is_arr = matches!(tmp.type_name(), "array" | "array of tables"); }
tmp }
#[derive(Getters, MutGetters, Debug)]
#[getset(get = "pub with_prefix", get_mut = "pub with_prefix")]
pub struct CfgOpts<'s, 'd> {
src: &'s Path,
src_format: Option<&'s String>,
save: bool,
dst: Option<&'d PathBuf>,
key: TomlKey<'s>,
value: TomlValue,
high_light: Option<&'d HighLightRes<'d>>,
table_style: &'d str,
set_value: bool,
preview: bool,
}
#[derive(Debug, Clone)]
pub enum TomlKey<'s> {
Str(Cow<'s, str>),
Vec(&'s Vec<String>),
}
impl<'s> TomlKey<'s> {
pub fn concat_key(&self) -> Cow<str> {
match self {
TomlKey::Str(k) => k.clone(),
TomlKey::Vec(v) => {
let s = v
.iter()
.filter(|x| !x.is_empty())
.map(|x| quote_dot(x))
.collect::<Vec<_>>()
.join(".");
Cow::from(s)
}
}
}
}
#[derive(Debug, Clone)]
pub enum TomlValue {
Normal,
Table(toml_edit::Table),
AOT(toml_edit::ArrayOfTables),
NONE,
}
impl<'s, 'd> CfgOpts<'s, 'd> {
#[allow(clippy::too_many_arguments)]
pub fn new(
src: &'s Path,
src_format: Option<&'s String>,
save: bool,
dst: Option<&'d PathBuf>,
key: TomlKey<'s>,
high_light: Option<&'d HighLightRes<'d>>,
table_style: &'d str,
) -> Self {
Self {
src,
src_format,
save,
dst,
key,
value: TomlValue::Normal,
high_light,
table_style,
set_value: true,
preview: false,
}
}
}
fn quote_dot(x: &str) -> String {
if x.contains('.') {
format!("'{}'", x)
} else {
x.to_owned()
}
}
impl<'s, 'd> CfgOpts<'s, 'd> {
fn get_current_cfg_value(&self, key: &str) -> Result<(String, &str)> {
log::info!("Current:");
let ConvertedContents { contents, fmt } = get_config_file(
key,
self.src,
self.src_format,
self.high_light,
self.table_style,
)?;
Ok((contents, fmt))
}
pub fn set_value<V: Into<Value>>(&self, s: Option<V>) -> Result<()> {
let key = self.get_concatenated_key_str();
debug!("key: {key}");
let (mut cfg_str, src_fmt) = self.get_current_cfg_value(&key)?;
match self.get_key() {
TomlKey::Str(k) if k == "." || k.trim().is_empty() => return Ok(()),
TomlKey::Vec(k)
if k.is_empty() || k[0] == "." || k[0].trim().is_empty() =>
{
return Ok(())
}
_ => {}
}
self.reread_toml_str(src_fmt, &mut cfg_str)?;
let mut doc = cfg_str.parse::<Document>()?;
let key_chunks = self.get_key_chunks();
let v = get_item_mut(&mut doc, key_chunks.iter().copied());
if !self.set_value {
self.print_current_kv(v, &key_chunks)?;
return Ok(());
}
self.set_toml_value(s, v);
println!(
"\nkey:\t{:?}\ntype:\t{}\nnew value: {}\n",
key_chunks.cyan(),
v.type_name().blue(),
v.yellow().bold(),
);
let modified_str = doc.to_string();
if key_chunks.len() >= 2 {
self.print_new_value_table(key_chunks, &modified_str)?;
}
let mut converted = None;
#[allow(unused_assignments)]
let mut dst_fmt_lcase = String::with_capacity(src_fmt.len() + 5);
let (dst_fmt, dst) = match (self.get_dst_path(), self.get_src()) {
(d, s) if &d == s => (src_fmt, d),
(d, ..) => {
dst_fmt_lcase = d
.file_name()
.unwrap_or_else(|| {
panic!("{} {}", get_set_dft("invalid-path"), d.display())
})
.to_string_lossy()
.to_ascii_lowercase();
let fmt =
get_dst_fmt(&dst_fmt_lcase, false).unwrap_or(src_fmt)
;
let dst = get_dst(&d, fmt, self.get_src());
(fmt, dst)
}
};
if *self.get_preview() {
match dst_fmt {
"toml" => converted = Some(modified_str.to_string()),
"bson" => {
get_syntax_highlight(
"toml",
&modified_str,
self.high_light,
None,
)?;
log::info!("Bson: Binary data");
}
_ => {
converted = self.convert_or_get_str(
dst_fmt,
Path::new(""),
&modified_str,
true,
)?
}
}
if let Some(ref s) = converted {
get_syntax_highlight(dst_fmt, s, self.high_light, None)?;
}
println!();
}
self.show_unsave_warning(&dst);
if *self.get_save() {
match converted {
Some(ref s) => {
info!("Saved at: {}", dst.display());
fs::write(dst, s)?;
}
_ => {
self.convert_or_get_str(dst_fmt, &dst, &modified_str, false)?;
}
}
}
Ok(())
}
fn set_toml_value<V: Into<Value>>(&self, s: Option<V>, v: &mut Item) {
use TomlValue::*;
match s {
Some(s) => *v = toml_edit::value::<V>(s),
_ => match self.get_value() {
Normal => {}
Table(t) => *v = Item::Table(t.to_owned()),
AOT(t) => *v = Item::ArrayOfTables(t.to_owned()),
NONE => *v = Item::None,
},
}
}
fn reread_toml_str(&self, src_fmt: &str, cfg_str: &mut String) -> Result<()> {
match src_fmt {
"toml" if is_src_stdin(self.get_src()) => {
*cfg_str = get_static_stdin_data().to_owned()
}
"toml" => *cfg_str = fs::read_to_string(self.get_src())?,
_ => *cfg_str = ser_toml(&deser_yaml(cfg_str)?)?,
};
Ok(())
}
fn get_concatenated_key_str(&self) -> Cow<str> {
self.get_key().concat_key()
}
fn get_key_chunks(&self) -> Vec<&str> {
match self.get_key() {
TomlKey::Str(s) => s
.split('.')
.filter(|x| !x.is_empty())
.collect::<Vec<_>>(),
TomlKey::Vec(v) => v
.iter()
.map(|x| x.as_str())
.filter(|x| !x.is_empty())
.collect(),
}
}
fn print_current_kv(
&self,
v: &mut Item,
key_chunks: &Vec<&str>,
) -> Result<(), anyhow::Error> {
print!(
"\nkey:\t{:?}\ntype:\t{}",
key_chunks.cyan(),
v.type_name().blue().bold(),
);
println!("\nvalue: ");
get_syntax_highlight(
"toml",
&v.to_string(),
self.get_high_light().as_deref(),
None,
)?;
println!();
Ok(())
}
fn print_new_value_table(
&self,
mut key_chunks: Vec<&str>,
modified_str: &str,
) -> Result<(), anyhow::Error> {
let mut table = comfy_table::Table::new();
let mut set_table = |k| {
set_header(
&mut table,
&[
k,
get_set_text("type").unwrap_or("Type"),
get_set_dft("new-value").as_ref(),
],
self.table_style,
)
};
key_chunks.pop();
let chunks_no_last = key_chunks
.iter()
.filter(|x| !x.is_empty())
.map(|x| quote_dot(x))
.collect::<Vec<_>>()
.join(".");
set_table(&chunks_no_last);
if walk_cfg(
&deser_toml_to_yaml(modified_str)?,
&mut table,
&chunks_no_last,
) {
println!("{table}")
};
Ok(())
}
fn get_dst_path(&self) -> PathBuf {
match self.dst {
Some(p) if p.is_dir() => p.join(
self.get_src()
.file_name()
.expect("Failed to get src fname"),
),
Some(p) => p.into(),
_ => self.get_src().into(),
}
}
fn convert_or_get_str(
&self,
dst_fmt: &str,
dst: &Path,
modified_str: &str,
get_str: bool,
) -> Result<Option<String>> {
match dst_fmt {
"toml" => fs::write(dst, modified_str)?,
_ => {
let tmp = dst.parent().unwrap_or_else(|| Path::new(".")).join(format!(
"._{}.tomlyre.QeAlU63e0qGhBdCpYhNm9ZNGNq0RVaxZ5ytDBE0eGiLVFK97UPDXkyO5RCNaVFdk.tmp",
self.get_src().file_name().unwrap_or_default().to_string_lossy()
));
fs::write(&tmp, modified_str)?;
let conv =
ConvFmt::new(&tmp, "toml", dst_fmt, dst, *self.get_save());
let high_light = self.get_high_light();
let rm_tmp = || -> io::Result<()> {
fs::remove_file(&tmp)?;
Ok(())
};
if get_str {
let str = convert_data_format(conv, true, *high_light)?;
rm_tmp()?;
return Ok(str);
}
convert_data_format(conv, false, *high_light)?;
rm_tmp()?;
}
};
Ok(None)
}
fn show_unsave_warning(&self, dst: &Path) {
if *self.get_save() {
return;
}
warn!("{}", get_set_md("unsave-warn"));
info!("{}: {:?}", get_conv_dft("dst"), dst.as_os_str().blue());
}
}
fn get_set_dft(k: &str) -> Cow<str> {
get_l10n_text().get_or_default("set", k)
}
fn get_set_md(k: &str) -> Cow<str> {
get_l10n_text().get_or_default("set_md", k)
}
pub(crate) fn get_set_text(k: &str) -> glossa::Result<&str> {
get_l10n_text().get("set", k)
}