perf: copy-on-write on command data & avoid converting primitive types to strings thereby allocating memory (#2862)

This commit is contained in:
三咲雅 misaki masa 2025-06-11 10:13:51 +08:00 committed by GitHub
parent 0752b53298
commit 414d46fd16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 131 additions and 104 deletions

12
Cargo.lock generated
View file

@ -1025,7 +1025,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -1470,7 +1470,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -2992,9 +2992,9 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
@ -3318,9 +3318,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.10" version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -7,7 +7,7 @@ use yazi_shared::{event::CmdCow, url::Url};
use crate::{mgr::{LINKED, Mgr}, tasks::Tasks}; use crate::{mgr::{LINKED, Mgr}, tasks::Tasks};
pub struct Opt { pub struct Opt {
updates: HashMap<Cow<'static, str>, String>, updates: HashMap<Cow<'static, str>, Cow<'static, str>>,
} }
impl TryFrom<CmdCow> for Opt { impl TryFrom<CmdCow> for Opt {
@ -28,13 +28,13 @@ impl Mgr {
let updates = opt let updates = opt
.updates .updates
.into_iter() .into_iter()
.map(|(url, mime)| (Url::from(url.into_owned()), mime)) .map(|(url, mime)| (Url::from(url.as_ref()), mime))
.filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime)) .filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime))
.fold(HashMap::new(), |mut map, (u, m)| { .fold(HashMap::new(), |mut map, (u, m)| {
for u in linked.from_file(&u) { for u in linked.from_file(&u) {
map.insert(u, m.clone()); map.insert(u, m.to_string());
} }
map.insert(u, m); map.insert(u, m.into_owned());
map map
}); });

View file

@ -14,7 +14,7 @@ impl Notify {
if self.messages.iter().all(|m| m != &msg) { if self.messages.iter().all(|m| m != &msg) {
self.messages.push(msg); self.messages.push(msg);
emit!(Call(Cmd::args("app:update_notify", &[0]))); emit!(Call(Cmd::args("app:update_notify", [0])));
} }
} }
} }

View file

@ -69,7 +69,7 @@ impl Notify {
self.tick_handle = Some(tokio::spawn(async move { self.tick_handle = Some(tokio::spawn(async move {
tokio::time::sleep(interval).await; tokio::time::sleep(interval).await;
emit!(Call(Cmd::args("app:update_notify", &[interval.as_secs_f64()]))); emit!(Call(Cmd::args("app:update_notify", [interval.as_secs_f64()])));
})); }));
} }
} }

View file

@ -41,10 +41,10 @@ impl Tab {
let (Ok(s) | Err(InputError::Typed(s))) = result else { continue }; let (Ok(s) | Err(InputError::Typed(s))) = result else { continue };
emit!(Call( emit!(Call(
Cmd::args("mgr:filter_do", &[s]) Cmd::args("mgr:filter_do", [s])
.with_bool("smart", opt.case == FilterCase::Smart) .with("smart", opt.case == FilterCase::Smart)
.with_bool("insensitive", opt.case == FilterCase::Insensitive) .with("insensitive", opt.case == FilterCase::Insensitive)
.with_bool("done", done) .with("done", done)
)); ));
} }
}); });

View file

@ -33,10 +33,10 @@ impl Tab {
while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await { while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await {
emit!(Call( emit!(Call(
Cmd::args("mgr:find_do", &[s]) Cmd::args("mgr:find_do", [s])
.with_bool("previous", opt.prev) .with("previous", opt.prev)
.with_bool("smart", opt.case == FilterCase::Smart) .with("smart", opt.case == FilterCase::Smart)
.with_bool("insensitive", opt.case == FilterCase::Insensitive) .with("insensitive", opt.case == FilterCase::Insensitive)
)); ));
} }
}); });

View file

