feat: invalid UTF-8 support to Command:arg() and Command:env() APIs (#2895)

This commit is contained in:
三咲雅 misaki masa 2025-06-18 21:32:24 +08:00 committed by sxyazi
parent 750ca0c0d4
commit 1a6abae974
No known key found for this signature in database
4 changed files with 56 additions and 42 deletions

View file

@ -139,7 +139,7 @@ impl UserData for Url {
});
methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| {
Ok(Self::new(ud.take::<Self>()?.inner.into_search(frag.as_bytes().as_ref().into_os_str()?)))
Ok(Self::new(ud.take::<Self>()?.inner.into_search(frag.as_bytes().into_os_str()?)))
});
methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner));

View file

@ -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>, Cow<'static, str>>,
updates: HashMap<DataKey, Data>,
}
impl TryFrom<CmdCow> for Opt {
type Error = ();
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
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) {

View file

@ -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::<Self>()?;
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::<mlua::String>() {
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::<Self>()?.inner.env(
String::from_utf8_lossy(&key.as_bytes()).as_ref(),
String::from_utf8_lossy(&value.as_bytes()).as_ref(),
);
ud.borrow_mut::<Self>()?
.inner
.env(key.as_bytes().into_os_str()?, value.as_bytes().into_os_str()?);
Ok(ud)
},
);

View file

@ -46,6 +46,32 @@ impl Data {
}
}
#[inline]
pub fn into_string(self) -> Option<Cow<'static, str>> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
#[inline]
pub fn into_dict(self) -> Option<HashMap<DataKey, Data>> {
match self {
Self::Dict(d) => Some(d),
_ => None,
}
}
#[inline]
pub fn into_url(self) -> Option<Url> {
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<T: 'static>(self) -> Option<T> {
match self {
@ -54,34 +80,12 @@ impl Data {
}
}
#[inline]
pub fn into_url(self) -> Option<Url> {
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>, 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<Url> {
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<Url> {
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<i64, D::Error>
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,
}
}