Skip to content

Commit

Permalink
Merge pull request #5766 from epage/completer
Browse files Browse the repository at this point in the history
feat: Add CommandExt support
  • Loading branch information
epage authored Oct 2, 2024
2 parents 81b9db2 + a767a97 commit d2874a5
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 65 deletions.
35 changes: 32 additions & 3 deletions clap_builder/src/builder/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::path::Path;
// Internal
use crate::builder::app_settings::{AppFlags, AppSettings};
use crate::builder::arg_settings::ArgSettings;
use crate::builder::ext::Extension;
use crate::builder::ext::Extensions;
use crate::builder::ArgAction;
use crate::builder::IntoResettable;
Expand Down Expand Up @@ -106,6 +107,8 @@ pub struct Command {
external_value_parser: Option<super::ValueParser>,
long_help_exists: bool,
deferred: Option<fn(Command) -> Command>,
#[cfg(feature = "unstable-ext")]
ext: Extensions,
app_ext: Extensions,
}

Expand Down Expand Up @@ -1039,6 +1042,14 @@ impl Command {

Usage::new(self).create_usage_with_title(&[])
}

/// Extend [`Command`] with [`CommandExt`] data
#[cfg(feature = "unstable-ext")]
#[allow(clippy::should_implement_trait)]
pub fn add<T: CommandExt + Extension>(mut self, tagged: T) -> Self {
self.ext.set(tagged);
self
}
}

/// # Application-wide Settings
Expand Down Expand Up @@ -3964,6 +3975,18 @@ impl Command {
pub fn is_multicall_set(&self) -> bool {
self.is_set(AppSettings::Multicall)
}

/// Access an [`CommandExt`]
#[cfg(feature = "unstable-ext")]
pub fn get<T: CommandExt + Extension>(&self) -> Option<&T> {
self.ext.get::<T>()
}

/// Remove an [`CommandExt`]
#[cfg(feature = "unstable-ext")]
pub fn remove<T: CommandExt + Extension>(mut self) -> Option<T> {
self.ext.remove::<T>()
}
}

// Internally used only
Expand Down Expand Up @@ -4883,6 +4906,8 @@ impl Default for Command {
external_value_parser: Default::default(),
long_help_exists: false,
deferred: None,
#[cfg(feature = "unstable-ext")]
ext: Default::default(),
app_ext: Default::default(),
}
}
Expand All @@ -4908,20 +4933,24 @@ impl fmt::Display for Command {
}
}

/// User-provided data that can be attached to an [`Arg`]
#[cfg(feature = "unstable-ext")]
pub trait CommandExt: Extension {}

#[allow(dead_code)] // atm dependent on features enabled
pub(crate) trait AppTag: crate::builder::ext::Extension {}
pub(crate) trait AppExt: Extension {}

#[allow(dead_code)] // atm dependent on features enabled
#[derive(Default, Copy, Clone, Debug)]
struct TermWidth(usize);

impl AppTag for TermWidth {}
impl AppExt for TermWidth {}

#[allow(dead_code)] // atm dependent on features enabled
#[derive(Default, Copy, Clone, Debug)]
struct MaxTermWidth(usize);

impl AppTag for MaxTermWidth {}
impl AppExt for MaxTermWidth {}

fn two_elements_of<I, T>(mut iter: I) -> Option<(T, T)>
where
Expand Down
6 changes: 4 additions & 2 deletions clap_builder/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub use arg::ArgExt;
pub use arg_group::ArgGroup;
pub use arg_predicate::ArgPredicate;
pub use command::Command;
#[cfg(feature = "unstable-ext")]
pub use command::CommandExt;
pub use os_str::OsStr;
pub use possible_value::PossibleValue;
pub use range::ValueRange;
Expand All @@ -41,9 +43,9 @@ pub use resettable::Resettable;
pub use styled_str::StyledStr;
pub use styling::Styles;
pub use value_hint::ValueHint;
pub use value_parser::BoolValueParser;
pub use value_parser::_infer_ValueParser_for;
pub use value_parser::impl_prelude;
pub use value_parser::BoolValueParser;
pub use value_parser::BoolishValueParser;
pub use value_parser::EnumValueParser;
pub use value_parser::FalseyValueParser;
Expand All @@ -66,4 +68,4 @@ pub use value_parser::_AnonymousValueParser;
pub(crate) use self::str::Inner as StrInner;
pub(crate) use action::CountType;
pub(crate) use arg_settings::{ArgFlags, ArgSettings};
pub(crate) use command::AppTag;
pub(crate) use command::AppExt;
2 changes: 1 addition & 1 deletion clap_builder/src/builder/styling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl Styles {
}
}

impl super::AppTag for Styles {}
impl super::AppExt for Styles {}

