libpt_cli/repl/
default.rs1use std::fmt::Debug;
6
7use super::Repl;
8
9use embed_doc_image::embed_doc_image;
10
11pub const REPL_HELP_TEMPLATE: &str = r"{usage-heading} {usage}
13
14{all-args}{tab}
15";
16
17use clap::{Parser, Subcommand};
18use dialoguer::{BasicHistory, Completion};
19use libpt_log::trace;
20
21#[allow(clippy::needless_doctest_main)] #[embed_doc_image("repl_screenshot", "data/media/repl.png")]
57#[derive(Parser)]
58#[command(multicall = true, help_template = REPL_HELP_TEMPLATE)]
59#[allow(clippy::module_name_repetitions)] pub struct DefaultRepl<C>
61where
62 C: Debug + Subcommand + strum::IntoEnumIterator,
63{
64 #[command(subcommand)]
66 command: Option<C>,
67
68 #[clap(skip)]
71 buf: String,
72 #[clap(skip)]
73 buf_preparsed: Vec<String>,
74 #[clap(skip)]
75 completion: DefaultReplCompletion<C>,
76 #[clap(skip)]
77 history: BasicHistory,
78}
79
80#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
81struct DefaultReplCompletion<C>
82where
83 C: Debug + Subcommand + strum::IntoEnumIterator,
84{
85 commands: std::marker::PhantomData<C>,
86}
87
88impl<C> Repl<C> for DefaultRepl<C>
89where
90 C: Debug + Subcommand + strum::IntoEnumIterator,
91{
92 fn new() -> Self {
93 Self {
94 command: None,
95 buf_preparsed: Vec::new(),
96 buf: String::new(),
97 history: BasicHistory::new(),
98 completion: DefaultReplCompletion::new(),
99 }
100 }
101 fn command(&self) -> &Option<C> {
102 &self.command
103 }
104 fn step(&mut self) -> Result<(), super::error::Error> {
105 self.buf.clear();
106
107 self.buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default())
117 .completion_with(&self.completion)
118 .history_with(&mut self.history)
119 .interact_text()?;
120
121 self.buf_preparsed = Vec::new();
122 self.buf_preparsed
123 .extend(shlex::split(&self.buf).unwrap_or_default());
124
125 trace!("read input: {:?}", self.buf_preparsed);
126 trace!("repl after step: {:#?}", self);
127
128 let cmds = Self::try_parse_from(&self.buf_preparsed)?;
130 self.command = cmds.command;
131 Ok(())
132 }
133}
134
135impl<C> Default for DefaultRepl<C>
136where
137 C: Debug + Subcommand + strum::IntoEnumIterator,
138{
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl<C> Debug for DefaultRepl<C>
145where
146 C: Debug + Subcommand + strum::IntoEnumIterator,
147{
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 f.debug_struct("DefaultRepl")
150 .field("command", &self.command)
151 .field("buf", &self.buf)
152 .field("buf_preparsed", &self.buf_preparsed)
153 .field("completion", &self.completion)
154 .field("history", &"(no debug)")
155 .finish()
156 }
157}
158
159impl<C> DefaultReplCompletion<C>
160where
161 C: Debug + Subcommand + strum::IntoEnumIterator,
162{
163 pub const fn new() -> Self {
165 Self {
166 commands: std::marker::PhantomData::<C>,
167 }
168 }
169 fn commands() -> Vec<String> {
170 let mut buf = Vec::new();
171 buf.push("help".to_string());
173 for c in C::iter() {
174 buf.push(
177 format!("{c:?}")
178 .split_whitespace()
179 .map(str::to_lowercase)
180 .next()
181 .unwrap()
182 .to_string(),
183 );
184 }
185 trace!("commands: {buf:?}");
186 buf
187 }
188}
189
190impl<C> Default for DefaultReplCompletion<C>
191where
192 C: Debug + Subcommand + strum::IntoEnumIterator,
193{
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199impl<C> Completion for DefaultReplCompletion<C>
200where
201 C: Debug + Subcommand + strum::IntoEnumIterator,
202{
203 fn get(&self, input: &str) -> Option<String> {
205 let matches = Self::commands()
206 .into_iter()
207 .filter(|option| option.starts_with(input))
208 .collect::<Vec<_>>();
209
210 trace!("\nmatches: {matches:#?}");
211 if matches.len() == 1 {
212 Some(matches[0].to_string())
213 } else {
214 None
215 }
216 }
217}