Skip to main content

steel_core/command/brigadier/
builder.rs

1//! Command node builders.
2
3use super::{
4    ArgumentType, BrigadierRuntime, CommandContext, CommandRequirement, CommandRuntime,
5    CommandSyntaxError, NodeId, RegistrationError, RegistrationErrorKind, SuggestionProvider,
6    node::{
7        CommandNodeData, CommandRedirect, CommandRedirectTarget, UnregisteredCommandNode,
8        merge_or_push,
9    },
10    runtime::{BrigadierExecutor, BrigadierModifier},
11};
12use crate::command::brigadier::node::ArgumentData;
13use std::sync::Arc;
14
15/// Builds one literal or argument command node and its descendants.
16pub(crate) struct CommandNodeBuilder<S, R = BrigadierRuntime>
17where
18    R: CommandRuntime<S>,
19{
20    data: CommandNodeData<S, R::Argument>,
21    children: Vec<Self>,
22    executor: Option<Arc<R::Executor>>,
23    requirement: CommandRequirement<S>,
24    execution_requirement: CommandRequirement<S>,
25    redirect: Option<CommandRedirect<S, R>>,
26}
27
28/// Requirements for traversing a scoped route and executing its current node.
29pub(crate) struct CommandRequirementRoute<S> {
30    traversal: CommandRequirement<S>,
31    execution: CommandRequirement<S>,
32}
33
34impl<S> CommandRequirementRoute<S> {
35    /// Creates the requirements for one resolved route through a command tree.
36    pub(crate) const fn new(
37        traversal: CommandRequirement<S>,
38        execution: CommandRequirement<S>,
39    ) -> Self {
40        Self {
41            traversal,
42            execution,
43        }
44    }
45}
46
47impl<S> Clone for CommandRequirementRoute<S> {
48    fn clone(&self) -> Self {
49        Self {
50            traversal: self.traversal.clone(),
51            execution: self.execution.clone(),
52        }
53    }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
57struct RequirementRouteKey {
58    governing_scope: Option<usize>,
59    descendant_scopes: Vec<usize>,
60}
61
62#[derive(Clone, Copy)]
63struct ActiveRequirementScope<'path> {
64    index: usize,
65    remaining: &'path [Box<str>],
66}
67
68impl<S, R> Clone for CommandNodeBuilder<S, R>
69where
70    R: CommandRuntime<S>,
71    R::Argument: Clone,
72{
73    fn clone(&self) -> Self {
74        Self {
75            data: self.data.clone(),
76            children: self.children.clone(),
77            executor: self.executor.as_ref().map(Arc::clone),
78            requirement: self.requirement.clone(),
79            execution_requirement: self.execution_requirement.clone(),
80            redirect: self.redirect.clone(),
81        }
82    }
83}
84
85/// Creates a literal using the standard synchronous Brigadier runtime.
86pub(crate) fn literal<S>(name: impl Into<Box<str>>) -> CommandNodeBuilder<S> {
87    CommandNodeBuilder::literal(name)
88}
89
90/// Creates an argument using the standard synchronous Brigadier runtime.
91pub(crate) fn argument<S>(
92    name: impl Into<Box<str>>,
93    argument_type: ArgumentType,
94) -> CommandNodeBuilder<S> {
95    CommandNodeBuilder::argument(name, argument_type)
96}
97
98impl<S, R> CommandNodeBuilder<S, R>
99where
100    R: CommandRuntime<S>,
101{
102    /// Creates a literal for this runtime model.
103    pub(crate) fn literal(name: impl Into<Box<str>>) -> Self {
104        Self {
105            data: CommandNodeData::Literal(name.into()),
106            children: Vec::new(),
107            executor: None,
108            requirement: CommandRequirement::allow_all(),
109            execution_requirement: CommandRequirement::allow_all(),
110            redirect: None,
111        }
112    }
113
114    /// Creates an argument for this runtime model.
115    pub(crate) fn argument(name: impl Into<Box<str>>, argument_type: R::Argument) -> Self {
116        Self {
117            data: CommandNodeData::Argument(name.into(), ArgumentData::new(argument_type)),
118            children: Vec::new(),
119            executor: None,
120            requirement: CommandRequirement::allow_all(),
121            execution_requirement: CommandRequirement::allow_all(),
122            redirect: None,
123        }
124    }
125
126    /// Add a custom [`SuggestionProvider`] to the corresponding argument
127    /// does nothing if the node isn't an argument
128    #[must_use]
129    pub(crate) fn suggests(
130        self,
131        suggestion: impl SuggestionProvider<S, R::Argument> + 'static,
132    ) -> Self {
133        self.suggests_arc(Arc::new(suggestion))
134    }
135
136    /// Add a custom [`SuggestionProvider`] wrap in an Arc to the corresponding argument
137    /// does nothing if the node isn't an argument
138    #[must_use]
139    pub(crate) fn suggests_arc(
140        mut self,
141        suggestion: Arc<impl SuggestionProvider<S, R::Argument> + 'static>,
142    ) -> Self {
143        debug_assert!(
144            matches!(self.data, CommandNodeData::Argument(_, _)),
145            "suggests must be called on an argument node"
146        );
147        if let CommandNodeData::Argument(_, data) = &mut self.data {
148            data.custom_suggestions = Some(suggestion);
149        }
150        self
151    }
152
153    /// Returns this node's literal name, or `None` for an argument node.
154    pub(crate) fn literal_name(&self) -> Option<&str> {
155        match &self.data {
156            CommandNodeData::Literal(name) => Some(name),
157            CommandNodeData::Root | CommandNodeData::Argument { .. } => None,
158        }
159    }
160
161    /// Replaces this node's literal name, returning `None` for an argument node.
162    pub(crate) fn with_literal_name(mut self, name: impl Into<Box<str>>) -> Option<Self> {
163        let CommandNodeData::Literal(literal) = &mut self.data else {
164            return None;
165        };
166        *literal = name.into();
167        Some(self)
168    }
169
170    /// Adds a child while preserving registration order.
171    #[must_use]
172    pub(crate) fn then(mut self, child: Self) -> Self {
173        self.children.push(child);
174        self
175    }
176
177    /// Attaches an executor payload without interpreting it.
178    #[must_use]
179    pub(crate) fn executes_with_executor(mut self, executor: Arc<R::Executor>) -> Self {
180        self.executor = Some(executor);
181        self
182    }
183
184    /// Replaces the allow-all requirement with `requirement`.
185    #[must_use]
186    pub(crate) fn requires(mut self, requirement: CommandRequirement<S>) -> Self {
187        self.requirement = requirement;
188        self
189    }
190
191    /// Adds a requirement while preserving any predicate already on this node.
192    #[must_use]
193    pub(crate) fn also_requires(mut self, requirement: CommandRequirement<S>) -> Self
194    where
195        S: 'static,
196    {
197        self.requirement = self.requirement.and(requirement);
198        self
199    }
200
201    /// Adds a requirement that applies only when this node's executor is selected.
202    #[must_use]
203    pub(crate) fn also_requires_execution(mut self, requirement: CommandRequirement<S>) -> Self
204    where
205        S: 'static,
206    {
207        self.execution_requirement = self.execution_requirement.and(requirement);
208        self
209    }
210
211    /// Returns the number of occurrences of one literal path below this node.
212    ///
213    /// Argument nodes do not consume path segments.
214    pub(crate) fn literal_path_match_count(&self, path: &[Box<str>]) -> usize {
215        let Some((name, remaining)) = path.split_first() else {
216            return 0;
217        };
218        let mut matches = 0;
219        for child in &self.children {
220            let Some(literal) = child.literal_name() else {
221                matches += child.literal_path_match_count(path);
222                continue;
223            };
224            if literal != name.as_ref() {
225                continue;
226            }
227            if remaining.is_empty() {
228                matches += 1;
229            } else {
230                matches += child.literal_path_match_count(remaining);
231            }
232        }
233        matches
234    }
235
236    /// Applies independently scoped requirements using literal-only paths.
237    ///
238    /// A descendant scope may traverse its ancestors, but it cannot execute an
239    /// ancestor unless the route's governing requirement also permits it.
240    pub(crate) fn apply_scoped_requirements(
241        &mut self,
242        scope_paths: &[Vec<Box<str>>],
243        mut requirements_for: impl FnMut(Option<usize>, &[usize]) -> CommandRequirementRoute<S>,
244    ) where
245        S: 'static,
246    {
247        let active = scope_paths
248            .iter()
249            .enumerate()
250            .map(|(index, path)| ActiveRequirementScope {
251                index,
252                remaining: path,
253            })
254            .collect::<Vec<_>>();
255        let mut cache = Vec::new();
256        self.apply_scoped_requirements_inner(
257            None,
258            &active,
259            None,
260            &mut cache,
261            &mut requirements_for,
262        );
263    }
264
265    fn apply_scoped_requirements_inner<F>(
266        &mut self,
267        inherited_scope: Option<usize>,
268        active: &[ActiveRequirementScope<'_>],
269        parent_route: Option<&RequirementRouteKey>,
270        cache: &mut Vec<(RequirementRouteKey, CommandRequirementRoute<S>)>,
271        requirements_for: &mut F,
272    ) where
273        S: 'static,
274        F: FnMut(Option<usize>, &[usize]) -> CommandRequirementRoute<S>,
275    {
276        let governing_scope = active
277            .iter()
278            .find(|scope| scope.remaining.is_empty())
279            .map_or(inherited_scope, |scope| Some(scope.index));
280        let descendant_scopes = active
281            .iter()
282            .filter(|scope| !scope.remaining.is_empty())
283            .map(|scope| scope.index)
284            .collect::<Vec<_>>();
285        let route = RequirementRouteKey {
286            governing_scope,
287            descendant_scopes,
288        };
289        let requirements = cached_route_requirements(cache, &route, requirements_for);
290
291        if parent_route != Some(&route) {
292            self.requirement = self.requirement.clone().and(requirements.traversal);
293        }
294        if !route.descendant_scopes.is_empty() && self.executor.is_some() {
295            self.execution_requirement = self
296                .execution_requirement
297                .clone()
298                .and(requirements.execution);
299        }
300
301        for child in &mut self.children {
302            let child_active = if let Some(literal) = child.literal_name() {
303                active
304                    .iter()
305                    .filter_map(|scope| {
306                        let (name, remaining) = scope.remaining.split_first()?;
307                        (name.as_ref() == literal).then_some(ActiveRequirementScope {
308                            index: scope.index,
309                            remaining,
310                        })
311                    })
312                    .collect::<Vec<_>>()
313            } else {
314                active
315                    .iter()
316                    .filter(|scope| {
317                        !scope.remaining.is_empty()
318                            && child.literal_path_match_count(scope.remaining) > 0
319                    })
320                    .copied()
321                    .collect::<Vec<_>>()
322            };
323            child.apply_scoped_requirements_inner(
324                governing_scope,
325                &child_active,
326                Some(&route),
327                cache,
328                requirements_for,
329            );
330        }
331    }
332
333    /// Redirects parsing to an existing node without transforming the source.
334    #[must_use]
335    pub(crate) fn redirects(mut self, target: impl Into<CommandRedirectTarget>) -> Self {
336        self.redirect = Some(CommandRedirect::identity(target.into()));
337        self
338    }
339
340    /// Redirects with an opaque runtime modifier payload.
341    #[must_use]
342    pub(crate) fn redirects_with_modifier(
343        mut self,
344        target: impl Into<CommandRedirectTarget>,
345        modifier: Arc<R::Modifier>,
346        forks: bool,
347    ) -> Self {
348        self.redirect = Some(CommandRedirect::with_modifier(
349            target.into(),
350            modifier,
351            forks,
352        ));
353        self
354    }
355
356    pub(super) fn normalize(self) -> Result<UnregisteredCommandNode<S, R>, RegistrationError> {
357        let mut children = Vec::new();
358        for child in self.children {
359            merge_or_push(&mut children, child.normalize()?)?;
360        }
361        if self.redirect.is_some() && !children.is_empty() {
362            return Err(RegistrationError::new(
363                RegistrationErrorKind::RedirectWithChildren {
364                    name: self.data.name().into(),
365                },
366            ));
367        }
368
369        Ok(UnregisteredCommandNode {
370            data: self.data,
371            children,
372            executor: self.executor,
373            requirement: self.requirement,
374            execution_requirement: self.execution_requirement,
375            redirect: self.redirect,
376        })
377    }
378}
379
380fn cached_route_requirements<S, F>(
381    cache: &mut Vec<(RequirementRouteKey, CommandRequirementRoute<S>)>,
382    route: &RequirementRouteKey,
383    requirements_for: &mut F,
384) -> CommandRequirementRoute<S>
385where
386    F: FnMut(Option<usize>, &[usize]) -> CommandRequirementRoute<S>,
387{
388    if let Some((_, requirements)) = cache.iter().find(|(cached, _)| cached == route) {
389        return requirements.clone();
390    }
391    let requirements = requirements_for(route.governing_scope, &route.descendant_scopes);
392    cache.push((route.clone(), requirements.clone()));
393    requirements
394}
395
396impl<S> CommandNodeBuilder<S, BrigadierRuntime> {
397    /// Attaches a standard synchronous command callback.
398    #[must_use]
399    pub(crate) fn executes(
400        self,
401        executor: impl Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync + 'static,
402    ) -> Self {
403        let executor: Arc<BrigadierExecutor<S>> = Arc::new(executor);
404        self.executes_with_executor(executor)
405    }
406
407    /// Redirects parsing and transforms the source once before continuing.
408    #[must_use]
409    pub(crate) fn redirects_with(
410        self,
411        target: NodeId,
412        modifier: impl Fn(&CommandContext<S>) -> Result<S, CommandSyntaxError> + Send + Sync + 'static,
413    ) -> Self {
414        let modifier: Arc<BrigadierModifier<S>> =
415            Arc::new(move |context| modifier(context).map(|source| vec![source]));
416        self.redirects_with_modifier(target, modifier, false)
417    }
418
419    /// Redirects parsing and expands one source into zero or more sources.
420    #[must_use]
421    pub(crate) fn forks(
422        self,
423        target: NodeId,
424        modifier: impl Fn(&CommandContext<S>) -> Result<Vec<S>, CommandSyntaxError>
425        + Send
426        + Sync
427        + 'static,
428    ) -> Self {
429        let modifier: Arc<BrigadierModifier<S>> = Arc::new(modifier);
430        self.redirects_with_modifier(target, modifier, true)
431    }
432}