impl Default for Styles {
fn default() -> Self {
Expand Down
118 changes: 59 additions & 59 deletions clap_complete/src/engine/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,146 +7,146 @@ use clap_lex::OsStrExt as _;

use super::CompletionCandidate;

/// Extend [`Arg`][clap::Arg] with a [`ValueCandidates`]
/// Extend [`Arg`][clap::Arg] with a completer
///
/// # Example
///
/// ```rust
/// use clap::Parser;
/// use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
/// use clap_complete::engine::{ArgValueCompleter, CompletionCandidate};
///
/// fn custom_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
/// let mut completions = vec![];
/// let Some(current) = current.to_str() else {
/// return completions;
/// };
///
/// if "foo".starts_with(current) {
/// completions.push(CompletionCandidate::new("foo"));
/// }
/// if "bar".starts_with(current) {
/// completions.push(CompletionCandidate::new("bar"));
/// }
/// if "baz".starts_with(current) {
/// completions.push(CompletionCandidate::new("baz"));
/// }
/// completions
/// }
///
/// #[derive(Debug, Parser)]
/// struct Cli {
/// #[arg(long, add = ArgValueCandidates::new(|| { vec![
/// CompletionCandidate::new("foo"),
/// CompletionCandidate::new("bar"),
/// CompletionCandidate::new("baz")] }))]
/// #[arg(long, add = ArgValueCompleter::new(custom_completer))]
/// custom: Option<String>,
/// }
/// ```
#[derive(Clone)]
pub struct ArgValueCandidates(Arc<dyn ValueCandidates>);
pub struct ArgValueCompleter(Arc<dyn ValueCompleter>);

impl ArgValueCandidates {
/// Create a new `ArgValueCandidates` with a custom completer
impl ArgValueCompleter {
/// Create a new `ArgValueCompleter` with a custom completer
pub fn new<C>(completer: C) -> Self
where
C: ValueCandidates + 'static,
C: ValueCompleter + 'static,
{
Self(Arc::new(completer))
}

/// All potential candidates for an argument.
/// Candidates that match `current`
///
/// See [`CompletionCandidate`] for more information.
pub fn candidates(&self) -> Vec<CompletionCandidate> {
self.0.candidates()
pub fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
self.0.complete(current)
}
}

impl std::fmt::Debug for ArgValueCandidates {
impl std::fmt::Debug for ArgValueCompleter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(type_name::<Self>())
}
}

impl ArgExt for ArgValueCandidates {}
impl ArgExt for ArgValueCompleter {}

/// User-provided completion candidates for an [`Arg`][clap::Arg], see [`ArgValueCandidates`]
/// User-provided completion candidates for an [`Arg`][clap::Arg], see [`ArgValueCompleter`]
///
/// This is useful when predefined value hints are not enough.
pub trait ValueCandidates: Send + Sync {
pub trait ValueCompleter: Send + Sync {
/// All potential candidates for an argument.
///
/// See [`CompletionCandidate`] for more information.
fn candidates(&self) -> Vec<CompletionCandidate>;
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate>;
}

impl<F> ValueCandidates for F
impl<F> ValueCompleter for F
where
F: Fn() -> Vec<CompletionCandidate> + Send + Sync,
F: Fn(&OsStr) -> Vec<CompletionCandidate> + Send + Sync,
{
fn candidates(&self) -> Vec<CompletionCandidate> {
self()
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
self(current)
}
}

/// Extend [`Arg`][clap::Arg] with a completer
/// Extend [`Arg`][clap::Arg] with a [`ValueCandidates`]
///
/// # Example
///
/// ```rust
/// use clap::Parser;
/// use clap_complete::engine::{ArgValueCompleter, CompletionCandidate};
///
/// fn custom_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
/// let mut completions = vec![];
/// let Some(current) = current.to_str() else {
/// return completions;
/// };
///
/// if "foo".starts_with(current) {
/// completions.push(CompletionCandidate::new("foo"));
/// }
/// if "bar".starts_with(current) {
/// completions.push(CompletionCandidate::new("bar"));
/// }
/// if "baz".starts_with(current) {
/// completions.push(CompletionCandidate::new("baz"));
/// }
/// completions
/// }
/// use clap_complete::engine::{ArgValueCandidates, CompletionCandidate};
///
/// #[derive(Debug, Parser)]
/// struct Cli {
/// #[arg(long, add = ArgValueCompleter::new(custom_completer))]
/// #[arg(long, add = ArgValueCandidates::new(|| { vec![
/// CompletionCandidate::new("foo"),
/// CompletionCandidate::new("bar"),
/// CompletionCandidate::new("baz")] }))]
/// custom: Option<String>,
/// }
/// ```
#[derive(Clone)]
pub struct ArgValueCompleter(Arc<dyn ValueCompleter>);
pub struct ArgValueCandidates(Arc<dyn ValueCandidates>);

impl ArgValueCompleter {
/// Create a new `ArgValueCompleter` with a custom completer
impl ArgValueCandidates {
/// Create a new `ArgValueCandidates` with a custom completer
pub fn new<C>(completer: C) -> Self
where
C: ValueCompleter + 'static,
C: ValueCandidates + 'static,
{
Self(Arc::new(completer))
}

/// Candidates that match `current`
/// All potential candidates for an argument.
///
/// See [`CompletionCandidate`] for more information.
pub fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
self.0.complete(current)
pub fn candidates(&self) -> Vec<CompletionCandidate> {
self.0.candidates()
}
}

impl std::fmt::Debug for ArgValueCompleter {
impl std::fmt::Debug for ArgValueCandidates {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(type_name::<Self>())
}
}

impl ArgExt for ArgValueCompleter {}
impl ArgExt for ArgValueCandidates {}

/// User-provided completion candidates for an [`Arg`][clap::Arg], see [`ArgValueCompleter`]
/// User-provided completion candidates for an [`Arg`][clap::Arg], see [`ArgValueCandidates`]
///
/// This is useful when predefined value hints are not enough.
pub trait ValueCompleter: Send + Sync {
pub trait ValueCandidates: Send + Sync {
/// All potential candidates for an argument.
///
/// See [`CompletionCandidate`] for more information.
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate>;
fn candidates(&self) -> Vec<CompletionCandidate>;
}

impl<F> ValueCompleter for F
impl<F> ValueCandidates for F
where
F: Fn(&OsStr) -> Vec<CompletionCandidate> + Send + Sync,
F: Fn() -> Vec<CompletionCandidate> + Send + Sync,
{
fn complete(&self, current: &OsStr) -> Vec<CompletionCandidate> {
self(current)
fn candidates(&self) -> Vec<CompletionCandidate> {
self()
}
}

Expand Down

0 comments on commit d2874a5

Please sign in to comment.