Skip to content

Commit 63b6485

Browse files
committed
WIP: Adding support for named arguments.
1 parent 2a81488 commit 63b6485

File tree

3 files changed

+187
-84
lines changed

3 files changed

+187
-84
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
[package]
22
name = "menu"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
authors = ["Jonathan 'theJPster' Pallant <[email protected]>"]
55
description = "A simple #[no_std] command line interface."
66
license = "MIT OR Apache-2.0"
7+
edition = "2018"
78

89
[dependencies]
10+
11+
12+
[dev-dependencies]
13+
pancurses = "0.16"

examples/simple.rs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
extern crate menu;
22

3-
use std::io::{self, Read, Write};
43
use menu::*;
4+
use pancurses::{endwin, initscr, noecho, Input};
55

66
const FOO_ITEM: Item<Output> = Item {
7-
item_type: ItemType::Callback(select_foo),
7+
item_type: ItemType::Callback {
8+
function: select_foo,
9+
parameters: &[
10+
Parameter::Mandatory("a"),
11+
Parameter::Optional("b"),
12+
Parameter::Named {
13+
parameter_name: "verbose",
14+
argument_name: "VALUE",
15+
},
16+
],
17+
},
818
command: "foo",
919
help: Some("makes a foo appear"),
1020
};
1121

1222
const BAR_ITEM: Item<Output> = Item {
13-
item_type: ItemType::Callback(select_bar),
23+
item_type: ItemType::Callback {
24+
function: select_bar,
25+
parameters: &[],
26+
},
1427
command: "bar",
1528
help: Some("fandoggles a bar"),
1629
};
@@ -29,13 +42,19 @@ const ROOT_MENU: Menu<Output> = Menu {
2942
};
3043

3144
const BAZ_ITEM: Item<Output> = Item {
32-
item_type: ItemType::Callback(select_baz),
45+
item_type: ItemType::Callback {
46+
function: select_baz,
47+
parameters: &[],
48+
},
3349
command: "baz",
3450
help: Some("thingamobob a baz"),
3551
};
3652

3753
const QUUX_ITEM: Item<Output> = Item {
38-
item_type: ItemType::Callback(select_quux),
54+
item_type: ItemType::Callback {
55+
function: select_quux,
56+
parameters: &[],
57+
},
3958
command: "quux",
4059
help: Some("maximum quux"),
4160
};
@@ -47,33 +66,40 @@ const SUB_MENU: Menu<Output> = Menu {
4766
exit: Some(exit_sub),
4867
};
4968

50-
struct Output;
69+
struct Output(pancurses::Window);
5170

5271
impl std::fmt::Write for Output {
5372
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
54-
let mut stdout = io::stdout();
55-
write!(stdout, "{}", s).unwrap();
56-
stdout.flush().unwrap();
73+
self.0.printw(s);
5774
Ok(())
5875
}
5976
}
6077

