mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: multi-entry support for plugin system (#3154)
This commit is contained in:
parent
fa0e7e62c1
commit
3713478753
13 changed files with 124 additions and 111 deletions
|
|
@ -24,9 +24,6 @@ pub(super) enum Command {
|
||||||
/// Manage packages.
|
/// Manage packages.
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
Pkg(CommandPkg),
|
Pkg(CommandPkg),
|
||||||
#[command(hide = true)]
|
|
||||||
/// Manage packages.
|
|
||||||
Pack(CommandPack), // TODO: remove
|
|
||||||
/// Publish a message to the current instance.
|
/// Publish a message to the current instance.
|
||||||
Pub(CommandPub),
|
Pub(CommandPub),
|
||||||
/// Publish a message to the specified instance.
|
/// Publish a message to the specified instance.
|
||||||
|
|
@ -83,26 +80,6 @@ pub(super) enum CommandPkg {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
|
||||||
#[command(arg_required_else_help = true)]
|
|
||||||
pub(super) struct CommandPack {
|
|
||||||
/// Add packages.
|
|
||||||
#[arg(short = 'a', long, num_args = 1..)]
|
|
||||||
pub(super) add: Option<Vec<String>>,
|
|
||||||
/// Delete packages.
|
|
||||||
#[arg(short = 'd', long, num_args = 1..)]
|
|
||||||
pub(super) delete: Option<Vec<String>>,
|
|
||||||
/// Install all packages.
|
|
||||||
#[arg(short = 'i', long)]
|
|
||||||
pub(super) install: bool,
|
|
||||||
/// List all packages.
|
|
||||||
#[arg(short = 'l', long)]
|
|
||||||
pub(super) list: bool,
|
|
||||||
/// Upgrade all packages.
|
|
||||||
#[arg(short = 'u', long)]
|
|
||||||
pub(super) upgrade: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
pub(super) struct CommandPub {
|
pub(super) struct CommandPub {
|
||||||
/// Kind of message.
|
/// Kind of message.
|
||||||
|
|
|
||||||
|
|
@ -73,24 +73,6 @@ async fn run() -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Pack(cmd) => {
|
|
||||||
package::init()?;
|
|
||||||
outln!(
|
|
||||||
"WARNING: `ya pack` is deprecated, use the new `ya pkg` instead. See https://github.com/sxyazi/yazi/pull/2770 for more details."
|
|
||||||
)?;
|
|
||||||
if cmd.install {
|
|
||||||
package::Package::load().await?.install().await?;
|
|
||||||
} else if cmd.list {
|
|
||||||
package::Package::load().await?.print()?;
|
|
||||||
} else if cmd.upgrade {
|
|
||||||
package::Package::load().await?.upgrade_many(&[]).await?;
|
|
||||||
} else if let Some(uses) = cmd.add {
|
|
||||||
package::Package::load().await?.add_many(&uses).await?;
|
|
||||||
} else if let Some(uses) = cmd.delete {
|
|
||||||
package::Package::load().await?.delete_many(&uses).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Command::Pub(cmd) => {
|
Command::Pub(cmd) => {
|
||||||
yazi_boot::init_default();
|
yazi_boot::init_default();
|
||||||
yazi_dds::init();
|
yazi_dds::init();
|
||||||
|
|
|
||||||
|
|
@ -41,15 +41,12 @@ impl Dependency {
|
||||||
|
|
||||||
pub(super) async fn delete_sources(&self) -> Result<()> {
|
pub(super) async fn delete_sources(&self) -> Result<()> {
|
||||||
let dir = self.target();
|
let dir = self.target();
|
||||||
let files = if self.is_flavor {
|
let files =
|
||||||
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
|
if self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };
|
||||||
} else {
|
|
||||||
&["main.lua", "README.md", "LICENSE"][..]
|
|
||||||
};
|
|
||||||
|
|
||||||
for p in files.iter().map(|&f| dir.join(f)) {
|
for path in files.iter().map(|s| dir.join(s)) {
|
||||||
ok_or_not_found(remove_sealed(&p).await)
|
ok_or_not_found(remove_sealed(&path).await)
|
||||||
.with_context(|| format!("failed to delete `{}`", p.display()))?;
|
.with_context(|| format!("failed to delete `{}`", path.display()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok_or_not_found(Local::remove_dir(&dir).await).is_ok() {
|
if ok_or_not_found(Local::remove_dir(&dir).await).is_ok() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use std::{io::BufWriter, path::PathBuf, str::FromStr};
|
use std::{io::BufWriter, path::{Path, PathBuf}, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use twox_hash::XxHash3_128;
|
use twox_hash::XxHash3_128;
|
||||||
use yazi_fs::Xdg;
|
use yazi_fs::{Xdg, provider::local::Local};
|
||||||
|
use yazi_shared::BytesExt;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub(crate) struct Dependency {
|
pub(crate) struct Dependency {
|
||||||
|
|
@ -59,6 +60,30 @@ impl Dependency {
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn plugin_files(dir: &Path) -> std::io::Result<Vec<String>> {
|
||||||
|
let mut it = Local::read_dir(dir).await?;
|
||||||
|
let mut files: Vec<String> =
|
||||||
|
["LICENSE", "README.md", "main.lua"].into_iter().map(Into::into).collect();
|
||||||
|
while let Some(entry) = it.next_entry().await? {
|
||||||
|
if let Ok(name) = entry.file_name().into_string()
|
||||||
|
&& let Some(stripped) = name.strip_suffix(".lua")
|
||||||
|
&& stripped != "main"
|
||||||
|
&& stripped.as_bytes().kebab_cased()
|
||||||
|
{
|
||||||
|
files.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files.sort_unstable();
|
||||||
|
Ok(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn flavor_files() -> Vec<String> {
|
||||||
|
["LICENSE", "LICENSE-tmtheme", "README.md", "flavor.toml", "preview.png", "tmtheme.xml"]
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Dependency {
|
impl FromStr for Dependency {
|
||||||
|
|
@ -75,7 +100,7 @@ impl FromStr for Dependency {
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = if child.is_empty() { repo } else { child };
|
let name = if child.is_empty() { repo } else { child };
|
||||||
if !name.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
|
if !name.as_bytes().kebab_cased() {
|
||||||
bail!("Package name `{name}` must be in kebab-case")
|
bail!("Package name `{name}` must be in kebab-case")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,14 +57,9 @@ impl Dependency {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
|
async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
|
||||||
let files = if is_flavor {
|
let files = if is_flavor { Self::flavor_files() } else { Self::plugin_files(from).await? };
|
||||||
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
|
|
||||||
} else {
|
|
||||||
&["main.lua", "README.md", "LICENSE"][..]
|
|
||||||
};
|
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
let (from, to) = (from.join(file), to.join(file));
|
let (from, to) = (from.join(&file), to.join(&file));
|
||||||
copy_and_seal(&from, &to)
|
copy_and_seal(&from, &to)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
|
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,11 @@ use super::Dependency;
|
||||||
impl Dependency {
|
impl Dependency {
|
||||||
pub(crate) async fn hash(&self) -> Result<String> {
|
pub(crate) async fn hash(&self) -> Result<String> {
|
||||||
let dir = self.target();
|
let dir = self.target();
|
||||||
let files = if self.is_flavor {
|
let files =
|
||||||
&[
|
if self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };
|
||||||
"LICENSE",
|
|
||||||
"LICENSE-tmtheme",
|
|
||||||
"README.md",
|
|
||||||
"filestyle.toml",
|
|
||||||
"flavor.toml",
|
|
||||||
"preview.png",
|
|
||||||
"tmtheme.xml",
|
|
||||||
][..]
|
|
||||||
} else {
|
|
||||||
&["LICENSE", "README.md", "main.lua"][..]
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut h = XxHash3_128::new();
|
let mut h = XxHash3_128::new();
|
||||||
for &file in files {
|
for file in files {
|
||||||
h.write(file.as_bytes());
|
h.write(file.as_bytes());
|
||||||
h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0");
|
h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0");
|
||||||
h.write(&ok_or_not_found(Local::read(dir.join(file)).await)?);
|
h.write(&ok_or_not_found(Local::read(dir.join(file)).await)?);
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ impl Ember<'static> {
|
||||||
if it.peek() == Some(&b'@') {
|
if it.peek() == Some(&b'@') {
|
||||||
it.next(); // Skip `@` as it's a prefix for static messages
|
it.next(); // Skip `@` as it's a prefix for static messages
|
||||||
}
|
}
|
||||||
if !it.all(|b| b.is_ascii_alphanumeric() || b == b'-') {
|
if !it.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
|
||||||
bail!("Kind must be alphanumeric with dashes");
|
bail!("Kind `{kind}` must be in kebab-case");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt::Debug;
|
use std::{borrow::Cow, fmt::Debug};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
@ -35,7 +35,7 @@ impl TryFrom<CmdCow> for PluginOpt {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mode = c.str("mode").map(Into::into).unwrap_or_default();
|
let mode = c.str("mode").map(Into::into).unwrap_or_default();
|
||||||
Ok(Self { id, args, mode, cb: c.take_any("callback") })
|
Ok(Self { id: Self::normalize_id(id), args, mode, cb: c.take_any("callback") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,22 @@ impl Debug for PluginOpt {
|
||||||
|
|
||||||
impl PluginOpt {
|
impl PluginOpt {
|
||||||
pub fn new_callback(id: impl Into<SStr>, cb: PluginCallback) -> Self {
|
pub fn new_callback(id: impl Into<SStr>, cb: PluginCallback) -> Self {
|
||||||
Self { id: id.into(), mode: PluginMode::Sync, cb: Some(cb), ..Default::default() }
|
Self {
|
||||||
|
id: Self::normalize_id(id.into()),
|
||||||
|
mode: PluginMode::Sync,
|
||||||
|
cb: Some(cb),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_id(s: SStr) -> SStr {
|
||||||
|
match s {
|
||||||
|
Cow::Borrowed(s) => s.trim_end_matches(".main").into(),
|
||||||
|
Cow::Owned(mut s) => {
|
||||||
|
s.truncate(s.trim_end_matches(".main").len());
|
||||||
|
s.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use yazi_proxy::AppProxy;
|
||||||
use yazi_shared::{event::Cmd, pool::Symbol};
|
use yazi_shared::{event::Cmd, pool::Symbol};
|
||||||
|
|
||||||
use super::slim_lua;
|
use super::slim_lua;
|
||||||
use crate::loader::LOADER;
|
use crate::loader::{LOADER, Loader};
|
||||||
|
|
||||||
pub fn peek(
|
pub fn peek(
|
||||||
cmd: &'static Cmd,
|
cmd: &'static Cmd,
|
||||||
|
|
@ -18,8 +18,10 @@ pub fn peek(
|
||||||
mime: Symbol<str>,
|
mime: Symbol<str>,
|
||||||
skip: usize,
|
skip: usize,
|
||||||
) -> Option<CancellationToken> {
|
) -> Option<CancellationToken> {
|
||||||
|
let (id, ..) = Loader::normalize_id(&cmd.name).ok()?;
|
||||||
|
|
||||||
let ct = CancellationToken::new();
|
let ct = CancellationToken::new();
|
||||||
if let Some(c) = LOADER.read().get(cmd.name.as_ref()) {
|
if let Some(c) = LOADER.read().get(id) {
|
||||||
if c.sync_peek {
|
if c.sync_peek {
|
||||||
peek_sync(cmd, file, mime, skip);
|
peek_sync(cmd, file, mime, skip);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -32,11 +34,11 @@ pub fn peek(
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
select! {
|
select! {
|
||||||
_ = ct_.cancelled() => {},
|
_ = ct_.cancelled() => {},
|
||||||
Ok(b) = LOADER.ensure(&cmd.name, |c| c.sync_peek) => {
|
Ok(b) = LOADER.ensure(id, |c| c.sync_peek) => {
|
||||||
if b {
|
if b {
|
||||||
peek_sync( cmd, file, mime, skip);
|
peek_sync(cmd, file, mime, skip);
|
||||||
} else {
|
} else {
|
||||||
peek_async( cmd, file, mime, skip, ct_);
|
peek_async(cmd, file, mime, skip, ct_);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => {}
|
else => {}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{borrow::Cow, ops::Deref};
|
use std::{borrow::Cow, ops::Deref};
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail, ensure};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use mlua::{ChunkMode, ExternalError, Lua, Table};
|
use mlua::{ChunkMode, ExternalError, Lua, Table};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use yazi_boot::BOOT;
|
use yazi_boot::BOOT;
|
||||||
use yazi_fs::provider::local::Local;
|
use yazi_fs::provider::local::Local;
|
||||||
use yazi_macro::plugin_preset as preset;
|
use yazi_macro::plugin_preset as preset;
|
||||||
use yazi_shared::{LOG_LEVEL, RoCell};
|
use yazi_shared::{BytesExt, LOG_LEVEL, RoCell};
|
||||||
|
|
||||||
use super::Chunk;
|
use super::Chunk;
|
||||||
|
|
||||||
|
|
@ -20,7 +20,6 @@ pub struct Loader {
|
||||||
impl Deref for Loader {
|
impl Deref for Loader {
|
||||||
type Target = RwLock<HashMap<String, Chunk>>;
|
type Target = RwLock<HashMap<String, Chunk>>;
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target { &self.cache }
|
fn deref(&self) -> &Self::Target { &self.cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,26 +51,29 @@ impl Default for Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loader {
|
impl Loader {
|
||||||
pub async fn ensure<F, T>(&self, name: &str, f: F) -> Result<T>
|
pub async fn ensure<F, T>(&self, id: &str, f: F) -> Result<T>
|
||||||
where
|
where
|
||||||
F: FnOnce(&Chunk) -> T,
|
F: FnOnce(&Chunk) -> T,
|
||||||
{
|
{
|
||||||
if let Some(c) = self.cache.read().get(name) {
|
let (id, plugin, entry) = Self::normalize_id(id)?;
|
||||||
return Self::compatible_or_error(name, c).map(|_| f(c));
|
if let Some(c) = self.cache.read().get(id) {
|
||||||
|
return Self::compatible_or_error(id, c).map(|_| f(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
let p = BOOT.plugin_dir.join(format!("{name}.yazi/main.lua"));
|
let p = BOOT.plugin_dir.join(format!("{plugin}.yazi/{entry}.lua"));
|
||||||
let chunk =
|
let chunk =
|
||||||
Local::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into();
|
Local::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into();
|
||||||
|
|
||||||
let result = Self::compatible_or_error(name, &chunk);
|
let result = Self::compatible_or_error(id, &chunk);
|
||||||
let inspect = f(&chunk);
|
let inspect = f(&chunk);
|
||||||
|
|
||||||
self.cache.write().insert(name.to_owned(), chunk);
|
self.cache.write().insert(id.to_owned(), chunk);
|
||||||
result.map(|_| inspect)
|
result.map(|_| inspect)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
||||||
|
let (id, ..) = Self::normalize_id(id)?;
|
||||||
|
|
||||||
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
|
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
|
||||||
if let Ok(t) = loaded.raw_get(id) {
|
if let Ok(t) = loaded.raw_get(id) {
|
||||||
return Ok(t);
|
return Ok(t);
|
||||||
|
|
@ -85,18 +87,20 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_once(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
pub fn load_once(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
||||||
|
let (id, ..) = Self::normalize_id(id)?;
|
||||||
|
|
||||||
let mut mode = ChunkMode::Text;
|
let mut mode = ChunkMode::Text;
|
||||||
let f = match self.read().get(id) {
|
let f = match self.cache.read().get(id) {
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
mode = c.mode;
|
mode = c.mode;
|
||||||
lua.load(c).set_name(id).into_function()
|
lua.load(c).set_name(id).into_function()
|
||||||
}
|
}
|
||||||
None => Err(format!("plugin `{id}` not found").into_lua_err()),
|
None => Err(format!("Plugin `{id}` not found").into_lua_err()),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if mode != ChunkMode::Binary {
|
if mode != ChunkMode::Binary {
|
||||||
let b = f.dump(LOG_LEVEL.get().is_none());
|
let b = f.dump(LOG_LEVEL.get().is_none());
|
||||||
if let Some(c) = self.write().get_mut(id) {
|
if let Some(c) = self.cache.write().get_mut(id) {
|
||||||
c.mode = ChunkMode::Binary;
|
c.mode = ChunkMode::Binary;
|
||||||
c.bytes = Cow::Owned(b);
|
c.bytes = Cow::Owned(b);
|
||||||
}
|
}
|
||||||
|
|
@ -106,10 +110,13 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
|
||||||
|
let (id, ..) = Self::normalize_id(id)?;
|
||||||
lua.globals().raw_get::<Table>("package")?.raw_get::<Table>("loaded")?.raw_get(id)
|
lua.globals().raw_get::<Table>("package")?.raw_get::<Table>("loaded")?.raw_get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_with(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> {
|
pub fn load_with(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> {
|
||||||
|
let (id, ..) = Self::normalize_id(id)?;
|
||||||
|
|
||||||
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
|
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
|
||||||
if let Ok(t) = loaded.raw_get(id) {
|
if let Ok(t) = loaded.raw_get(id) {
|
||||||
return Ok(t);
|
return Ok(t);
|
||||||
|
|
@ -122,15 +129,24 @@ impl Loader {
|
||||||
Ok(t)
|
Ok(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compatible_or_error(name: &str, chunk: &Chunk) -> Result<()> {
|
pub fn compatible_or_error(id: &str, chunk: &Chunk) -> Result<()> {
|
||||||
if chunk.compatible() {
|
if chunk.compatible() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
bail!(
|
bail!(
|
||||||
"Plugin `{name}` requires at least Yazi {}, but your current version is Yazi {}.",
|
"Plugin `{id}` requires at least Yazi {}, but your current version is Yazi {}.",
|
||||||
chunk.since,
|
chunk.since,
|
||||||
yazi_boot::actions::Actions::version()
|
yazi_boot::actions::Actions::version()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn normalize_id(id: &str) -> anyhow::Result<(&str, &str, &str)> {
|
||||||
|
let id = id.trim_end_matches(".main");
|
||||||
|
let (plugin, entry) = if let Some((a, b)) = id.split_once(".") { (a, b) } else { (id, "main") };
|
||||||
|
|
||||||
|
ensure!(plugin.as_bytes().kebab_cased(), "Plugin name `{plugin}` must be in kebab-case");
|
||||||
|
ensure!(entry.as_bytes().kebab_cased(), "Entry name `{entry}` must be in kebab-case");
|
||||||
|
Ok((id, plugin, entry))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use mlua::{ExternalResult, Function, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, Table, Value};
|
use mlua::{ExternalResult, Function, IntoLua, Lua, MetaMethod, MultiValue, ObjectLike, Table, Value};
|
||||||
use yazi_binding::runtime_mut;
|
use yazi_binding::{runtime, runtime_mut};
|
||||||
|
|
||||||
use super::LOADER;
|
use super::LOADER;
|
||||||
|
|
||||||
|
|
@ -12,14 +12,15 @@ impl Require {
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"require",
|
"require",
|
||||||
lua.create_function(|lua, id: mlua::String| {
|
lua.create_function(|lua, id: mlua::String| {
|
||||||
let s = &id.to_str()?;
|
let id = id.to_str()?;
|
||||||
futures::executor::block_on(LOADER.ensure(s, |_| ())).into_lua_err()?;
|
let id = Self::absolute_id(lua, &id)?;
|
||||||
|
futures::executor::block_on(LOADER.ensure(&id, |_| ())).into_lua_err()?;
|
||||||
|
|
||||||
runtime_mut!(lua)?.push(s);
|
runtime_mut!(lua)?.push(&id);
|
||||||
let mod_ = LOADER.load(lua, s);
|
let mod_ = LOADER.load(lua, &id);
|
||||||
runtime_mut!(lua)?.pop();
|
runtime_mut!(lua)?.pop();
|
||||||
|
|
||||||
Self::create_mt(lua, s, mod_?, true)
|
Self::create_mt(lua, id.into_owned(), mod_?, true)
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -28,19 +29,20 @@ impl Require {
|
||||||
lua.globals().raw_set(
|
lua.globals().raw_set(
|
||||||
"require",
|
"require",
|
||||||
lua.create_async_function(|lua, id: mlua::String| async move {
|
lua.create_async_function(|lua, id: mlua::String| async move {
|
||||||
let s = &id.to_str()?;
|
let id = id.to_str()?;
|
||||||
LOADER.ensure(s, |_| ()).await.into_lua_err()?;
|
let id = Self::absolute_id(&lua, &id)?;
|
||||||
|
LOADER.ensure(&id, |_| ()).await.into_lua_err()?;
|
||||||
|
|
||||||
runtime_mut!(lua)?.push(s);
|
runtime_mut!(lua)?.push(&id);
|
||||||
let mod_ = LOADER.load(&lua, s);
|
let mod_ = LOADER.load(&lua, &id);
|
||||||
runtime_mut!(lua)?.pop();
|
runtime_mut!(lua)?.pop();
|
||||||
|
|
||||||
Self::create_mt(&lua, s, mod_?, false)
|
Self::create_mt(&lua, id.into_owned(), mod_?, false)
|
||||||
})?,
|
})?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_mt(lua: &Lua, id: &str, r#mod: Table, sync: bool) -> mlua::Result<Table> {
|
fn create_mt(lua: &Lua, id: String, r#mod: Table, sync: bool) -> mlua::Result<Table> {
|
||||||
let id: Arc<str> = Arc::from(id);
|
let id: Arc<str> = Arc::from(id);
|
||||||
let mt = lua.create_table_from([
|
let mt = lua.create_table_from([
|
||||||
(
|
(
|
||||||
|
|
@ -112,4 +114,13 @@ impl Require {
|
||||||
(LOADER.try_load(lua, id)?, args)
|
(LOADER.try_load(lua, id)?, args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn absolute_id<'a>(lua: &Lua, id: &'a str) -> mlua::Result<Cow<'a, str>> {
|
||||||
|
let Some(stripped) = id.strip_prefix('.') else { return Ok(id.into()) };
|
||||||
|
Ok(if let Some(cur) = runtime!(lua)?.current() {
|
||||||
|
format!("{}.{stripped}", cur.split('.').next().unwrap_or(cur)).into()
|
||||||
|
} else {
|
||||||
|
stripped.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ pub fn compose(
|
||||||
b"render" => Utils::render(lua)?,
|
b"render" => Utils::render(lua)?,
|
||||||
b"emit" => Utils::emit(lua)?,
|
b"emit" => Utils::emit(lua)?,
|
||||||
b"mgr_emit" => Utils::mgr_emit(lua)?,
|
b"mgr_emit" => Utils::mgr_emit(lua)?,
|
||||||
b"manager_emit" => Utils::mgr_emit(lua)?, // TODO: remove this in the future
|
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
b"image_info" => Utils::image_info(lua)?,
|
b"image_info" => Utils::image_info(lua)?,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
pub trait BytesExt {
|
pub trait BytesExt {
|
||||||
|
fn kebab_cased(&self) -> bool;
|
||||||
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
|
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BytesExt for [u8] {
|
impl BytesExt for [u8] {
|
||||||
|
fn kebab_cased(&self) -> bool {
|
||||||
|
self.iter().all(|&b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-'))
|
||||||
|
}
|
||||||
|
|
||||||
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {
|
fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||||
let idx = memchr::memmem::find(self, sep)?;
|
let idx = memchr::memmem::find(self, sep)?;
|
||||||
let (left, right) = self.split_at(idx);
|
let (left, right) = self.split_at(idx);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue