use crate::core::{ExecutionContext, InvocationArgs};
use std::fmt::Debug;
use std::{option::Option, sync::Arc};
use super::strategy::FallbackSubcommandStrategy;
use super::{
CommandStrategy, FunctionStrategy, StrategyError, SubcommandCatalog, SubcommandRouter,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SwitchDefinition {
pub name: String,
pub description: String,
pub aliases: Vec<String>,
}
impl SwitchDefinition {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
aliases: Vec::new(),
}
}
pub fn with_aliases(mut self, aliases: Vec<String>) -> Self {
self.aliases = aliases;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArgumentDefinition {
pub name: String,
pub description: String,
pub aliases: Vec<String>,
pub required: bool,
pub value_type: ValueType,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ValueType {
String,
Bool,
Float,
Int,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ArgumentValue {
String(String),
Int(i64),
Bool(bool),
Float(f64),
}
impl Eq for ArgumentValue {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Argument {
pub name: String,
pub value: ArgumentValue,
}
impl ArgumentDefinition {
pub fn new(
name: impl Into<String>,
description: impl Into<String>,
value_type: ValueType,
) -> Self {
Self {
name: name.into(),
description: description.into(),
aliases: Vec::new(),
required: false,
value_type,
}
}
pub fn set_value(&self, value: ArgumentValue) -> Argument {
Argument {
name: self.name.clone(),
value,
}
}
pub fn with_aliases(mut self, aliases: Vec<impl Into<String>>) -> Self {
self.aliases = aliases.into_iter().map(|s| s.into()).collect();
self
}
pub fn set_required(mut self) -> Self {
self.required = true;
self
}
}
#[derive(Clone)]
pub struct CommandMetaData {
pub name: String,
pub description: String,
pub usage: Option<String>,
pub long_description: Option<String>,
pub examples: Vec<String>,
pub options: Vec<SwitchDefinition>,
pub arguments: Vec<ArgumentDefinition>,
pub aliases: Vec<String>,
}
impl CommandMetaData {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
usage: None,
long_description: None,
examples: Vec::new(),
options: Vec::new(),
arguments: Vec::new(),
aliases: Vec::new(),
}
}
pub fn with_usage(mut self, usage: impl Into<String>) -> Self {
self.usage = Some(usage.into());
self
}
pub fn with_long_description(mut self, long_description: impl Into<String>) -> Self {
self.long_description = Some(long_description.into());
self
}
pub fn with_examples(mut self, examples: Vec<String>) -> Self {
self.examples = examples;
self
}
pub fn with_options(mut self, options: Vec<SwitchDefinition>) -> Self {
self.options = options;
self
}
pub fn with_arguments(mut self, arguments: Vec<ArgumentDefinition>) -> Self {
self.arguments = arguments;
self
}
pub fn with_aliases(mut self, aliases: Vec<String>) -> Self {
self.aliases = aliases;
self
}
}
#[derive(Clone)]
pub struct Command {
pub metadata: CommandMetaData,
strategy: Arc<dyn CommandStrategy>,
}
impl Command {
pub fn new<S>(name: impl Into<String>, description: impl Into<String>, strategy: S) -> Self
where
S: CommandStrategy + 'static,
{
Self {
metadata: CommandMetaData::new(name, description),
strategy: Arc::new(strategy),
}
}
pub(crate) fn execute(
&self,
context: &ExecutionContext,
mut invocation: InvocationArgs,
) -> Result<(), StrategyError> {
match invocation.subcommand.take() {
Some(subcommand) => self
.resolve_subcommand(&subcommand.name)
.ok_or_else(|| {
StrategyError::invalid_arguments(format!(
"unknown subcommand '{}'",
subcommand.name
))
})?
.execute(context, *subcommand),
None => self.strategy.execute(context, invocation),
}
}
pub fn subcommand_catalog(&self) -> Option<&dyn SubcommandCatalog> {
self.strategy.subcommand_catalog()
}
pub fn from_fn<F>(name: impl Into<String>, description: impl Into<String>, runner: F) -> Self
where
F: Fn(&ExecutionContext, InvocationArgs) -> Result<(), StrategyError>
+ Send
+ Sync
+ 'static,
{
Self::new(name, description, FunctionStrategy::new(runner))
}
pub fn from_fn_with_context<F>(
name: impl Into<String>,
description: impl Into<String>,
runner: F,
) -> Self
where
F: Fn(&ExecutionContext, InvocationArgs) -> Result<(), StrategyError>
+ Send
+ Sync
+ 'static,
{
Self::from_fn(name, description, runner)
}
pub fn builder(name: impl Into<String>, description: impl Into<String>) -> CommandBuilder {
CommandBuilder::new(name, description)
}
pub(crate) fn resolve_subcommand(&self, token: &str) -> Option<Command> {
self.subcommand_catalog().and_then(|catalog| {
catalog.subcommands().into_iter().find(|command| {
command.metadata.name == token
|| command.metadata.aliases.iter().any(|alias| alias == token)
})
})
}
}
pub fn command(name: impl Into<String>, description: impl Into<String>) -> CommandBuilder {
CommandBuilder::new(name, description)
}
pub fn argument(name: impl Into<String>, description: impl Into<String>) -> ArgumentDefinition {
ArgumentDefinition::new(name, description, ValueType::String)
}
pub fn argument_of_type(
name: impl Into<String>,
description: impl Into<String>,
value_type: ValueType,
) -> ArgumentDefinition {
ArgumentDefinition::new(name, description, value_type)
}
pub fn switch(name: impl Into<String>, description: impl Into<String>) -> SwitchDefinition {
SwitchDefinition::new(name, description)
}
pub struct CommandBuilder {
metadata: CommandMetaData,
strategy: Option<Arc<dyn CommandStrategy>>,
subcommands: Vec<Command>,
}
impl CommandBuilder {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
metadata: CommandMetaData::new(name, description),
strategy: None,
subcommands: Vec::new(),
}
}
pub fn handler<S>(mut self, strategy: S) -> Self
where
S: CommandStrategy + 'static,
{
self.strategy = Some(Arc::new(strategy));
self
}
pub fn handler_fn<F>(mut self, runner: F) -> Self
where
F: Fn(&ExecutionContext, InvocationArgs) -> Result<(), StrategyError>
+ Send
+ Sync
+ 'static,
{
self.strategy = Some(Arc::new(FunctionStrategy::new(runner)));
self
}
pub fn handler_fn_with_context<F>(mut self, runner: F) -> Self
where
F: Fn(&ExecutionContext, InvocationArgs) -> Result<(), StrategyError>
+ Send
+ Sync
+ 'static,
{
self.strategy = Some(Arc::new(FunctionStrategy::new(runner)));
self
}
pub fn subcommand<C>(mut self, subcommand: C) -> Self
where
C: Into<Command>,
{
self.subcommands.push(subcommand.into());
self
}
pub fn with_usage(mut self, usage: impl Into<String>) -> Self {
self.metadata = self.metadata.with_usage(usage);
self
}
pub fn with_long_description(mut self, long_description: impl Into<String>) -> Self {
self.metadata = self.metadata.with_long_description(long_description);
self
}
pub fn with_examples(mut self, examples: Vec<String>) -> Self {
self.metadata = self.metadata.with_examples(examples);
self
}
pub fn with_options(mut self, options: Vec<SwitchDefinition>) -> Self {
self.metadata = self.metadata.with_options(options);
self
}
pub fn with_arguments(mut self, arguments: Vec<ArgumentDefinition>) -> Self {
self.metadata = self.metadata.with_arguments(arguments);
self
}
pub fn with_aliases(mut self, aliases: Vec<impl Into<String>>) -> Self {
self.metadata = self
.metadata
.with_aliases(aliases.into_iter().map(|s| s.into()).collect());
self
}
pub fn build(self) -> Command {
let strategy: Arc<dyn CommandStrategy> = if self.subcommands.is_empty() {
self.strategy.unwrap_or_else(|| {
Arc::new(FunctionStrategy::new(|_, _| {
Err(StrategyError::internal(
"command has no handler; configure a handler or subcommand",
))
}))
})
} else {
let mut router = SubcommandRouter::new();
for subcommand in self.subcommands {
router.register_mut(subcommand);
}
match self.strategy {
Some(fallback) => Arc::new(FallbackSubcommandStrategy::new(fallback, router)),
None => Arc::new(router),
}
};
Command {
metadata: self.metadata,
strategy,
}
}
}
impl From<CommandBuilder> for Command {
fn from(value: CommandBuilder) -> Self {
value.build()
}
}