6178
fn main() {
79+
let window = initscr();
80+
noecho();
6281
let mut buffer = [0u8; 64];
63-
let mut o = Output;
82+
let mut o = Output(window);
6483
let mut r = Runner::new(&ROOT_MENU, &mut buffer, &mut o);
6584
loop {
66-
let mut ch = [0x00u8; 1];
67-
// Wait for char
68-
if let Ok(_) = io::stdin().read(&mut ch) {
69-
// Fix newlines
70-
if ch[0] == 0x0A {
71-
ch[0] = 0x0D;
85+
match r.context.0.getch() {
86+
Some(Input::Character('\n')) => {
87+
r.input_byte(b'\r');
7288
}
73-
// Feed char to runner
74-
r.input_byte(ch[0]);
89+
Some(Input::Character(c)) => {
90+
let mut buf = [0; 4];
91+
for b in c.encode_utf8(&mut buf).bytes() {
92+
r.input_byte(b);
93+
}
94+
}
95+
Some(Input::KeyDC) => break,
96+
Some(input) => {
97+
r.context.0.addstr(&format!("{:?}", input));
98+
}
99+
None => (),
75100
}
76101
}
102+
endwin();
77103
}
78104

79105
fn enter_root(_menu: &Menu<Output>) {

src/lib.rs

Lines changed: 137 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,26 @@
33
type MenuCallbackFn<T> = fn(menu: &Menu<T>);
44
type ItemCallbackFn<T> = fn(menu: &Menu<T>, item: &Item<T>, args: &str, context: &mut T);
55

6+
/// Describes a parameter to the command
7+
pub enum Parameter<'a> {
8+
Mandatory(&'a str),
9+
Optional(&'a str),
10+
Named {
11+
parameter_name: &'a str,
12+
argument_name: &'a str,
13+
},
14+
}
15+
16+
/// Do we enter a sub-menu when this command is entered, or call a specific
17+
/// function?
618
pub enum ItemType<'a, T>
719
where
820
T: 'a,
921
{
10-
Callback(ItemCallbackFn<T>),
22+
Callback {
23+
function: ItemCallbackFn<T>,
24+
parameters: &'a [Parameter<'a>],
25+
},
1126
Menu(&'a Menu<'a, T>),
1227
}
1328

@@ -71,7 +86,7 @@ where
7186

7287
pub fn prompt(&mut self, newline: bool) {
7388
if newline {
74-
write!(self.context, "\n").unwrap();
89+
writeln!(self.context).unwrap();
7590
}
7691
if self.depth != 0 {
7792
let mut depth = 1;
@@ -92,64 +107,10 @@ where
92107
return;
93108
}
94109
let outcome = if input == 0x0D {
95-
write!(self.context, "\n").unwrap();
96-
if let Ok(s) = core::str::from_utf8(&self.buffer[0..self.used]) {
97-
if s == "help" {
98-
let menu = self.menus[self.depth].unwrap();
99-
for item in menu.items {
100-
if let Some(help) = item.help {
101-
writeln!(self.context, "{} - {}", item.command, help).unwrap();
102-
} else {
103-
writeln!(self.context, "{}", item.command).unwrap();
104-
}
105-
}
106-
if self.depth != 0 {
107-
writeln!(self.context, "exit - leave this menu.").unwrap();
108-
}
109-
writeln!(self.context, "help - print this help text.").unwrap();
110-
Outcome::CommandProcessed
111-
} else if s == "exit" && self.depth != 0 {
112-
if self.depth == self.menus.len() {
113-
writeln!(self.context, "Can't enter menu - structure too deep.").unwrap();
114-
} else {
115-
self.menus[self.depth] = None;
116-
self.depth -= 1;
117-
}
118-
Outcome::CommandProcessed
119-
} else {
120-
let mut parts = s.split(' ');
121-
if let Some(cmd) = parts.next() {
122-
let mut found = false;
123-
let menu = self.menus[self.depth].unwrap();
124-
for item in menu.items {
125-
if cmd == item.command {
126-
match item.item_type {
127-
ItemType::Callback(f) => f(menu, item, s, &mut self.context),
128-
ItemType::Menu(m) => {
129-
self.depth += 1;
130-
self.menus[self.depth] = Some(m);
131-
}
132-
}
133-
found = true;
134-
break;
135-
}
136-
}
137-
if !found {
138-
writeln!(self.context, "Command {:?} not found. Try 'help'.", cmd)
139-
.unwrap();
140-
}
141-
Outcome::CommandProcessed
142-
} else {
143-
writeln!(self.context, "Input empty").unwrap();
144-
Outcome::CommandProcessed
145-
}
146-
}
147-
} else {
148-
writeln!(self.context, "Input not valid UTF8").unwrap();
149-
Outcome::CommandProcessed
150-
}
151-
} else if input == 0x08 {
152-
// Handling backspace
110+
writeln!(self.context).unwrap();
111+
self.process_command()
112+
} else if (input == 0x08) || (input == 0x7F) {
113+
// Handling backspace or delete
153114
if self.used > 0 {
154115
write!(self.context, "\u{0008} \u{0008}").unwrap();
155116
self.used -= 1;
@@ -159,15 +120,17 @@ where
159120
self.buffer[self.used] = input;
160121
self.used += 1;
161122

162-
let valid = if let Ok(_) = core::str::from_utf8(&self.buffer[0..self.used]) {
163-
true
164-
} else {
165-
false
166-
};
123+
// We have to do this song and dance because `self.prompt()` needs
124+
// a mutable reference to self, and we can't have that while
125+
// holding a reference to the buffer at the same time.
126+
// This line grabs the buffer, checks it's OK, then releases it again
127+
let valid = core::str::from_utf8(&self.buffer[0..self.used]).is_ok();
128+
// Now we've released the buffer, we can draw the prompt
167129
if valid {
168130
write!(self.context, "\r").unwrap();
169131
self.prompt(false);
170132
}
133+
// Grab the buffer again to render it to the screen
171134
if let Ok(s) = core::str::from_utf8(&self.buffer[0..self.used]) {
172135
write!(self.context, "{}", s).unwrap();
173136
}
@@ -184,6 +147,115 @@ where
184147
Outcome::NeedMore => {}
185148
}
186149
}
150+
151+
fn process_command(&mut self) -> Outcome {
152+
if let Ok(command_line) = core::str::from_utf8(&self.buffer[0..self.used]) {
153+
if command_line == "help" {
154+
let menu = self.menus[self.depth].unwrap();
155+
for item in menu.items {
156+
self.print_help(&item);
157+
}
158+
if self.depth != 0 {
159+
writeln!(self.context, "* exit - leave this menu.").unwrap();
160+
}
161+
writeln!(self.context, "* help - print this help text").unwrap();
162+
Outcome::CommandProcessed
163+
} else if command_line == "exit" && self.depth != 0 {
164+
if self.depth == self.menus.len() {
165+
writeln!(self.context, "Can't enter menu - structure too deep.").unwrap();
166+
} else {
167+
self.menus[self.depth] = None;
168+
self.depth -= 1;
169+
}
170+
Outcome::CommandProcessed
171+
} else {
172+
let mut parts = command_line.split(' ');
173+
if let Some(cmd) = parts.next() {
174+
let mut found = false;
175+
let menu = self.menus[self.depth].unwrap();
176+
for item in menu.items {
177+
if cmd == item.command {
178+
match item.item_type {
179+
ItemType::Callback {
180+
function,
181+
parameters,
182+
} => self.call_function(
183+
function,
184+
parameters,
185+
menu,
186+
item,
187+
command_line,
188+
),
189+
ItemType::Menu(m) => {
190+
self.depth += 1;
191+
self.menus[self.depth] = Some(m);
192+
}
193+
}
194+
found = true;
195+
break;
196+
}
197+
}
198+
if !found {
199+
writeln!(self.context, "Command {:?} not found. Try 'help'.", cmd).unwrap();
200+
}
201+
Outcome::CommandProcessed
202+
} else {
203+
writeln!(self.context, "Input empty").unwrap();
204+
Outcome::CommandProcessed
205+
}
206+
}
207+
} else {
208+
writeln!(self.context, "Input not valid UTF8").unwrap();
209+
Outcome::CommandProcessed
210+
}
211+
}
212+
213+
fn print_help(&mut self, item: &Item<T>) {
214+
match item.item_type {
215+
ItemType::Callback { parameters, .. } => {
216+
if !parameters.is_empty() {
217+
write!(self.context, "* {}", item.command).unwrap();
218+
for param in parameters.iter() {
219+
match param {
220+
Parameter::Mandatory(name) => {
221+
write!(self.context, " <{}>", name).unwrap();
222+
}
223+
Parameter::Optional(name) => {
224+
write!(self.context, " [ <{}> ]", name).unwrap();
225+
}
226+
Parameter::Named {
227+
parameter_name,
228+
argument_name,
229+
} => {
230+
write!(self.context, " [ --{}={} ]", parameter_name, argument_name)
231+
.unwrap();
232+
}
233+
}
234+
}
235+
} else {
236+
write!(self.context, "* {}", item.command).unwrap();
237+
}
238+
}
239+
ItemType::Menu(_menu) => {
240+
write!(self.context, "* {}", item.command).unwrap();
241+
}
242+
}
243+
if let Some(help) = item.help {
244+
write!(self.context, " - {}", help).unwrap();
245+
}
246+
writeln!(self.context).unwrap();
247+
}
248+
249+
fn call_function(
250+
&self,
251+
_function: ItemCallbackFn<T>,
252+
_parameters: &[Parameter],
253+
_parent_menu: &Menu<T>,
254+
_item: &Item<T>,
255+
_command: &str,
256+
) {
257+
258+
}
187259
}
188260

189261
#[cfg(test)]

0 commit comments

Comments
 (0)