xat 0.3.4

An ease of use library for xattrs
Documentation
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 {
    /// Lists all xattrs for the provided path
    List {
        #[arg()]
        path: PathBuf,
    },
    /// Returns the value of the key on the path
    Get {
        #[arg()]
        path: PathBuf,

        #[arg()]
        key: String,
    },
    /// Adds the key=val to the path
    Set {
        #[arg()]
        path: PathBuf,

        #[arg()]
        key: String,
        #[arg()]
        val: Value,
    },
    /// Deletes the key on the path
    Del {
        #[arg()]
        path: PathBuf,

        #[arg()]
        key: String,
    },
    /// Sets Created kv as now on path
    SetCreated {
        #[arg()]
        path: PathBuf,
    },
    /// Bulk set
    /// ["key", 20]
    /// {"key": 41}
    SetBulk {
        #[arg()]
        path: PathBuf,

        #[arg()]
        vv: Vec<String>,
    },
}