use std::collections::BTreeMap;
use std::sync::Arc;
use anyhow::Result;
use clap::Command;
use crate::completion::CommandSpec;
use crate::config::ResolvedConfig;
use crate::core::command_policy::CommandPolicyRegistry;
use crate::core::plugin::{DescribeCommandAuthV1, DescribeCommandV1, ResponseV1};
use crate::core::runtime::RuntimeHints;
#[derive(Debug, Clone)]
pub struct NativeCommandCatalogEntry {
pub name: String,
pub about: String,
pub auth: Option<DescribeCommandAuthV1>,
pub subcommands: Vec<String>,
pub completion: CommandSpec,
}
pub struct NativeCommandContext<'a> {
pub config: &'a ResolvedConfig,
pub runtime_hints: RuntimeHints,
}
impl<'a> NativeCommandContext<'a> {
pub fn new(config: &'a ResolvedConfig, runtime_hints: RuntimeHints) -> Self {
Self {
config,
runtime_hints,
}
}
}
pub enum NativeCommandOutcome {
Help(String),
Response(Box<ResponseV1>),
Exit(i32),
}
pub trait NativeCommand: Send + Sync {
fn command(&self) -> Command;
fn auth(&self) -> Option<DescribeCommandAuthV1> {
None
}
fn describe(&self) -> DescribeCommandV1 {
let mut describe = DescribeCommandV1::from_clap(self.command());
describe.auth = self.auth();
describe
}
fn execute(
&self,
args: &[String],
context: &NativeCommandContext<'_>,
) -> Result<NativeCommandOutcome>;
}
#[derive(Clone, Default)]
#[must_use]
pub struct NativeCommandRegistry {
commands: Arc<BTreeMap<String, Arc<dyn NativeCommand>>>,
}
impl NativeCommandRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn with_command(mut self, command: impl NativeCommand + 'static) -> Self {
self.register(command);
self
}
pub fn register(&mut self, command: impl NativeCommand + 'static) {
let mut next = (*self.commands).clone();
let command = Arc::new(command) as Arc<dyn NativeCommand>;
let name = normalize_name(&command.describe().name);
next.insert(name, command);
self.commands = Arc::new(next);
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn command(&self, name: &str) -> Option<&Arc<dyn NativeCommand>> {
self.commands.get(&normalize_name(name))
}
pub fn catalog(&self) -> Vec<NativeCommandCatalogEntry> {
self.commands
.values()
.map(|command| {
let describe = command.describe();
let completion = crate::plugin::conversion::to_command_spec(&describe);
NativeCommandCatalogEntry {
name: describe.name.clone(),
about: describe.about.clone(),
auth: describe.auth.clone(),
subcommands: crate::plugin::conversion::direct_subcommand_names(&completion),
completion,
}
})
.collect()
}
pub fn command_policy_registry(&self) -> CommandPolicyRegistry {
let mut registry = CommandPolicyRegistry::new();
for command in self.commands.values() {
let describe = command.describe();
register_describe_command_policies(&mut registry, &describe, &[]);
}
registry
}
}
fn register_describe_command_policies(
registry: &mut CommandPolicyRegistry,
command: &DescribeCommandV1,
parent: &[String],
) {
let mut segments = parent.to_vec();
segments.push(command.name.clone());
if let Some(policy) = command.command_policy(crate::core::command_policy::CommandPath::new(
segments.clone(),
)) {
registry.register(policy);
}
for subcommand in &command.subcommands {
register_describe_command_policies(registry, subcommand, &segments);
}
}
fn normalize_name(value: &str) -> String {
value.trim().to_ascii_lowercase()
}
#[cfg(test)]
mod tests;