yazi/yazi-shared/src/event/cmd.rs
2025-02-07 02:01:23 +08:00

187 lines
4.4 KiB
Rust

use std::{any::Any, borrow::Cow, collections::HashMap, fmt::{self, Display}, mem, str::FromStr};
use anyhow::{Result, bail};
use serde::{Deserialize, de};
use super::{Data, DataKey};
use crate::url::Url;
#[derive(Debug, Default)]
pub struct Cmd {
pub name: String,
pub args: HashMap<DataKey, Data>,
}
impl Cmd {
#[inline]
pub fn new(name: &str) -> Self { Self { name: name.to_owned(), ..Default::default() } }
#[inline]
pub fn args(name: &str, args: &[impl ToString]) -> Self {
Self {
name: name.to_owned(),
args: args
.iter()
.enumerate()
.map(|(i, s)| (DataKey::Integer(i as i64), Data::String(s.to_string())))
.collect(),
}
}
// --- With
#[inline]
pub fn with(mut self, name: impl Into<DataKey>, value: impl ToString) -> Self {
self.args.insert(name.into(), Data::String(value.to_string()));
self
}
#[inline]
pub fn with_opt(mut self, name: impl Into<DataKey>, value: Option<impl ToString>) -> Self {
if let Some(v) = value {
self.args.insert(name.into(), Data::String(v.to_string()));
}
self
}
#[inline]
pub fn with_bool(mut self, name: impl Into<DataKey>, state: bool) -> Self {
self.args.insert(name.into(), Data::Boolean(state));
self
}
#[inline]
pub fn with_any(mut self, name: impl Into<DataKey>, data: impl Any + Send + Sync) -> Self {
self.args.insert(name.into(), Data::Any(Box::new(data)));
self
}
// --- Get
#[inline]
pub fn get(&self, name: impl Into<DataKey>) -> Option<&Data> { self.args.get(&name.into()) }
#[inline]
pub fn str(&self, name: impl Into<DataKey>) -> Option<&str> {
self.get(name).and_then(Data::as_str)
}
#[inline]
pub fn bool(&self, name: impl Into<DataKey>) -> bool { self.maybe_bool(name).unwrap_or(false) }
#[inline]
pub fn maybe_bool(&self, name: impl Into<DataKey>) -> Option<bool> {
self.get(name).and_then(Data::as_bool)
}
#[inline]
pub fn first(&self) -> Option<&Data> { self.get(0) }
#[inline]
pub fn first_str(&self) -> Option<&str> { self.str(0) }
// --- Take
#[inline]
pub fn take(&mut self, name: impl Into<DataKey>) -> Option<Data> {
self.args.remove(&name.into())
}
#[inline]
pub fn take_str(&mut self, name: impl Into<DataKey>) -> Option<String> {
if let Some(Data::String(s)) = self.take(name) { Some(s) } else { None }
}
#[inline]
pub fn take_first(&mut self) -> Option<Data> { self.take(0) }
#[inline]
pub fn take_first_str(&mut self) -> Option<String> {
if let Some(Data::String(s)) = self.take_first() { Some(s) } else { None }
}
#[inline]
pub fn take_first_url(&mut self) -> Option<Url> { self.take_first()?.into_url() }
#[inline]
pub fn take_any<T: 'static>(&mut self, name: impl Into<DataKey>) -> Option<T> {
self.args.remove(&name.into()).and_then(|d| d.into_any())
}
// Parse
pub fn parse_args(
words: impl Iterator<Item = String>,
obase: bool,
) -> Result<HashMap<DataKey, Data>> {
let mut i = 0i64;
words
.into_iter()
.map(|word| {
let Some(arg) = word.strip_prefix("--") else {
i += 1;
return Ok((DataKey::Integer(i - obase as i64), Data::String(word)));
};
let mut parts = arg.splitn(2, '=');
let Some(key) = parts.next().map(|s| s.to_owned()) else {
bail!("invalid argument: {arg}");
};
let val = if let Some(val) = parts.next() {
Data::String(val.to_owned())
} else {
Data::Boolean(true)
};
Ok((DataKey::String(Cow::Owned(key)), val))
})
.collect()
}
}
impl Display for Cmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
for (k, v) in &self.args {
match k {
DataKey::Integer(_) => {
if let Some(s) = v.as_str() {
write!(f, " {s}")?;
}
}
DataKey::String(k) => {
if v.as_bool().is_some_and(|b| b) {
write!(f, " --{k}")?;
} else if let Some(s) = v.as_str() {
write!(f, " --{k}={s}")?;
}
}
_ => {}
}
}
Ok(())
}
}
impl FromStr for Cmd {
type Err = anyhow::Error;
#[allow(clippy::explicit_counter_loop)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut args = crate::shell::split_unix(s)?;
if args.is_empty() || args[0].is_empty() {
bail!("command name cannot be empty");
}
Ok(Cmd {
name: mem::take(&mut args[0]),
args: Cmd::parse_args(args.into_iter().skip(1), true)?,
})
}
}
impl<'de> Deserialize<'de> for Cmd {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<_>::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom)
}
}