1+ //! # Menu
2+ //!
3+ //! A basic command-line interface for `#![no_std]` Rust programs. Peforms
4+ //! zero heap allocation.
15#![ no_std]
6+ #![ deny( missing_docs) ]
27
3- type MenuCallbackFn < T > = fn ( menu : & Menu < T > , context : & mut T ) ;
4- type ItemCallbackFn < T > = fn ( menu : & Menu < T > , item : & Item < T > , args : & [ & str ] , context : & mut T ) ;
8+ /// The type of function we call when we enter/exit a menu.
9+ pub type MenuCallbackFn < T > = fn ( menu : & Menu < T > , context : & mut T ) ;
10+
11+ /// The type of function we call when we a valid command has been entered.
12+ pub type ItemCallbackFn < T > = fn ( menu : & Menu < T > , item : & Item < T > , args : & [ & str ] , context : & mut T ) ;
513
614#[ derive( Debug ) ]
715/// Describes a parameter to the command
@@ -14,7 +22,9 @@ pub enum Parameter<'a> {
1422 Named ( & ' a str ) ,
1523 /// A named parameter with argument (e.g. `--mode=foo` or `--level=3`)
1624 NamedValue {
25+ /// The bit that comes after the `--`
1726 parameter_name : & ' a str ,
27+ /// The bit that comes after the `--name=`, e.g. `INT` or `FILE`. It's mostly for help text.
1828 argument_name : & ' a str ,
1929 } ,
2030}
@@ -25,35 +35,54 @@ pub enum ItemType<'a, T>
2535where
2636 T : ' a ,
2737{
38+ /// Call a function when this command is entered
2839 Callback {
40+ /// The function to call
2941 function : ItemCallbackFn < T > ,
42+ /// The list of parameters for this function. Pass an empty list if there aren't any.
3043 parameters : & ' a [ Parameter < ' a > ] ,
3144 } ,
45+ /// This item is a sub-menu you can enter
3246 Menu ( & ' a Menu < ' a , T > ) ,
47+ /// Internal use only - do not use
3348 _Dummy,
3449}
3550
36- /// Menu Item
51+ /// An `Item` is a what our menus are made from. Each item has a `name` which
52+ /// you have to enter to select this item. Each item can also have zero or
53+ /// more parameters, and some optional help text.
3754pub struct Item < ' a , T >
3855where
3956 T : ' a ,
4057{
58+ /// The word you need to enter to activate this item. It is recommended
59+ /// that you avoid whitespace in this string.
4160 pub command : & ' a str ,
61+ /// Optional help text. Printed if you enter `help`.
4262 pub help : Option < & ' a str > ,
63+ /// The type of this item - menu, callback, etc.
4364 pub item_type : ItemType < ' a , T > ,
4465}
4566
46- /// A Menu is made of Items
67+ /// A ` Menu` is made of one or more `Item`s.
4768pub struct Menu < ' a , T >
4869where
4970 T : ' a ,
5071{
72+ /// Each menu has a label which is visible in the prompt, unless you are
73+ /// the root menu.
5174 pub label : & ' a str ,
75+ /// A slice of menu items in this menu.
5276 pub items : & ' a [ & ' a Item < ' a , T > ] ,
77+ /// A function to call when this menu is entered. If this is the root menu, this is called when the runner is created.
5378 pub entry : Option < MenuCallbackFn < T > > ,
79+ /// A function to call when this menu is exited. Never called for the root menu.
5480 pub exit : Option < MenuCallbackFn < T > > ,
5581}
5682
83+ /// This structure handles the menu. You feed it bytes as they are read from
84+ /// the console and it executes menu actions when commands are typed in
85+ /// (followed by Enter).
5786pub struct Runner < ' a , T >
5887where
5988 T : core:: fmt:: Write ,
6493 /// Maximum four levels deep
6594 menus : [ Option < & ' a Menu < ' a , T > > ; 4 ] ,
6695 depth : usize ,
67- pub context : & ' a mut T ,
96+ /// The context object the `Runner` carries around.
97+ pub context : T ,
6898}
6999
70100/// Looks for the named parameter in the parameter list of the item, then
@@ -192,9 +222,13 @@ impl<'a, T> Runner<'a, T>
192222where
193223 T : core:: fmt:: Write ,
194224{
195- pub fn new ( menu : & ' a Menu < ' a , T > , buffer : & ' a mut [ u8 ] , context : & ' a mut T ) -> Runner < ' a , T > {
225+ /// Create a new `Runner`. You need to supply a top-level menu, and a
226+ /// buffer that the `Runner` can use. Feel free to pass anything as the
227+ /// `context` type - the only requirement is that the `Runner` can
228+ /// `write!` to the context, which it will do for all text output.
229+ pub fn new ( menu : & ' a Menu < ' a , T > , buffer : & ' a mut [ u8 ] , mut context : T ) -> Runner < ' a , T > {
196230 if let Some ( cb_fn) = menu. entry {
197- cb_fn ( menu, context) ;
231+ cb_fn ( menu, & mut context) ;
198232 }
199233 let mut r = Runner {
200234 menus : [ Some ( menu) , None , None , None ] ,
@@ -207,6 +241,8 @@ where
207241 r
208242 }
209243
244+ /// Print out a new command prompt, including sub-menu names if
245+ /// applicable.
210246 pub fn prompt ( & mut self , newline : bool ) {
211247 if newline {
212248 writeln ! ( self . context) . unwrap ( ) ;
@@ -224,6 +260,9 @@ where
224260 write ! ( self . context, "> " ) . unwrap ( ) ;
225261 }
226262
263+ /// Add a byte to the menu runner's buffer. If this byte is a
264+ /// carriage-return, the buffer is scanned and the appropriate action
265+ /// performed.
227266 pub fn input_byte ( & mut self , input : u8 ) {
228267 // Strip carriage returns
229268 if input == 0x0A {
@@ -271,8 +310,10 @@ where
271310 }
272311 }
273312
313+ /// Scan the buffer and do the right thing based on its contents.
274314 fn process_command ( & mut self ) -> Outcome {
275315 if let Ok ( command_line) = core:: str:: from_utf8 ( & self . buffer [ 0 ..self . used ] ) {
316+ // We have a valid string
276317 if command_line == "help" {
277318 let menu = self . menus [ self . depth ] . unwrap ( ) ;
278319 for item in menu. items {
@@ -313,7 +354,7 @@ where
313354 function,
314355 parameters,
315356 } => Self :: call_function (
316- self . context ,
357+ & mut self . context ,
317358 function,
318359 parameters,
319360 menu,
@@ -342,7 +383,8 @@ where
342383 }
343384 }
344385 } else {
345- writeln ! ( self . context, "Input not valid UTF8" ) . unwrap ( ) ;
386+ // Hmm .. we did not have a valid string
387+ writeln ! ( self . context, "Input was not valid UTF-8" ) . unwrap ( ) ;
346388 Outcome :: CommandProcessed
347389 }
348390 }
@@ -435,10 +477,12 @@ where
435477 }
436478 }
437479 Parameter :: NamedValue { parameter_name, .. } => {
438- if let Some ( name) = arg[ 2 ..] . split ( '=' ) . next ( ) {
439- if name == * parameter_name {
440- found = true ;
441- break ;
480+ if arg. contains ( '=' ) {
481+ if let Some ( name) = arg[ 2 ..] . split ( '=' ) . next ( ) {
482+ if name == * parameter_name {
483+ found = true ;
484+ break ;
485+ }
442486 }
443487 }
444488 }
0 commit comments