use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Library {
pub version: u32,
#[serde(default)]
pub commands: Vec<Entry>,
#[serde(default)]
pub disabled: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Entry {
pub id: String,
pub cmd: CommandBody,
pub desc: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub params: BTreeMap<String, ParamSpec>,
#[serde(default)]
pub danger: bool,
#[serde(skip)]
pub layer: Layer,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum CommandBody {
Shared(String),
PerShell(BTreeMap<ShellFamily, String>),
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ParamSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub desc: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Layer {
#[default]
Builtin,
Project,
User,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ShellFamily {
Posix,
PowerShell,
}
impl Entry {
pub fn cmd_for(&self, family: ShellFamily) -> Option<&str> {
match &self.cmd {
CommandBody::Shared(cmd) => Some(cmd),
CommandBody::PerShell(variants) => variants.get(&family).map(String::as_str),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(cmd: CommandBody) -> Entry {
Entry {
id: "test".into(),
cmd,
desc: "test".into(),
tags: Vec::new(),
params: BTreeMap::new(),
danger: false,
layer: Layer::Builtin,
}
}
#[test]
fn shared_command_serves_every_family() {
let e = entry(CommandBody::Shared("git status".into()));
assert_eq!(e.cmd_for(ShellFamily::Posix), Some("git status"));
assert_eq!(e.cmd_for(ShellFamily::PowerShell), Some("git status"));
}
#[test]
fn missing_variant_hides_entry_from_that_shell() {
let e = entry(CommandBody::PerShell(BTreeMap::from([(
ShellFamily::Posix,
"ss -tulpn".into(),
)])));
assert_eq!(e.cmd_for(ShellFamily::Posix), Some("ss -tulpn"));
assert_eq!(e.cmd_for(ShellFamily::PowerShell), None);
}
#[test]
fn layers_order_from_weakest_to_strongest() {
assert!(Layer::Builtin < Layer::Project);
assert!(Layer::Project < Layer::User);
}
}