1pub use crate::builder::CliBuilder;
2
3use core::fmt::Debug;
4
5#[cfg(not(feature = "history"))]
6use core::marker::PhantomData;
7
8use crate::{
9 buffer::Buffer,
10 builder::DEFAULT_PROMPT,
11 codes,
12 command::RawCommand,
13 editor::Editor,
14 input::{ControlInput, Input, InputGenerator},
15 service::{Autocomplete, CommandProcessor, Help, ParseError, ProcessError},
16 token::Tokens,
17 writer::{WriteExt, Writer},
18};
19
20#[cfg(feature = "autocomplete")]
21use crate::autocomplete::Request;
22
23#[cfg(feature = "help")]
24use crate::{help::HelpRequest, service::HelpError};
25
26#[cfg(feature = "history")]
27use crate::history::History;
28
29use embedded_io::{Error, Write};
30
31pub struct CliHandle<'a, W: Write<Error = E>, E: embedded_io::Error> {
32 new_prompt: Option<&'static str>,
33 writer: Writer<'a, W, E>,
34}
35
36impl<'a, W, E> CliHandle<'a, W, E>
37where
38 W: Write<Error = E>,
39 E: embedded_io::Error,
40{
41 pub fn set_prompt(&mut self, prompt: &'static str) {
43 self.new_prompt = Some(prompt)
44 }
45
46 pub fn writer(&mut self) -> &mut Writer<'a, W, E> {
47 &mut self.writer
48 }
49
50 fn new(writer: Writer<'a, W, E>) -> Self {
51 Self {
52 new_prompt: None,
53 writer,
54 }
55 }
56}
57
58impl<'a, W, E> Debug for CliHandle<'a, W, E>
59where
60 W: Write<Error = E>,
61 E: embedded_io::Error,
62{
63 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
64 f.debug_struct("CliHandle").finish()
65 }
66}
67
68#[cfg(feature = "history")]
69enum NavigateHistory {
70 Older,
71 Newer,
72}
73
74enum NavigateInput {
75 Backward,
76 Forward,
77}
78
79#[doc(hidden)]
80pub struct Cli<W: Write<Error = E>, E: Error, CommandBuffer: Buffer, HistoryBuffer: Buffer> {
81 editor: Option<Editor<CommandBuffer>>,
82 #[cfg(feature = "history")]
83 history: History<HistoryBuffer>,
84 input_generator: Option<InputGenerator>,
85 prompt: &'static str,
86 writer: W,
87 #[cfg(not(feature = "history"))]
88 _ph: PhantomData<HistoryBuffer>,
89}
90
91impl<W, E, CommandBuffer, HistoryBuffer> Debug for Cli<W, E, CommandBuffer, HistoryBuffer>
92where
93 W: Write<Error = E>,
94 E: embedded_io::Error,
95 CommandBuffer: Buffer,
96 HistoryBuffer: Buffer,
97{
98 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99 f.debug_struct("Cli")
100 .field("editor", &self.editor)
101 .field("input_generator", &self.input_generator)
102 .field("prompt", &self.prompt)
103 .finish()
104 }
105}
106
107impl<W, E, CommandBuffer, HistoryBuffer> Cli<W, E, CommandBuffer, HistoryBuffer>
108where
109 W: Write<Error = E>,
110 E: embedded_io::Error,
111 CommandBuffer: Buffer,
112 HistoryBuffer: Buffer,
113{
114 #[allow(unused_variables)]
115 #[deprecated(since = "0.2.1", note = "please use `builder` instead")]
116 pub fn new(
117 writer: W,
118 command_buffer: CommandBuffer,
119 history_buffer: HistoryBuffer,
120 ) -> Result<Self, E> {
121 let mut cli = Self {
122 editor: Some(Editor::new(command_buffer)),
123 #[cfg(feature = "history")]
124 history: History::new(history_buffer),
125 input_generator: Some(InputGenerator::new()),
126 prompt: DEFAULT_PROMPT,
127 writer,
128 #[cfg(not(feature = "history"))]
129 _ph: PhantomData,
130 };
131
132 cli.writer.flush_str(cli.prompt)?;
133
134 Ok(cli)
135 }
136
137 pub(crate) fn from_builder(
138 builder: CliBuilder<W, E, CommandBuffer, HistoryBuffer>,
139 ) -> Result<Self, E> {
140 let mut cli = Self {
141 editor: Some(Editor::new(builder.command_buffer)),
142 #[cfg(feature = "history")]
143 history: History::new(builder.history_buffer),
144 input_generator: Some(InputGenerator::new()),
145 prompt: builder.prompt,
146 writer: builder.writer,
147 #[cfg(not(feature = "history"))]
148 _ph: PhantomData,
149 };
150
151 cli.writer.flush_str(cli.prompt)?;
152
153 Ok(cli)
154 }
155
156 pub fn process_byte<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
161 &mut self,
162 b: u8,
163 processor: &mut P,
164 ) -> Result<(), E> {
165 if let (Some(mut editor), Some(mut input_generator)) =
166 (self.editor.take(), self.input_generator.take())
167 {
168 let result = input_generator
169 .accept(b)
170 .map(|input| match input {
171 Input::Control(control) => {
172 self.on_control_input::<C, _>(&mut editor, control, processor)
173 }
174 Input::Char(text) => self.on_text_input(&mut editor, text),
175 })
176 .unwrap_or(Ok(()));
177
178 self.editor = Some(editor);
179 self.input_generator = Some(input_generator);
180 result
181 } else {
182 Ok(())
183 }
184 }
185
186 pub fn set_prompt(&mut self, prompt: &'static str) -> Result<(), E> {
191 self.prompt = prompt;
192 self.clear_line(false)?;
193
194 if let Some(editor) = self.editor.as_mut() {
195 self.writer.flush_str(editor.text())?;
196 }
197
198 Ok(())
199 }
200
201 pub fn write(
202 &mut self,
203 f: impl FnOnce(&mut Writer<'_, W, E>) -> Result<(), E>,
204 ) -> Result<(), E> {
205 self.clear_line(true)?;
206
207 let mut cli_writer = Writer::new(&mut self.writer);
208
209 f(&mut cli_writer)?;
210
211 if cli_writer.is_dirty() {
213 self.writer.write_str(codes::CRLF)?;
214 }
215 self.writer.write_str(self.prompt)?;
216 if let Some(editor) = self.editor.as_mut() {
217 self.writer.flush_str(editor.text())?;
218 }
219
220 Ok(())
221 }
222
223 fn clear_line(&mut self, clear_prompt: bool) -> Result<(), E> {
224 self.writer.write_str("\r")?;
225 self.writer.write_bytes(codes::CLEAR_LINE)?;
226
227 if !clear_prompt {
228 self.writer.write_str(self.prompt)?;
229 }
230
231 self.writer.flush()
232 }
233
234 fn on_text_input(&mut self, editor: &mut Editor<CommandBuffer>, text: &str) -> Result<(), E> {
235 let is_inside = editor.cursor() < editor.len();
236 if let Some(c) = editor.insert(text) {
237 if is_inside {
238 debug_assert_eq!(c.chars().count(), 1);
240 self.writer.write_bytes(codes::INSERT_CHAR)?;
241 }
242 self.writer.flush_str(c)?;
243 }
244 Ok(())
245 }
246
247 fn on_control_input<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
248 &mut self,
249 editor: &mut Editor<CommandBuffer>,
250 control: ControlInput,
251 processor: &mut P,
252 ) -> Result<(), E> {
253 match control {
254 ControlInput::Enter => {
255 self.writer.write_str(codes::CRLF)?;
256
257 #[cfg(feature = "history")]
258 self.history.push(editor.text());
259 let text = editor.text_mut();
260
261 let tokens = Tokens::new(text);
262 self.process_input::<C, _>(tokens, processor)?;
263
264 editor.clear();
265
266 self.writer.flush_str(self.prompt)?;
267 }
268 ControlInput::Tab => {
269 #[cfg(feature = "autocomplete")]
270 self.process_autocomplete::<C>(editor)?;
271 }
272 ControlInput::Backspace => {
273 if editor.move_left() {
274 editor.remove();
275 self.writer.flush_bytes(codes::CURSOR_BACKWARD)?;
276 self.writer.flush_bytes(codes::DELETE_CHAR)?;
277 }
278 }
279 ControlInput::Down =>
280 {
281 #[cfg(feature = "history")]
282 self.navigate_history(editor, NavigateHistory::Newer)?
283 }
284 ControlInput::Up =>
285 {
286 #[cfg(feature = "history")]
287 self.navigate_history(editor, NavigateHistory::Older)?
288 }
289 ControlInput::Forward => self.navigate_input(editor, NavigateInput::Forward)?,
290 ControlInput::Back => self.navigate_input(editor, NavigateInput::Backward)?,
291 }
292
293 Ok(())
294 }
295
296 fn navigate_input(
297 &mut self,
298 editor: &mut Editor<CommandBuffer>,
299 dir: NavigateInput,
300 ) -> Result<(), E> {
301 match dir {
302 NavigateInput::Backward if editor.move_left() => {
303 self.writer.flush_bytes(codes::CURSOR_BACKWARD)?;
304 }
305 NavigateInput::Forward if editor.move_right() => {
306 self.writer.flush_bytes(codes::CURSOR_FORWARD)?;
307 }
308 _ => return Ok(()),
309 }
310 Ok(())
311 }
312
313 #[cfg(feature = "history")]
314 fn navigate_history(
315 &mut self,
316 editor: &mut Editor<CommandBuffer>,
317 dir: NavigateHistory,
318 ) -> Result<(), E> {
319 let history_elem = match dir {
320 NavigateHistory::Older => self.history.next_older(),
321 NavigateHistory::Newer => self.history.next_newer().or(Some("")),
322 };
323 if let Some(element) = history_elem {
324 editor.clear();
325 editor.insert(element);
326 self.clear_line(false)?;
327
328 self.writer.flush_str(editor.text())?;
329 }
330 Ok(())
331 }
332
333 #[cfg(feature = "autocomplete")]
334 fn process_autocomplete<C: Autocomplete>(
335 &mut self,
336 editor: &mut Editor<CommandBuffer>,
337 ) -> Result<(), E> {
338 let initial_cursor = editor.cursor();
339 editor.autocompletion(|request, autocompletion| {
340 C::autocomplete(request.clone(), autocompletion);
341 match request {
342 Request::CommandName(name) if "help".starts_with(name) => {
343 let autocompleted = unsafe { "help".get_unchecked(name.len()..) };
345 autocompletion.merge_autocompletion(autocompleted)
346 }
347 _ => {}
348 }
349 });
350 if editor.cursor() > initial_cursor {
351 let autocompleted = editor.text_range(initial_cursor..);
352 self.writer.flush_str(autocompleted)?;
353 }
354 Ok(())
355 }
356
357 fn process_command<P: CommandProcessor<W, E>>(
358 &mut self,
359 command: RawCommand<'_>,
360 handler: &mut P,
361 ) -> Result<(), E> {
362 let cli_writer = Writer::new(&mut self.writer);
363 let mut handle = CliHandle::new(cli_writer);
364
365 let res = handler.process(&mut handle, command);
366
367 if let Some(prompt) = handle.new_prompt {
368 self.prompt = prompt;
369 }
370 if handle.writer.is_dirty() {
371 self.writer.write_str(codes::CRLF)?;
372 }
373 self.writer.flush()?;
374
375 match res {
376 Err(ProcessError::ParseError(err)) => self.process_error(err),
377 Err(ProcessError::WriteError(err)) => Err(err),
378 Ok(()) => Ok(()),
379 }
380 }
381
382 #[allow(clippy::extra_unused_type_parameters)]
383 fn process_input<C: Help, P: CommandProcessor<W, E>>(
384 &mut self,
385 tokens: Tokens<'_>,
386 handler: &mut P,
387 ) -> Result<(), E> {
388 if let Some(command) = RawCommand::from_tokens(&tokens) {
389 #[cfg(feature = "help")]
390 if let Some(request) = HelpRequest::from_command(&command) {
391 return self.process_help::<C>(request);
392 }
393
394 self.process_command(command, handler)?;
395 };
396
397 Ok(())
398 }
399
400 fn process_error(&mut self, error: ParseError<'_>) -> Result<(), E> {
401 self.writer.write_str("error: ")?;
402 match error {
403 ParseError::MissingRequiredArgument { name } => {
404 self.writer.write_str("missing required argument: ")?;
405 self.writer.write_str(name)?;
406 }
407 ParseError::NonAsciiShortOption => {
408 self.writer
409 .write_str("non-ascii in short options is not supported")?;
410 }
411 ParseError::ParseValueError { value, expected } => {
412 self.writer.write_str("failed to parse '")?;
413 self.writer.write_str(value)?;
414 self.writer.write_str("', expected ")?;
415 self.writer.write_str(expected)?;
416 }
417 ParseError::UnexpectedArgument { value } => {
418 self.writer.write_str("unexpected argument: ")?;
419 self.writer.write_str(value)?;
420 }
421 ParseError::UnexpectedLongOption { name } => {
422 self.writer.write_str("unexpected option: -")?;
423 self.writer.write_str("-")?;
424 self.writer.write_str(name)?;
425 }
426 ParseError::UnexpectedShortOption { name } => {
427 if name.is_ascii_alphabetic() {
429 self.writer.write_str("unexpected option: -")?;
430 self.writer.write_bytes(&[name as u8])?;
431 }
432 }
433 ParseError::UnknownCommand => {
434 self.writer.write_str("unknown command")?;
435 }
436 }
437 self.writer.flush_str(codes::CRLF)
438 }
439
440 #[cfg(feature = "help")]
441 fn process_help<C: Help>(&mut self, request: HelpRequest<'_>) -> Result<(), E> {
442 let mut writer = Writer::new(&mut self.writer);
443
444 match request {
445 HelpRequest::All => C::list_commands(&mut writer)?,
446 HelpRequest::Command(command) => {
447 match C::command_help(&mut |_| Ok(()), command.clone(), &mut writer) {
448 Err(HelpError::UnknownCommand) => {
449 writer.write_str("error: ")?;
450 writer.write_str("unknown command")?;
451 }
452 Err(HelpError::WriteError(err)) => return Err(err),
453 Ok(()) => {}
454 }
455 }
456 };
457
458 if writer.is_dirty() {
459 self.writer.write_str(codes::CRLF)?;
460 }
461 self.writer.flush()?;
462
463 Ok(())
464 }
465}