From 1a6abae974370702c8865459344bf256de58359e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Wed, 18 Jun 2025 21:32:24 +0800 Subject: [PATCH] feat: invalid UTF-8 support to `Command:arg()` and `Command:env()` APIs (#2895) --- yazi-binding/src/url.rs | 2 +- yazi-core/src/mgr/commands/update_mimes.rs | 10 +-- yazi-plugin/src/process/command.rs | 12 ++-- yazi-shared/src/event/data.rs | 74 +++++++++++++--------- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index ef507564..7b01b827 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -139,7 +139,7 @@ impl UserData for Url { }); methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| { - Ok(Self::new(ud.take::()?.inner.into_search(frag.as_bytes().as_ref().into_os_str()?))) + Ok(Self::new(ud.take::()?.inner.into_search(frag.as_bytes().into_os_str()?))) }); methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner)); diff --git a/yazi-core/src/mgr/commands/update_mimes.rs b/yazi-core/src/mgr/commands/update_mimes.rs index 638aacac..36937cc5 100644 --- a/yazi-core/src/mgr/commands/update_mimes.rs +++ b/yazi-core/src/mgr/commands/update_mimes.rs @@ -1,20 +1,20 @@ -use std::{borrow::Cow, collections::HashMap}; +use std::collections::HashMap; use tracing::error; use yazi_macro::render; -use yazi_shared::{event::CmdCow, url::Url}; +use yazi_shared::event::{CmdCow, Data, DataKey}; use crate::{mgr::{LINKED, Mgr}, tasks::Tasks}; pub struct Opt { - updates: HashMap, Cow<'static, str>>, + updates: HashMap, } impl TryFrom for Opt { type Error = (); fn try_from(mut c: CmdCow) -> Result { - Ok(Self { updates: c.try_take("updates").ok_or(())?.into_dict_string() }) + Ok(Self { updates: c.try_take("updates").and_then(Data::into_dict).ok_or(())? }) } } @@ -28,7 +28,7 @@ impl Mgr { let updates = opt .updates .into_iter() - .flat_map(|(url, mime)| Url::try_from(url.as_ref()).map(|u| (u, mime))) + .flat_map(|(key, value)| key.into_url().zip(value.into_string())) .filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime)) .fold(HashMap::new(), |mut map, (u, m)| { for u in linked.from_file(&u) { diff --git a/yazi-plugin/src/process/command.rs b/yazi-plugin/src/process/command.rs index 8a224dba..f1d2c57c 100644 --- a/yazi-plugin/src/process/command.rs +++ b/yazi-plugin/src/process/command.rs @@ -3,6 +3,7 @@ use std::{any::TypeId, io, process::Stdio}; use mlua::{AnyUserData, ExternalError, IntoLuaMulti, Lua, MetaMethod, Table, UserData, Value}; use tokio::process::{ChildStderr, ChildStdin, ChildStdout}; use yazi_binding::Error; +use yazi_shared::IntoOsStr; use super::{Child, output::Output}; use crate::process::Status; @@ -143,11 +144,11 @@ impl UserData for Command { let mut me = ud.borrow_mut::()?; match arg { Value::String(s) => { - me.inner.arg(String::from_utf8_lossy(&s.as_bytes()).as_ref()); + me.inner.arg(s.as_bytes().into_os_str()?); } Value::Table(t) => { for s in t.sequence_values::() { - me.inner.arg(String::from_utf8_lossy(&s?.as_bytes()).as_ref()); + me.inner.arg(s?.as_bytes().into_os_str()?); } } _ => return Err("arg must be a string or table of strings".into_lua_err()), @@ -162,10 +163,9 @@ impl UserData for Command { methods.add_function_mut( "env", |_, (ud, key, value): (AnyUserData, mlua::String, mlua::String)| { - ud.borrow_mut::()?.inner.env( - String::from_utf8_lossy(&key.as_bytes()).as_ref(), - String::from_utf8_lossy(&value.as_bytes()).as_ref(), - ); + ud.borrow_mut::()? + .inner + .env(key.as_bytes().into_os_str()?, value.as_bytes().into_os_str()?); Ok(ud) }, ); diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 3b7a8109..c16b6124 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -46,6 +46,32 @@ impl Data { } } + #[inline] + pub fn into_string(self) -> Option> { + match self { + Self::String(s) => Some(s), + _ => None, + } + } + + #[inline] + pub fn into_dict(self) -> Option> { + match self { + Self::Dict(d) => Some(d), + _ => None, + } + } + + #[inline] + pub fn into_url(self) -> Option { + match self { + Self::String(s) => Url::try_from(s.as_ref()).ok(), + Self::Url(u) => Some(u), + Self::Bytes(b) => Url::try_from(b.as_slice()).ok(), + _ => None, + } + } + #[inline] pub fn into_any(self) -> Option { match self { @@ -54,34 +80,12 @@ impl Data { } } - #[inline] - pub fn into_url(self) -> Option { - match self { - Data::String(s) => Url::try_from(s.as_ref()).ok(), - Data::Url(u) => Some(u), - _ => None, - } - } - - pub fn into_dict_string(self) -> HashMap, Cow<'static, str>> { - let Self::Dict(dict) = self else { - return Default::default(); - }; - - let mut map = HashMap::with_capacity(dict.len()); - for pair in dict { - if let (DataKey::String(k), Self::String(v)) = pair { - map.insert(k, v); - } - } - map - } - #[inline] pub fn to_url(&self) -> Option { match self { Self::String(s) => Url::try_from(s.as_ref()).ok(), Self::Url(u) => Some(u.clone()), + Self::Bytes(b) => Url::try_from(b.as_slice()).ok(), _ => None, } } @@ -158,6 +162,16 @@ impl DataKey { } } + #[inline] + pub fn into_url(self) -> Option { + match self { + Self::String(s) => Url::try_from(s.as_ref()).ok(), + Self::Url(u) => Some(u), + Self::Bytes(b) => Url::try_from(b.as_slice()).ok(), + _ => None, + } + } + fn deserialize_integer<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, @@ -204,9 +218,9 @@ macro_rules! impl_as_integer { #[inline] pub fn $name(&self) -> Option<$t> { match self { - Data::Integer(i) => <$t>::try_from(*i).ok(), - Data::String(s) => s.parse().ok(), - Data::Id(i) => <$t>::try_from(i.get()).ok(), + Self::Integer(i) => <$t>::try_from(*i).ok(), + Self::String(s) => s.parse().ok(), + Self::Id(i) => <$t>::try_from(i.get()).ok(), _ => None, } } @@ -220,10 +234,10 @@ macro_rules! impl_as_number { #[inline] pub fn $name(&self) -> Option<$t> { match self { - Data::Integer(i) if *i == (*i as $t as _) => Some(*i as $t), - Data::Number(n) => <$t>::try_from(*n).ok(), - Data::String(s) => s.parse().ok(), - Data::Id(i) if i.0 == (i.0 as $t as _) => Some(i.0 as $t), + Self::Integer(i) if *i == (*i as $t as _) => Some(*i as $t), + Self::Number(n) => <$t>::try_from(*n).ok(), + Self::String(s) => s.parse().ok(), + Self::Id(i) if i.0 == (i.0 as $t as _) => Some(i.0 as $t), _ => None, } }