standout_input/
collector.rs1use 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}