Skip to content

Commit 22d97b0

Browse files
feat: support custom oma plugin as subcommand (#534)
* feat: support custom oma plugin as subcommand * feat: add localisation for subcmd-related strings --------- Co-authored-by: Mingcong Bai <[email protected]>
1 parent 33fb34e commit 22d97b0

File tree

6 files changed

+127
-12
lines changed

6 files changed

+127
-12
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ authors = ["eatradish <[email protected]>"]
1010

1111
[dependencies]
1212
# Cli
13-
clap = { version = "4.5.19", features = ["cargo", "wrap_help", "color", "derive", "env"] }
13+
clap = { version = "4.5.19", features = ["cargo", "wrap_help", "color", "derive", "env", "string"] }
1414
anyhow = "1.0.89"
1515
ctrlc = "3.4.5"
1616
dialoguer = "0.12.0"
@@ -51,6 +51,7 @@ termtree = "0.5.1"
5151
textwrap = "0.16.2"
5252
sysinfo = "0.37"
5353
once_cell = "1.21"
54+
itertools = "0.14.0"
5455

5556
# oma crates
5657
oma-utils = { path = "./oma-utils", features = ["dbus", "human-bytes", "oma"] }

i18n/en-US/oma.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,6 @@ not-allow-delete-using-kernel = Not allow deletion of the Linux kernel version i
297297
yes-mode-conflict-ui = `--yes` mode will not be applied in UI mode.
298298
oma-refresh-no-metadata-to-download = No suitable package metadata available based on your repository configuration.
299299
delete-current-kernel-tips = { $kernel } is currently in use, removing it may render the system inoperable.
300+
custom-command-unknown = Unknown command: `{ $subcmd }'.
301+
custom-command-applet-exec = Executing custom command oma-{ $subcmd } ...
302+
custom-command-applet-exception = Custom command exited with error { $s }.

i18n/zh-CN/oma.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,6 @@ not-allow-delete-using-kernel = 不允许删除正在使用的 Linux 内核版
290290
yes-mode-conflict-ui = UI 模式下将不会应用 `--yes` 模式。
291291
oma-refresh-no-metadata-to-download = 源配置中没有适用的软件包元数据。
292292
delete-current-kernel-tips = { $kernel } 为正在使用的内核,移除后可能导致系统无法工作。
293+
custom-command-unknown = 未知命令:`{ $subcmd }'。
294+
custom-command-applet-exec = 正在执行自定义命令 oma-{ $subcmd } ...
295+
custom-command-applet-exception = 自定义命令异常退出,错误码:{ $s }

src/args.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::env;
1+
use std::{env, ffi::OsStr, path::Path};
22

3-
use clap::{Args, Parser, Subcommand, crate_name, crate_version};
3+
use clap::{Arg, Args, Command, Parser, Subcommand, crate_name, crate_version};
44
use enum_dispatch::enum_dispatch;
5+
use itertools::Itertools;
56

67
use crate::{
78
GlobalOptions,
@@ -42,7 +43,15 @@ pub(crate) trait CliExecuter {
4243
}
4344

4445
#[derive(Parser, Debug)]
45-
#[command(version, about, long_about = None, disable_version_flag = true, max_term_width = 80, after_help = after_help())]
46+
#[command(
47+
version,
48+
about,
49+
long_about = None,
50+
disable_version_flag = true,
51+
max_term_width = 80,
52+
after_help = after_help(),
53+
subcommands = custom_subcmds()
54+
)]
4655
pub struct OhManagerAilurus {
4756
#[command(flatten)]
4857
pub global: GlobalOptions,
@@ -152,6 +161,58 @@ fn after_help() -> &'static str {
152161
}
153162
}
154163

