aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/console/input.rs
blob: 9661c7add7dad4c0ee3aa284ae1f9f3aca4073ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
pub mod input_argument;
pub mod input_option;

pub use input_argument::*;
pub use input_option::*;

use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput;
use shirabe_external_packages::symfony::console::completion::completion_suggestions::{
    CompletionSuggestions, StringOrSuggestion,
};

/// PHP: `\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>`.
///
/// PHP closures are bound to the command instance (`$this`); commands cannot capture a handle
/// to themselves while `configure()` runs inside `new()`, so the bound command is passed in as
/// `this` at call time instead.
pub type SuggestedValuesClosure = Box<
    dyn Fn(
        &dyn crate::command::BaseCommand,
        &CompletionInput,
        &mut CompletionSuggestions,
    ) -> anyhow::Result<Vec<String>>,
>;

/// PHP: the `list<string>|\Closure(...)` union taken by the suggestedValues parameter of the
/// `Composer\Console\Input\InputArgument` / `InputOption` backport.
///
/// The closure returns `Vec<String>` rather than `list<string|Suggestion>`: every Composer
/// closure returns plain strings, and `complete` lifts them into suggestions. PHP's runtime
/// "Closure must return an array" LogicException is statically guaranteed by the type.
pub enum SuggestedValues {
    List(Vec<String>),
    Closure(SuggestedValuesClosure),
}

impl std::fmt::Debug for SuggestedValues {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SuggestedValues::List(values) => f.debug_tuple("List").field(values).finish(),
            SuggestedValues::Closure(_) => f.write_str("Closure(..)"),
        }
    }
}

impl SuggestedValues {
    /// Whether PHP's `[] !== $suggestedValues` is false, i.e. no suggestions were declared.
    pub(crate) fn is_empty(&self) -> bool {
        matches!(self, SuggestedValues::List(values) if values.is_empty())
    }

    /// The shared body of the `InputArgument::complete` / `InputOption::complete` backport.
    pub(crate) fn complete(
        &self,
        this: &dyn crate::command::BaseCommand,
        input: &CompletionInput,
        suggestions: &mut CompletionSuggestions,
    ) -> anyhow::Result<()> {
        let values = match self {
            SuggestedValues::List(values) => values.clone(),
            SuggestedValues::Closure(closure) => closure(this, input, suggestions)?,
        };
        if !values.is_empty() {
            suggestions
                .suggest_values(values.into_iter().map(StringOrSuggestion::String).collect());
        }
        Ok(())
    }

    /// PHP: `$this->suggestX()($input)` — invoking a suggestion closure directly. Calling this
    /// on a List is a programming error (PHP would fatal on `$array()`).
    pub fn call(
        &self,
        this: &dyn crate::command::BaseCommand,
        input: &CompletionInput,
        suggestions: &mut CompletionSuggestions,
    ) -> anyhow::Result<Vec<String>> {
        match self {
            SuggestedValues::Closure(closure) => closure(this, input, suggestions),
            SuggestedValues::List(_) => panic!("SuggestedValues::call on a non-closure"),
        }
    }
}

pub enum InputDefinitionItem {
    Argument(std::rc::Rc<input_argument::InputArgument>),
    Option(std::rc::Rc<input_option::InputOption>),
}

impl InputDefinitionItem {
    /// Converts to the Symfony-typed definition item accepted by `CommandData::set_definition`.
    pub(crate) fn to_definition_item(
        &self,
    ) -> shirabe_external_packages::symfony::console::input::input_definition::DefinitionItem {
        use shirabe_external_packages::symfony::console::input::input_definition::DefinitionItem;
        match self {
            InputDefinitionItem::Argument(argument) => {
                DefinitionItem::InputArgument(argument.to_base())
            }
            InputDefinitionItem::Option(option) => DefinitionItem::InputOption(option.to_base()),
        }
    }
}

impl From<input_argument::InputArgument> for InputDefinitionItem {
    fn from(value: input_argument::InputArgument) -> Self {
        Self::Argument(std::rc::Rc::new(value))
    }
}

impl From<input_option::InputOption> for InputDefinitionItem {
    fn from(value: input_option::InputOption) -> Self {
        Self::Option(std::rc::Rc::new(value))
    }
}