use std::{
fmt::Display,
io::{stdout, Write},
path::PathBuf,
str::FromStr,
};
use clap::{Parser, Subcommand};
use serde_json::Value;
use crate::{dt::now2rfc3339, Errors, Jattr, Jattrs};
#[derive(Default, Clone, Copy)]
enum Format {
Json,
Ndjson,
#[default]
Normal,
}
impl Display for Format {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let strng = match self {
Format::Json => "json",
Format::Ndjson => "ndjson",
Format::Normal => "normal",
};
write!(f, "{}", strng)
}
}
impl FromStr for Format {
type Err = Errors;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"json" => Ok(Format::Json),
"ndjson" => Ok(Format::Ndjson),
"normal" => Ok(Format::Normal),
_ => Err(Errors::Generic("not format string".to_string())),
}
}
}
impl Format {
fn write_to<W: Write>(&self, w: &mut W, value: Value) -> Result<(), Errors> {
let strng = match self {
Format::Json => serde_json::to_string_pretty(&value)?,
Format::Ndjson => match value {
Value::Array(a) => {
let v: Vec<_> = a
.iter()
.map(|x| serde_json::to_string(x).expect("error in ndjson format"))
.collect();
v.join("\n")
}
_ => {
format!("{}\n", serde_json::to_string(&value)?)
}
},
Format::Normal => serde_json::to_string_pretty(&value)?,
};
writeln!(w, "{}", strng)?;
Ok(())
}
}
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(short, long, default_value_t = Format::Normal)]
output_format: Format,
#[command(subcommand)]
sub_cmd: SubCmd,
}
impl Cli {
pub fn run(&self) -> Result<(), Errors> {
let mut returns: Vec<Value> = vec![];
match &self.sub_cmd {
SubCmd::Get { key, path } => {
if let Some(j) = Jattr::load(path, key)? {
println!("{}: {}", path.display(), j);
} else {
println!("<{}> not on <{}> file", key, path.display());
return Err(Errors::KeyDoesntExist(key.clone(), path.to_path_buf()));
}
}
SubCmd::List { path } => {
if let Some(j) = Jattrs::load(path)? {
let cmd = Value::String("list".to_string());
let p = Value::String(path.display().to_string());
let v = j.to_value();
returns.push(Value::Array([cmd, p, v["xattrs"].clone()].to_vec()))
} else {
println!("None on <{}>", path.display());
};
}
SubCmd::Del { key, path } => {
Jattr::new_none(key).del(path, key)?;
}
SubCmd::Set { key, val, path } => {
let j = Jattr::new(key, val.clone());
j.set(path)?;
}
SubCmd::SetCreated { path } => {
let val = now2rfc3339();
let j = Jattr::new("created", val.clone());
j.set(path)?;
}
SubCmd::SetBulk { vv, path } => {
let kvs = vec_value2kvs(vv)?;
let cmd = Value::String("set".to_string());
let p = Value::String(path.display().to_string());
for kv in kvs {
let j = Jattr::new(&kv.0, kv.1.clone());
j.set(path)?;
returns.push(Value::Array(
[cmd.clone(), p.clone(), Value::String(kv.0), kv.1].to_vec(),
))
}
}
};
let mut stdout = stdout();
self.output_format
.write_to(&mut stdout, Value::Array(returns))
}
}
fn vec_value2kvs(vv: &Vec<String>) -> Result<Vec<(String, Value)>, Errors> {
let mut valid = vec![];
for s in vv {
let v = serde_json::from_str(s)?;
match v {
Value::Array(a) => {
if a.len() == 2 {
let key = a[0].as_str().unwrap();
let val = a[1].clone();
valid.push((key.to_string(), val));
}
}
Value::Object(o) => {
for kv in o.iter() {
valid.push((kv.0.clone(), kv.1.clone()));
}
}
_ => continue,
}
}
Ok(valid)
}
#[non_exhaustive]
#[derive(Subcommand, Debug)]
enum SubCmd {
List {
#[arg()]
path: PathBuf,
},
Get {
#[arg()]
path: PathBuf,
#[arg()]
key: String,
},
Set {
#[arg()]
path: PathBuf,
#[arg()]
key: String,
#[arg()]
val: Value,
},
Del {
#[arg()]
path: PathBuf,
#[arg()]
key: String,
},
SetCreated {
#[arg()]
path: PathBuf,
},
SetBulk {
#[arg()]
path: PathBuf,
#[arg()]
vv: Vec<String>,
},
}