Skip to main content

standout_input/
collector.rs

1use clap::ArgMatches;
2
3use crate::InputError;
4use crate::InputSources;
5
6pub trait InputCollector<T>: Send + Sync {
7    fn name(&self) -> &'static str;
8
9    fn is_available(&self, matches: &ArgMatches) -> bool;
10
11    fn collect(&self, matches: &ArgMatches) -> Result<Option<T>, InputError>;
12
13    fn bind_sources(&self, _sources: &InputSources) -> Option<Box<dyn InputCollector<T>>> {
14        None
15    }
16
17    fn validate(&self, _value: &T) -> Result<(), String> {
18        Ok(())
19    }
20
21    fn can_retry(&self) -> bool {
22        false
23    }
24}
25
26#[cfg(any(feature = "editor", feature = "simple-prompts", feature = "inquire"))]
27pub(crate) fn prompt_value_from<T>(
28    collector: &dyn InputCollector<T>,
29    sources: &InputSources,
30) -> Result<T, InputError> {
31    let bound = collector.bind_sources(sources);
32    let source: &dyn InputCollector<T> = match bound.as_ref() {
33        Some(bound) => bound.as_ref(),
34        None => collector,
35    };
36    let matches = empty_matches();
37    if !source.is_available(matches) {
38        return Err(InputError::NoInput);
39    }
40    source.collect(matches)?.ok_or(InputError::NoInput)
41}
42
43#[cfg(any(feature = "editor", feature = "simple-prompts", feature = "inquire"))]
44pub(crate) fn empty_matches() -> &'static ArgMatches {
45    use std::sync::OnceLock;
46    static MATCHES: OnceLock<ArgMatches> = OnceLock::new();
47    MATCHES.get_or_init(|| {
48        clap::Command::new("__standout_input_prompt__")
49            .no_binary_name(true)
50            .try_get_matches_from(std::iter::empty::<&str>())
51            .expect("empty command always parses with no args")
52    })
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ResolvedInput<T> {
57    pub value: T,
58    pub source: InputSourceKind,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum InputSourceKind {
63    Arg,
64    Flag,
65    File,
66    Stdin,
67    Env,
68    Clipboard,
69    Editor,
70    Prompt,
71    Default,
72}
73
74impl std::fmt::Display for InputSourceKind {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            Self::Arg => write!(f, "argument"),
78            Self::Flag => write!(f, "flag"),
79            Self::File => write!(f, "file"),
80            Self::Stdin => write!(f, "stdin"),
81            Self::Env => write!(f, "environment variable"),
82            Self::Clipboard => write!(f, "clipboard"),
83            Self::Editor => write!(f, "editor"),
84            Self::Prompt => write!(f, "prompt"),
85            Self::Default => write!(f, "default"),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn source_kind_display() {
96        assert_eq!(InputSourceKind::Arg.to_string(), "argument");
97        assert_eq!(InputSourceKind::File.to_string(), "file");
98        assert_eq!(InputSourceKind::Stdin.to_string(), "stdin");
99        assert_eq!(InputSourceKind::Editor.to_string(), "editor");
100    }
101}