@ -20,7 +20,8 @@ impl Tab {
handle.abort(); handle.abort();
} }
let mut input = InputProxy::show(InputCfg::search(opt.via.as_ref()).with_value(&*opt.subject)); let mut input =
InputProxy::show(InputCfg::search(opt.via.into_str()).with_value(&*opt.subject));
tokio::spawn(async move { tokio::spawn(async move {
if let Some(Ok(subject)) = input.recv().await { if let Some(Ok(subject)) = input.recv().await {
opt.subject = Cow::Owned(subject); opt.subject = Cow::Owned(subject);

View file

@ -15,7 +15,7 @@ impl Sendable {
Value::Number(n) => Data::Number(n), Value::Number(n) => Data::Number(n),
Value::String(b) => { Value::String(b) => {
if let Ok(s) = b.to_str() { if let Ok(s) = b.to_str() {
Data::String(s.to_owned()) Data::String(s.to_owned().into())
} else { } else {
Data::Bytes(b.as_bytes().to_owned()) Data::Bytes(b.as_bytes().to_owned())
} }
@ -99,7 +99,7 @@ impl Sendable {
Data::Boolean(b) => Value::Boolean(*b), Data::Boolean(b) => Value::Boolean(*b),
Data::Integer(i) => Value::Integer(*i), Data::Integer(i) => Value::Integer(*i),
Data::Number(n) => Value::Number(*n), Data::Number(n) => Value::Number(*n),
Data::String(s) => Value::String(lua.create_string(s)?), Data::String(s) => Value::String(lua.create_string(s.as_ref())?),
Data::List(l) => { Data::List(l) => {
let mut vec = Vec::with_capacity(l.len()); let mut vec = Vec::with_capacity(l.len());
for v in l { for v in l {

View file

@ -25,24 +25,19 @@ impl App {
app.render(); app.render();
let mut events = Vec::with_capacity(50); let mut events = Vec::with_capacity(50);
let (mut timeout, mut last_render) = (None, Instant::now());
macro_rules! drain_events { macro_rules! drain_events {
() => { () => {
for event in events.drain(..) { for event in events.drain(..) {
app.dispatch(event)?; app.dispatch(event)?;
try_render!(); if !NEED_RENDER.load(Ordering::Relaxed) {
} continue;
}; }
}
let (mut timeout, mut last_render) = (None, Instant::now()); timeout = Duration::from_millis(10).checked_sub(last_render.elapsed());
macro_rules! try_render { if timeout.is_none() {
() => {
if NEED_RENDER.load(Ordering::Relaxed) {
if let Some(sub) = Duration::from_millis(10).checked_sub(last_render.elapsed()) {
timeout = Some(sleep(sub));
} else {
app.render(); app.render();
(timeout, last_render) = (None, Instant::now()); last_render = Instant::now();
} }
} }
}; };
@ -51,7 +46,7 @@ impl App {
loop { loop {
if let Some(t) = timeout.take() { if let Some(t) = timeout.take() {
select! { select! {
_ = t => { _ = sleep(t) => {
app.render(); app.render();
last_render = Instant::now(); last_render = Instant::now();
} }

View file

@ -141,7 +141,7 @@ impl<'a> Executor<'a> {
on!(TABS, switch); on!(TABS, switch);
on!(TABS, swap); on!(TABS, swap);
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Mgr), "help" => self.app.cx.help.toggle(Layer::Mgr),
// Plugin // Plugin
@ -172,7 +172,7 @@ impl<'a> Executor<'a> {
on!(open_with); on!(open_with);
on!(process_exec); on!(process_exec);
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Tasks), "help" => self.app.cx.help.toggle(Layer::Tasks),
// Plugin // Plugin
@ -195,7 +195,7 @@ impl<'a> Executor<'a> {
on!(swipe); on!(swipe);
on!(copy); on!(copy);
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Spot), "help" => self.app.cx.help.toggle(Layer::Spot),
// Plugin // Plugin
@ -217,7 +217,7 @@ impl<'a> Executor<'a> {
on!(close); on!(close);
on!(arrow); on!(arrow);
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Pick), "help" => self.app.cx.help.toggle(Layer::Pick),
// Plugin // Plugin
@ -241,7 +241,7 @@ impl<'a> Executor<'a> {
match self.app.cx.input.mode() { match self.app.cx.input.mode() {
InputMode::Normal => { InputMode::Normal => {
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => return self.app.cx.help.toggle(Layer::Input), "help" => return self.app.cx.help.toggle(Layer::Input),
// Plugin // Plugin
@ -249,7 +249,7 @@ impl<'a> Executor<'a> {
_ => {} _ => {}
} }
} }
InputMode::Insert => match cmd.name.as_str() { InputMode::Insert => match cmd.name.as_ref() {
"complete" if cmd.bool("trigger") => return self.app.cx.cmp.trigger(cmd), "complete" if cmd.bool("trigger") => return self.app.cx.cmp.trigger(cmd),
_ => {} _ => {}
}, },
@ -286,7 +286,7 @@ impl<'a> Executor<'a> {
on!(arrow); on!(arrow);
on!(filter); on!(filter);
match cmd.name.as_str() { match cmd.name.as_ref() {
"close" => self.app.cx.help.toggle(Layer::Help), "close" => self.app.cx.help.toggle(Layer::Help),
// Plugin // Plugin
"plugin" => self.app.plugin(cmd), "plugin" => self.app.plugin(cmd),
@ -308,7 +308,7 @@ impl<'a> Executor<'a> {
on!(close); on!(close);
on!(arrow); on!(arrow);
match cmd.name.as_str() { match cmd.name.as_ref() {
// Help // Help
"help" => self.app.cx.help.toggle(Layer::Cmp), "help" => self.app.cx.help.toggle(Layer::Cmp),
// Plugin // Plugin

View file

@ -58,7 +58,7 @@ impl Fetcher {
impl UserData for Fetcher { impl UserData for Fetcher {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cmd, |lua, me| lua.create_string(&me.inner.run.name)); cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref()));
} }
} }
@ -75,7 +75,7 @@ impl Spotter {
impl UserData for Spotter { impl UserData for Spotter {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cmd, |lua, me| lua.create_string(&me.inner.run.name)); cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref()));
} }
} }
@ -92,7 +92,7 @@ impl Preloader {
impl UserData for Preloader { impl UserData for Preloader {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cmd, |lua, me| lua.create_string(&me.inner.run.name)); cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref()));
} }
} }
@ -109,6 +109,6 @@ impl Previewer {
impl UserData for Previewer { impl UserData for Previewer {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, cmd, |lua, me| lua.create_string(&me.inner.run.name)); cached_field!(fields, cmd, |lua, me| lua.create_string(me.inner.run.name.as_ref()));
} }
} }

View file

@ -19,7 +19,7 @@ pub fn peek(
skip: usize, skip: usize,
) -> Option<CancellationToken> { ) -> Option<CancellationToken> {
let ct = CancellationToken::new(); let ct = CancellationToken::new();
if let Some(c) = LOADER.read().get(&cmd.name) { if let Some(c) = LOADER.read().get(cmd.name.as_ref()) {
if c.sync_peek { if c.sync_peek {
peek_sync(cmd, file, mime, skip); peek_sync(cmd, file, mime, skip);
} else { } else {
@ -59,7 +59,7 @@ fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Cow<'static, str>, sk
plugin.call_method("peek", job) plugin.call_method("peek", job)
}); });
AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb)); AppProxy::plugin(PluginOpt::new_callback(cmd.name.as_ref(), cb));
} }
fn peek_async( fn peek_async(

View file

@ -16,5 +16,5 @@ pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) {
plugin.call_method("seek", job) plugin.call_method("seek", job)
}); });
AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb)); AppProxy::plugin(PluginOpt::new_callback(cmd.name.as_ref(), cb));
} }

View file

@ -14,8 +14,8 @@ impl Utils {
} }
pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> { pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (name, args): (mlua::String, Table)| { lua.create_function(|_, (name, args): (String, Table)| {
let mut cmd = Cmd::new_or(&name.to_str()?, Layer::Mgr)?; let mut cmd = Cmd::new_or(name, Layer::Mgr)?;
cmd.args = Sendable::table_to_args(args)?; cmd.args = Sendable::table_to_args(args)?;
Ok(emit!(Call(cmd))) Ok(emit!(Call(cmd)))
}) })
@ -23,7 +23,11 @@ impl Utils {
pub(super) fn mgr_emit(lua: &Lua) -> mlua::Result<Function> { pub(super) fn mgr_emit(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (name, args): (String, Table)| { lua.create_function(|_, (name, args): (String, Table)| {
emit!(Call(Cmd { name, args: Sendable::table_to_args(args)?, layer: Layer::Mgr })); emit!(Call(Cmd {
name: name.into(),
args: Sendable::table_to_args(args)?,
layer: Layer::Mgr,
}));
Ok(()) Ok(())
}) })
} }

View file

@ -21,7 +21,7 @@ impl Utils {
let cand = cand?; let cand = cand?;
cands.push(Chord { cands.push(Chord {
on: Self::parse_keys(cand.raw_get("on")?)?, on: Self::parse_keys(cand.raw_get("on")?)?,
run: vec![Cmd::args("which:callback", &[i]).with_any("tx", tx.clone())], run: vec![Cmd::args("which:callback", [i]).with_any("tx", tx.clone())],
desc: cand.raw_get("desc").ok(), desc: cand.raw_get("desc").ok(),
r#for: None, r#for: None,
}); });
@ -31,7 +31,7 @@ impl Utils {
emit!(Call( emit!(Call(
Cmd::new("which:show") Cmd::new("which:show")
.with_any("candidates", cands) .with_any("candidates", cands)
.with_bool("silent", t.raw_get("silent").unwrap_or_default()) .with("silent", t.raw_get::<bool>("silent").unwrap_or_default())
)); ));
Ok(rx.recv().await.map(|idx| idx + 1)) Ok(rx.recv().await.map(|idx| idx + 1))

View file

@ -11,6 +11,6 @@ impl CmpProxy {
#[inline] #[inline]
pub fn trigger(word: &str, ticket: Id) { pub fn trigger(word: &str, ticket: Id) {
emit!(Call(Cmd::args("cmp:trigger", &[word]).with("ticket", ticket))); emit!(Call(Cmd::args("cmp:trigger", [word]).with("ticket", ticket)));
} }
} }

View file

@ -13,7 +13,7 @@ impl MgrProxy {
#[inline] #[inline]
pub fn peek(force: bool) { pub fn peek(force: bool) {
emit!(Call(Cmd::new("mgr:peek").with_bool("force", force))); emit!(Call(Cmd::new("mgr:peek").with("force", force)));
} }
#[inline] #[inline]
@ -34,7 +34,7 @@ impl MgrProxy {
#[inline] #[inline]
pub fn remove_do(targets: Vec<Url>, permanently: bool) { pub fn remove_do(targets: Vec<Url>, permanently: bool) {
emit!(Call( emit!(Call(
Cmd::new("mgr:remove_do").with_bool("permanently", permanently).with_any("targets", targets) Cmd::new("mgr:remove_do").with("permanently", permanently).with_any("targets", targets)
)); ));
} }
@ -50,6 +50,6 @@ impl MgrProxy {
#[inline] #[inline]
pub fn update_paged_by(page: usize, only_if: &Url) { pub fn update_paged_by(page: usize, only_if: &Url) {
emit!(Call(Cmd::args("mgr:update_paged", &[page]).with_any("only-if", only_if.clone()))); emit!(Call(Cmd::args("mgr:update_paged", [page]).with_any("only-if", only_if.clone())));
} }
} }

View file

@ -33,7 +33,7 @@ impl TryFrom<CmdCow> for SearchOpt {
} }
// Via // Via
#[derive(PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub enum SearchOptVia { pub enum SearchOptVia {
Rg, Rg,
Rga, Rga,
@ -50,8 +50,8 @@ impl From<&str> for SearchOptVia {
} }
} }
impl AsRef<str> for SearchOptVia { impl SearchOptVia {
fn as_ref(&self) -> &str { pub fn into_str(self) -> &'static str {
match self { match self {
Self::Rg => "rg", Self::Rg => "rg",
Self::Rga => "rga", Self::Rga => "rga",

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
use yazi_macro::emit; use yazi_macro::emit;
use yazi_shared::{event::Cmd, url::Url}; use yazi_shared::{event::Cmd, url::Url};
@ -8,25 +10,25 @@ pub struct TabProxy;
impl TabProxy { impl TabProxy {
#[inline] #[inline]
pub fn cd(target: &Url) { pub fn cd(target: &Url) {
emit!(Call(Cmd::args("mgr:cd", &[target]))); emit!(Call(Cmd::args("mgr:cd", [target])));
} }
#[inline] #[inline]
pub fn reveal(target: &Url) { pub fn reveal(target: &Url) {
emit!(Call(Cmd::args("mgr:reveal", &[target]).with("no-dummy", true))); emit!(Call(Cmd::args("mgr:reveal", [target]).with("no-dummy", true)));
} }
#[inline] #[inline]
pub fn arrow(step: impl AsRef<str>) { pub fn arrow(step: impl Into<Cow<'static, str>>) {
emit!(Call(Cmd::args("mgr:arrow", &[step.as_ref()]))); emit!(Call(Cmd::args("mgr:arrow", [step.into()])));
} }
#[inline] #[inline]
pub fn search_do(opt: SearchOpt) { pub fn search_do(opt: SearchOpt) {
emit!(Call( emit!(Call(
// TODO: use second positional argument instead of `args` parameter // TODO: use second positional argument instead of `args` parameter
Cmd::args("mgr:search_do", &[opt.subject]) Cmd::args("mgr:search_do", [opt.subject])
.with("via", opt.via.as_ref().to_owned()) .with("via", Cow::Borrowed(opt.via.into_str()))
.with("args", opt.args_raw.into_owned()) .with("args", opt.args_raw.into_owned())
)); ));
} }

View file

@ -1,4 +1,4 @@
use std::{any::Any, borrow::Cow, collections::HashMap, fmt::{self, Display}, str::FromStr}; use std::{any::Any, borrow::Cow, collections::HashMap, fmt::{self, Display}, mem, str::FromStr};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use serde::{Deserialize, de}; use serde::{Deserialize, de};
@ -8,33 +8,48 @@ use crate::{Layer, url::Url};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Cmd { pub struct Cmd {
pub name: String, pub name: Cow<'static, str>,
pub args: HashMap<DataKey, Data>, pub args: HashMap<DataKey, Data>,
pub layer: Layer, pub layer: Layer,
} }
impl Cmd { impl Cmd {
pub fn new(s: &str) -> Self { pub fn new<N>(name: N) -> Self
Self::new_or(s, Default::default()) where
.unwrap_or_else(|_| Self { name: "null".to_owned(), ..Default::default() }) N: Into<Cow<'static, str>>,
{
Self::new_or(name, Default::default())
.unwrap_or_else(|_| Self { name: "null".into(), ..Default::default() })
} }
pub fn new_or(s: &str, default: Layer) -> Result<Self> { pub fn new_or<N>(name: N, default: Layer) -> Result<Self>
let (layer, name) = match s.split_once(':') { where
Some((l, n)) => (l.parse()?, n), N: Into<Cow<'static, str>>,
None => (default, s), {
let cow: Cow<'static, str> = name.into();
let (layer, name) = match cow.find(':') {
None => (default, cow),
Some(i) => (cow[..i].parse()?, match cow {
Cow::Borrowed(s) => Cow::Borrowed(&s[i + 1..]),
Cow::Owned(mut s) => {
s.drain(..i + 1);
Cow::Owned(s)
}
}),
}; };
Ok(Self { name: name.to_owned(), args: Default::default(), layer }) Ok(Self { name, args: Default::default(), layer })
} }
pub fn args(name: &str, args: &[impl ToString]) -> Self { pub fn args<N, D, I>(name: N, args: I) -> Self
where
N: Into<Cow<'static, str>>,
D: Into<Data>,
I: IntoIterator<Item = D>,
{
let mut me = Self::new(name); let mut me = Self::new(name);
me.args = args me.args =
.iter() args.into_iter().enumerate().map(|(i, a)| (DataKey::Integer(i as i64), a.into())).collect();
.enumerate()
.map(|(i, s)| (DataKey::Integer(i as i64), Data::String(s.to_string())))
.collect();
me me
} }
@ -59,12 +74,6 @@ impl Cmd {
self 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] #[inline]
pub fn with_any(mut self, name: impl Into<DataKey>, data: impl Any + Send + Sync) -> Self { 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.args.insert(name.into(), Data::Any(Box::new(data)));
@ -107,7 +116,7 @@ impl Cmd {
} }
#[inline] #[inline]
pub fn take_str(&mut self, name: impl Into<DataKey>) -> Option<String> { pub fn take_str(&mut self, name: impl Into<DataKey>) -> Option<Cow<'static, str>> {
if let Some(Data::String(s)) = self.take(name) { Some(s) } else { None } if let Some(Data::String(s)) = self.take(name) { Some(s) } else { None }
} }
@ -115,7 +124,7 @@ impl Cmd {
pub fn take_first(&mut self) -> Option<Data> { self.take(0) } pub fn take_first(&mut self) -> Option<Data> { self.take(0) }
#[inline] #[inline]
pub fn take_first_str(&mut self) -> Option<String> { pub fn take_first_str(&mut self) -> Option<Cow<'static, str>> {
if let Some(Data::String(s)) = self.take_first() { Some(s) } else { None } if let Some(Data::String(s)) = self.take_first() { Some(s) } else { None }
} }
@ -141,7 +150,7 @@ impl Cmd {
.map(|(word, normal)| { .map(|(word, normal)| {
let Some(arg) = word.strip_prefix("--").filter(|_| normal) else { let Some(arg) = word.strip_prefix("--").filter(|_| normal) else {
i += 1; i += 1;
return Ok((DataKey::Integer(i - obase as i64), Data::String(word))); return Ok((DataKey::Integer(i - obase as i64), Data::String(word.into())));
}; };
let mut parts = arg.splitn(2, '='); let mut parts = arg.splitn(2, '=');
@ -150,7 +159,7 @@ impl Cmd {
}; };
let val = if let Some(val) = parts.next() { let val = if let Some(val) = parts.next() {
Data::String(val.to_owned()) Data::String(val.to_owned().into())
} else { } else {
Data::Boolean(true) Data::Boolean(true)
}; };
@ -189,12 +198,12 @@ impl FromStr for Cmd {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let (words, last) = crate::shell::split_unix(s, true)?; let (mut words, last) = crate::shell::split_unix(s, true)?;
if words.is_empty() || words[0].is_empty() { if words.is_empty() || words[0].is_empty() {
bail!("command name cannot be empty"); bail!("command name cannot be empty");
} }
let mut me = Self::new(&words[0]); let mut me = Self::new(mem::take(&mut words[0]));
me.args = Cmd::parse_args(words.into_iter().skip(1), last, true)?; me.args = Cmd::parse_args(words.into_iter().skip(1), last, true)?;
Ok(me) Ok(me)
} }

View file

@ -40,7 +40,7 @@ impl CmdCow {
#[inline] #[inline]
pub fn take_str(&mut self, name: impl Into<DataKey>) -> Option<Cow<'static, str>> { pub fn take_str(&mut self, name: impl Into<DataKey>) -> Option<Cow<'static, str>> {
match self { match self {
Self::Owned(c) => c.take_str(name).map(Cow::Owned), Self::Owned(c) => c.take_str(name),
Self::Borrowed(c) => c.str(name).map(Cow::Borrowed), Self::Borrowed(c) => c.str(name).map(Cow::Borrowed),
} }
} }
@ -56,7 +56,7 @@ impl CmdCow {
#[inline] #[inline]
pub fn take_first_str(&mut self) -> Option<Cow<'static, str>> { pub fn take_first_str(&mut self) -> Option<Cow<'static, str>> {
match self { match self {
Self::Owned(c) => c.take_first_str().map(Cow::Owned), Self::Owned(c) => c.take_first_str(),
Self::Borrowed(c) => c.first_str().map(Cow::Borrowed), Self::Borrowed(c) => c.first_str().map(Cow::Borrowed),
} }
} }

View file

@ -12,7 +12,7 @@ pub enum Data {
Boolean(bool), Boolean(bool),
Integer(i64), Integer(i64),
Number(f64), Number(f64),
String(String), String(Cow<'static, str>),
List(Vec<Data>), List(Vec<Data>),
Dict(HashMap<DataKey, Data>), Dict(HashMap<DataKey, Data>),
Id(Id), Id(Id),
@ -56,13 +56,13 @@ impl Data {
#[inline] #[inline]
pub fn into_url(self) -> Option<Url> { pub fn into_url(self) -> Option<Url> {
match self { match self {
Data::String(s) => Some(Url::from(s)), Data::String(s) => Some(Url::from(s.as_ref())),
Data::Url(u) => Some(u), Data::Url(u) => Some(u),
_ => None, _ => None,
} }
} }
pub fn into_dict_string(self) -> HashMap<Cow<'static, str>, String> { pub fn into_dict_string(self) -> HashMap<Cow<'static, str>, Cow<'static, str>> {
let Self::Dict(dict) = self else { let Self::Dict(dict) = self else {
return Default::default(); return Default::default();
}; };
@ -79,7 +79,7 @@ impl Data {
#[inline] #[inline]
pub fn to_url(&self) -> Option<Url> { pub fn to_url(&self) -> Option<Url> {
match self { match self {
Self::String(s) => Some(Url::from(s)), Self::String(s) => Some(Url::from(s.as_ref())),
Self::Url(u) => Some(u.clone()), Self::Url(u) => Some(u.clone()),
_ => None, _ => None,
} }
@ -90,18 +90,34 @@ impl From<bool> for Data {
fn from(value: bool) -> Self { Self::Boolean(value) } fn from(value: bool) -> Self { Self::Boolean(value) }
} }
impl From<f64> for Data {
fn from(value: f64) -> Self { Self::Number(value) }
}
impl From<usize> for Data { impl From<usize> for Data {
fn from(value: usize) -> Self { Self::Id(value.into()) } fn from(value: usize) -> Self { Self::Id(value.into()) }
} }
impl From<String> for Data { impl From<String> for Data {
fn from(value: String) -> Self { Self::String(value) } fn from(value: String) -> Self { Self::String(Cow::Owned(value)) }
}
impl From<Cow<'static, str>> for Data {
fn from(value: Cow<'static, str>) -> Self { Self::String(value) }
} }
impl From<Id> for Data { impl From<Id> for Data {
fn from(value: Id) -> Self { Self::Id(value) } fn from(value: Id) -> Self { Self::Id(value) }
} }
impl From<&Url> for Data {
fn from(value: &Url) -> Self { Self::Url(value.clone()) }
}
impl From<&str> for Data {
fn from(value: &str) -> Self { Self::String(Cow::Owned(value.to_owned())) }
}
// --- Key // --- Key
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]