Skip to content

Commit 3713478

Browse files
authored
feat: multi-entry support for plugin system (#3154)
1 parent fa0e7e6 commit 3713478

File tree

13 files changed

+124
-111
lines changed

13 files changed

+124
-111
lines changed

yazi-cli/src/args.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ pub(super) enum Command {
2424
/// Manage packages.
2525
#[command(subcommand)]
2626
Pkg(CommandPkg),
27-
#[command(hide = true)]
28-
/// Manage packages.
29-
Pack(CommandPack), // TODO: remove
3027
/// Publish a message to the current instance.
3128
Pub(CommandPub),
3229
/// Publish a message to the specified instance.
@@ -83,26 +80,6 @@ pub(super) enum CommandPkg {
8380
},
8481
}
8582

86-
#[derive(clap::Args)]
87-
#[command(arg_required_else_help = true)]
88-
pub(super) struct CommandPack {
89-
/// Add packages.
90-
#[arg(short = 'a', long, num_args = 1..)]
91-
pub(super) add: Option<Vec<String>>,
92-
/// Delete packages.
93-
#[arg(short = 'd', long, num_args = 1..)]
94-
pub(super) delete: Option<Vec<String>>,
95-
/// Install all packages.
96-
#[arg(short = 'i', long)]
97-
pub(super) install: bool,
98-
/// List all packages.
99-
#[arg(short = 'l', long)]
100-
pub(super) list: bool,
101-
/// Upgrade all packages.
102-
#[arg(short = 'u', long)]
103-
pub(super) upgrade: bool,
104-
}
105-
10683
#[derive(clap::Args)]
10784
pub(super) struct CommandPub {
10885
/// Kind of message.

yazi-cli/src/main.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,6 @@ async fn run() -> anyhow::Result<()> {
7373
}
7474
}
7575

76-
Command::Pack(cmd) => {
77-
package::init()?;
78-
outln!(
79-
"WARNING: `ya pack` is deprecated, use the new `ya pkg` instead. See https://github.com/sxyazi/yazi/pull/2770 for more details."
80-
)?;
81-
if cmd.install {
82-
package::Package::load().await?.install().await?;
83-
} else if cmd.list {
84-
package::Package::load().await?.print()?;
85-
} else if cmd.upgrade {
86-
package::Package::load().await?.upgrade_many(&[]).await?;
87-
} else if let Some(uses) = cmd.add {
88-
package::Package::load().await?.add_many(&uses).await?;
89-
} else if let Some(uses) = cmd.delete {
90-
package::Package::load().await?.delete_many(&uses).await?;
91-
}
92-
}
93-
9476
Command::Pub(cmd) => {
9577
yazi_boot::init_default();
9678
yazi_dds::init();

yazi-cli/src/package/delete.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,12 @@ impl Dependency {
4141

4242
pub(super) async fn delete_sources(&self) -> Result<()> {
4343
let dir = self.target();
44-
let files = if self.is_flavor {
45-
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
46-
} else {
47-
&["main.lua", "README.md", "LICENSE"][..]
48-
};
44+
let files =
45+
if self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };
4946

50-
for p in files.iter().map(|&f| dir.join(f)) {
51-
ok_or_not_found(remove_sealed(&p).await)
52-
.with_context(|| format!("failed to delete `{}`", p.display()))?;
47+
for path in files.iter().map(|s| dir.join(s)) {
48+
ok_or_not_found(remove_sealed(&path).await)
49+
.with_context(|| format!("failed to delete `{}`", path.display()))?;
5350
}
5451

5552
if ok_or_not_found(Local::remove_dir(&dir).await).is_ok() {

yazi-cli/src/package/dependency.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
use std::{io::BufWriter, path::PathBuf, str::FromStr};
1+
use std::{io::BufWriter, path::{Path, PathBuf}, str::FromStr};
22

33
use anyhow::{Result, bail};
44
use serde::{Deserialize, Deserializer, Serialize, Serializer};
55
use twox_hash::XxHash3_128;
6-
use yazi_fs::Xdg;
6+
use yazi_fs::{Xdg, provider::local::Local};
7+
use yazi_shared::BytesExt;
78

89
#[derive(Clone, Default)]
910
pub(crate) struct Dependency {
@@ -59,6 +60,30 @@ impl Dependency {
5960
)?;
6061
Ok(())
6162
}
63+
64+
pub(super) async fn plugin_files(dir: &Path) -> std::io::Result<Vec<String>> {
65+
let mut it = Local::read_dir(dir).await?;
66+
let mut files: Vec<String> =
67+
["LICENSE", "README.md", "main.lua"].into_iter().map(Into::into).collect();
68+
while let Some(entry) = it.next_entry().await? {
69+
if let Ok(name) = entry.file_name().into_string()
70+
&& let Some(stripped) = name.strip_suffix(".lua")
71+
&& stripped != "main"
72+
&& stripped.as_bytes().kebab_cased()
73+
{
74+
files.push(name);
75+
}
76+
}
77+
files.sort_unstable();
78+
Ok(files)
79+
}
80+
81+
pub(super) fn flavor_files() -> Vec<String> {
82+
["LICENSE", "LICENSE-tmtheme", "README.md", "flavor.toml", "preview.png", "tmtheme.xml"]
83+
.into_iter()
84+
.map(Into::into)
85+
.collect()
86+
}
6287
}
6388

6489
impl FromStr for Dependency {
@@ -75,7 +100,7 @@ impl FromStr for Dependency {
75100
};
76101

77102
let name = if child.is_empty() { repo } else { child };
78-
if !name.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
103+
if !name.as_bytes().kebab_cased() {
79104
bail!("Package name `{name}` must be in kebab-case")
80105
}
81106

yazi-cli/src/package/deploy.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,9 @@ impl Dependency {
5757
}
5858

5959
async fn deploy_sources(from: &Path, to: &Path, is_flavor: bool) -> Result<()> {
60-
let files = if is_flavor {
61-
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
62-
} else {
63-
&["main.lua", "README.md", "LICENSE"][..]
64-
};
65-
60+
let files = if is_flavor { Self::flavor_files() } else { Self::plugin_files(from).await? };
6661
for file in files {
67-
let (from, to) = (from.join(file), to.join(file));
62+
let (from, to) = (from.join(&file), to.join(&file));
6863
copy_and_seal(&from, &to)
6964
.await
7065
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))?;

yazi-cli/src/package/hash.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,11 @@ use super::Dependency;
77
impl Dependency {
88
pub(crate) async fn hash(&self) -> Result<String> {
99
let dir = self.target();
10-
let files = if self.is_flavor {
11-
&[
12-
"LICENSE",
13-
"LICENSE-tmtheme",
14-
"README.md",
15-
"filestyle.toml",
16-
"flavor.toml",
17-
"preview.png",
18-
"tmtheme.xml",
19-
][..]
20-
} else {
21-
&["LICENSE", "README.md", "main.lua"][..]
22-
};
10+
let files =
11+
if self.is_flavor { Self::flavor_files() } else { Self::plugin_files(&dir).await? };
2312

2413
let mut h = XxHash3_128::new();
25-
for &file in files {
14+
for file in files {
2615
h.write(file.as_bytes());
2716
h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0");
2817
h.write(&ok_or_not_found(Local::read(dir.join(file)).await)?);

yazi-dds/src/ember/ember.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ impl Ember<'static> {
8181
if it.peek() == Some(&b'@') {
8282
it.next(); // Skip `@` as it's a prefix for static messages
8383
}
84-
if !it.all(|b| b.is_ascii_alphanumeric() || b == b'-') {
85-
bail!("Kind must be alphanumeric with dashes");
84+
if !it.all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
85+
bail!("Kind `{kind}` must be in kebab-case");
8686
}
8787

8888
Ok(())

yazi-parser/src/app/plugin.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt::Debug;
1+
use std::{borrow::Cow, fmt::Debug};
22

33
use anyhow::bail;
44
use hashbrown::HashMap;
@@ -35,7 +35,7 @@ impl TryFrom<CmdCow> for PluginOpt {
3535
};
3636

3737
let mode = c.str("mode").map(Into::into).unwrap_or_default();
38-
Ok(Self { id, args, mode, cb: c.take_any("callback") })
38+
Ok(Self { id: Self::normalize_id(id), args, mode, cb: c.take_any("callback") })
3939
}
4040
}
4141

@@ -52,7 +52,22 @@ impl Debug for PluginOpt {
5252

5353
impl PluginOpt {
5454
pub fn new_callback(id: impl Into<SStr>, cb: PluginCallback) -> Self {
55-
Self { id: id.into(), mode: PluginMode::Sync, cb: Some(cb), ..Default::default() }
55+
Self {
56+
id: Self::normalize_id(id.into()),
57+
mode: PluginMode::Sync,
58+
cb: Some(cb),
59+
..Default::default()
60+
}
61+
}
62+
63+
fn normalize_id(s: SStr) -> SStr {
64+
match s {
65+
Cow::Borrowed(s) => s.trim_end_matches(".main").into(),
66+
Cow::Owned(mut s) => {
67+
s.truncate(s.trim_end_matches(".main").len());
68+
s.into()
69+
}
70+
}
5671
}
5772
}
5873

yazi-plugin/src/isolate/peek.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ use yazi_proxy::AppProxy;
1010
use yazi_shared::{event::Cmd, pool::Symbol};
1111

1212
use super::slim_lua;
13-
use crate::loader::LOADER;
13+
use crate::loader::{LOADER, Loader};
1414

1515
pub fn peek(
1616
cmd: &'static Cmd,
1717
file: yazi_fs::File,
1818
mime: Symbol<str>,
1919
skip: usize,
2020
) -> Option<CancellationToken> {
21+
let (id, ..) = Loader::normalize_id(&cmd.name).ok()?;
22+
2123
let ct = CancellationToken::new();
22-
if let Some(c) = LOADER.read().get(cmd.name.as_ref()) {
24+
if let Some(c) = LOADER.read().get(id) {
2325
if c.sync_peek {
2426
peek_sync(cmd, file, mime, skip);
2527
} else {
@@ -32,11 +34,11 @@ pub fn peek(
3234
tokio::spawn(async move {
3335
select! {
3436
_ = ct_.cancelled() => {},
35-
Ok(b) = LOADER.ensure(&cmd.name, |c| c.sync_peek) => {
37+
Ok(b) = LOADER.ensure(id, |c| c.sync_peek) => {
3638
if b {
37-
peek_sync( cmd, file, mime, skip);
39+
peek_sync(cmd, file, mime, skip);
3840
} else {
39-
peek_async( cmd, file, mime, skip, ct_);
41+
peek_async(cmd, file, mime, skip, ct_);
4042
}
4143
},
4244
else => {}

yazi-plugin/src/loader/loader.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use std::{borrow::Cow, ops::Deref};
22

3-
use anyhow::{Context, Result, bail};
3+
use anyhow::{Context, Result, bail, ensure};
44
use hashbrown::HashMap;
55
use mlua::{ChunkMode, ExternalError, Lua, Table};
66
use parking_lot::RwLock;
77
use yazi_boot::BOOT;
88
use yazi_fs::provider::local::Local;
99
use yazi_macro::plugin_preset as preset;
10-
use yazi_shared::{LOG_LEVEL, RoCell};
10+
use yazi_shared::{BytesExt, LOG_LEVEL, RoCell};
1111

1212
use super::Chunk;
1313

@@ -20,7 +20,6 @@ pub struct Loader {
2020
impl Deref for Loader {
2121
type Target = RwLock<HashMap<String, Chunk>>;
2222

23-
#[inline]
2423
fn deref(&self) -> &Self::Target { &self.cache }
2524
}
2625

@@ -52,26 +51,29 @@ impl Default for Loader {
5251
}
5352

5453
impl Loader {
55-
pub async fn ensure<F, T>(&self, name: &str, f: F) -> Result<T>
54+
pub async fn ensure<F, T>(&self, id: &str, f: F) -> Result<T>
5655
where
5756
F: FnOnce(&Chunk) -> T,
5857
{
59-
if let Some(c) = self.cache.read().get(name) {
60-
return Self::compatible_or_error(name, c).map(|_| f(c));
58+
let (id, plugin, entry) = Self::normalize_id(id)?;
59+
if let Some(c) = self.cache.read().get(id) {
60+
return Self::compatible_or_error(id, c).map(|_| f(c));
6161
}
6262

63-
let p = BOOT.plugin_dir.join(format!("{name}.yazi/main.lua"));
63+
let p = BOOT.plugin_dir.join(format!("{plugin}.yazi/{entry}.lua"));
6464
let chunk =
6565
Local::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into();
6666

67-
let result = Self::compatible_or_error(name, &chunk);
67+
let result = Self::compatible_or_error(id, &chunk);
6868
let inspect = f(&chunk);
6969

70-
self.cache.write().insert(name.to_owned(), chunk);
70+
self.cache.write().insert(id.to_owned(), chunk);
7171
result.map(|_| inspect)
7272
}
7373

7474
pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
75+
let (id, ..) = Self::normalize_id(id)?;
76+
7577
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
7678
if let Ok(t) = loaded.raw_get(id) {
7779
return Ok(t);
@@ -85,18 +87,20 @@ impl Loader {
8587
}
8688

8789
pub fn load_once(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
90+
let (id, ..) = Self::normalize_id(id)?;
91+
8892
let mut mode = ChunkMode::Text;
89-
let f = match self.read().get(id) {
93+
let f = match self.cache.read().get(id) {
9094
Some(c) => {
9195
mode = c.mode;
9296
lua.load(c).set_name(id).into_function()
9397
}
94-
None => Err(format!("plugin `{id}` not found").into_lua_err()),
98+
None => Err(format!("Plugin `{id}` not found").into_lua_err()),
9599
}?;
96100

97101
if mode != ChunkMode::Binary {
98102
let b = f.dump(LOG_LEVEL.get().is_none());
99-
if let Some(c) = self.write().get_mut(id) {
103+
if let Some(c) = self.cache.write().get_mut(id) {
100104
c.mode = ChunkMode::Binary;
101105
c.bytes = Cow::Owned(b);
102106
}
@@ -106,10 +110,13 @@ impl Loader {
106110
}
107111

108112
pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
113+
let (id, ..) = Self::normalize_id(id)?;
109114
lua.globals().raw_get::<Table>("package")?.raw_get::<Table>("loaded")?.raw_get(id)
110115
}
111116

112117
pub fn load_with(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> {
118+
let (id, ..) = Self::normalize_id(id)?;
119+
113120
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
114121
if let Ok(t) = loaded.raw_get(id) {
115122
return Ok(t);
@@ -122,15 +129,24 @@ impl Loader {
122129
Ok(t)
123130
}
124131

125-
pub fn compatible_or_error(name: &str, chunk: &Chunk) -> Result<()> {
132+
pub fn compatible_or_error(id: &str, chunk: &Chunk) -> Result<()> {
126133
if chunk.compatible() {
127134
return Ok(());
128135
}
129136

130137
bail!(
131-
"Plugin `{name}` requires at least Yazi {}, but your current version is Yazi {}.",
138+
"Plugin `{id}` requires at least Yazi {}, but your current version is Yazi {}.",
132139
chunk.since,
133140
yazi_boot::actions::Actions::version()
134141
);
135142
}
143+
144+
pub fn normalize_id(id: &str) -> anyhow::Result<(&str, &str, &str)> {
145+
let id = id.trim_end_matches(".main");
146+
let (plugin, entry) = if let Some((a, b)) = id.split_once(".") { (a, b) } else { (id, "main") };
147+
148+
ensure!(plugin.as_bytes().kebab_cased(), "Plugin name `{plugin}` must be in kebab-case");
149+
ensure!(entry.as_bytes().kebab_cased(), "Entry name `{entry}` must be in kebab-case");
150+
Ok((id, plugin, entry))
151+
}
136152
}

0 commit comments

Comments
 (0)