diff --git a/src/admin/handler.rs b/src/admin/handler.rs index a7e4d79a18e1e6ff41740297be21a01899dfc94a..738d748c7fc0b36f4ca6b2ba21e7cbd241906891 100644 --- a/src/admin/handler.rs +++ b/src/admin/handler.rs @@ -1,7 +1,7 @@ use std::{panic::AssertUnwindSafe, sync::Arc, time::Instant}; use clap::{CommandFactory, Parser}; -use conduit::{error, trace, utils::string::common_prefix, Error, Result}; +use conduit::{checked, error, trace, utils::string::common_prefix, Error, Result}; use futures_util::future::FutureExt; use ruma::{ events::{ @@ -60,8 +60,11 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) - // Parse and process a message from the admin room async fn process(services: Arc<Services>, msg: &str) -> CommandOutput { - let mut lines = msg.lines().filter(|l| !l.trim().is_empty()); - let command = lines.next().expect("each string has at least one line"); + let lines = msg.lines().filter(|l| !l.trim().is_empty()); + let command = lines + .clone() + .next() + .expect("each string has at least one line"); let (parsed, body) = match parse_command(command) { Ok(parsed) => parsed, Err(error) => { @@ -71,12 +74,12 @@ async fn process(services: Arc<Services>, msg: &str) -> CommandOutput { }, }; - let timer = Instant::now(); - let body: Vec<&str> = body.iter().map(String::as_str).collect(); + let body = parse_body(AdminCommand::command(), &body, lines.skip(1).collect()).expect("trailing body parsed"); let context = Command { services: &services, body: &body, }; + let timer = Instant::now(); let result = Box::pin(admin::process(parsed, &context)).await; let elapsed = timer.elapsed(); conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}"); @@ -95,6 +98,32 @@ fn parse_command(command_line: &str) -> Result<(AdminCommand, Vec<String>), Stri Ok((com, argv)) } +fn parse_body<'a>(mut cmd: clap::Command, body: &'a [String], lines: Vec<&'a str>) -> Result<Vec<&'a str>> { + let mut start = 1; + 'token: for token in body.iter().skip(1) { + let cmd_ = cmd.clone(); + for sub in cmd_.get_subcommands() { + if sub.get_name() == *token { + start = checked!(start + 1)?; + cmd = sub.clone(); + continue 'token; + } + } + + // positional arguments have to be skipped too + let num_posargs = cmd_.get_positionals().count(); + start = checked!(start + num_posargs)?; + break; + } + + Ok(body + .iter() + .skip(start) + .map(String::as_str) + .chain(lines) + .collect::<Vec<&'a str>>()) +} + fn complete_command(mut cmd: clap::Command, line: &str) -> String { let argv = parse_line(line); let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));