164+
fn custom_subcmds() -> Vec<Command> {
165+
let plugins = list_helpers();
166+
if let Ok(plugins) = plugins {
167+
plugins
168+
.iter()
169+
.map(|plugin| {
170+
let name = plugin.strip_prefix("oma-").unwrap_or("???");
171+
Command::new(name.to_string())
172+
.arg(
173+
Arg::new("COMMANDS")
174+
.required(false)
175+
.num_args(1..)
176+
.help("Applet specific commands"),
177+
)
178+
.about("")
179+
})
180+
.collect()
181+
} else {
182+
vec![]
183+
}
184+
}
185+
186+
fn list_helpers() -> Result<Vec<String>, anyhow::Error> {
187+
let mut plugins_dir: Box<dyn Iterator<Item = _>> =
188+
Box::new(Path::new("/usr/libexec").read_dir()?);
189+
190+
let plugins_local_dir = Path::new("/usr/local/libexec").read_dir();
191+
192+
if let Ok(plugins_local_dir) = plugins_local_dir {
193+
plugins_dir = Box::new(plugins_dir.chain(plugins_local_dir));
194+
}
195+
196+
let plugins = plugins_dir
197+
.filter_map(|x| {
198+
if let Ok(x) = x {
199+
let path = x.path();
200+
let filename = path
201+
.file_name()
202+
.unwrap_or_else(|| OsStr::new(""))
203+
.to_string_lossy();
204+
if path.is_file() && filename.starts_with("oma-") {
205+
return Some(filename.to_string());
206+
}
207+
}
208+
None
209+
})
210+
.unique()
211+
.collect();
212+
213+
Ok(plugins)
214+
}
215+
155216
#[test]
156217
fn test() {
157218
use clap::CommandFactory;

src/main.rs

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs::{create_dir_all, read_dir, remove_file};
44
use std::io::{self, IsTerminal, stderr, stdin};
55
use std::path::{Path, PathBuf};
66

7-
use std::process::exit;
7+
use std::process::{Command, exit};
88
use std::sync::{LazyLock, OnceLock};
99
use std::thread;
1010
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -23,7 +23,7 @@ mod utils;
2323

2424
use args::{CliExecuter, OhManagerAilurus, print_version};
2525
use clap::builder::FalseyValueParser;
26-
use clap::{ArgAction, Args, ColorChoice, CommandFactory, Parser};
26+
use clap::{ArgAction, ArgMatches, Args, ColorChoice, CommandFactory, FromArgMatches};
2727
use clap_complete::CompleteEnv;
2828
use error::OutputError;
2929
use i18n_embed::{DesktopLanguageRequester, Localizer};
@@ -152,7 +152,8 @@ fn main() {
152152

153153
ctrlc::set_handler(single_handler).expect("oma could not initialize SIGINT handler.");
154154

155-
let oma = OhManagerAilurus::parse();
155+
// 要适配额外的插件子命令,所以这里要保留 matches
156+
let (matches, oma) = parse_args();
156157

157158
if oma.global.version {
158159
print_version();
@@ -185,7 +186,7 @@ fn main() {
185186
}
186187
}
187188

188-
let code = match try_main(oma, &config) {
189+
let code = match try_main(oma, config, matches) {
189190
Ok(exit_code) => {
190191
unlock_oma().ok();
191192
exit_code
@@ -208,6 +209,19 @@ fn main() {
208209
exit(code);
209210
}
210211

212+
fn parse_args() -> (ArgMatches, OhManagerAilurus) {
213+
let matches = OhManagerAilurus::command().get_matches();
214+
let oma = match OhManagerAilurus::from_arg_matches(&matches).map_err(|e| {
215+
let mut cmd = OhManagerAilurus::command();
216+
e.format(&mut cmd)
217+
}) {
218+
Ok(oma) => oma,
219+
Err(e) => e.exit(),
220+
};
221+
222+
(matches, oma)
223+
}
224+
211225
fn init_localizer() {
212226
let localizer = crate::lang::localizer();
213227
let requested_languages = DesktopLanguageRequester::requested_languages();
@@ -371,15 +385,47 @@ fn enable_ansi(oma: &OhManagerAilurus) -> bool {
371385
|| oma.global.color == ColorChoice::Always
372386
}
373387

374-
fn try_main(oma: OhManagerAilurus, config: &Config) -> Result<i32, OutputError> {
375-
init_color_formatter(&oma, config);
388+
fn try_main(
389+
oma: OhManagerAilurus,
390+
config: Config,
391+
matches: ArgMatches,
392+
) -> Result<i32, OutputError> {
393+
init_color_formatter(&oma, &config);
376394

377395
let no_progress =
378396
oma.global.no_progress || !is_terminal() || oma.global.debug || oma.global.dry_run;
379397

380398
let code = match oma.subcmd {
381-
Some(subcmd) => subcmd.execute(config, no_progress),
382-
None => Tui::from(&oma.global).execute(config, no_progress),
399+
Some(subcmd) => subcmd.execute(&config, no_progress),
400+
None => {
401+
if let Some((subcommand, args)) = matches.subcommand() {
402+
let mut plugin = Path::new("/usr/local/libexec").join(format!("oma-{subcommand}"));
403+
let plugin_fallback = Path::new("/usr/libexec").join(format!("oma-{subcommand}"));
404+
405+
if !plugin.is_file() {
406+
plugin = plugin_fallback;
407+
if !plugin.is_file() {
408+
error!("{}", fl!("custom-command-unknown", subcmd = subcommand));
409+
return Ok(1);
410+
}
411+
}
412+
413+
info!("{}", fl!("custom-command-applet-exec", subcmd = subcommand));
414+
let mut process = &mut Command::new(plugin);
415+
if let Some(args) = args.get_many::<String>("COMMANDS") {
416+
process = process.args(args);
417+
}
418+
419+
let status = process.status().unwrap().code().unwrap();
420+
if status != 0 {
421+
error!("{}", fl!("custom-command-applet-exception", s = status));
422+
}
423+
424+
Ok(status)
425+
} else {
426+
Tui::from(&oma.global).execute(&config, no_progress)
427+
}
428+
}
383429
};
384430

385431
if !oma.global.no_bell && config.bell() {

0 commit comments

Comments
 (0)