Skip to content

Commit

Permalink
Merge pull request #24 from eivindbergem/noline-support
Browse files Browse the repository at this point in the history
Noline support MVP
  • Loading branch information
thejpster authored Aug 30, 2024
2 parents be2b623 + c101737 commit d3bfe52
Show file tree
Hide file tree
Showing 5 changed files with 512 additions and 147 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
- name: Build (default features)
run: cargo build
- name: Run Tests
- name: Build (no features)
run: cargo build --no-default-features
- name: Build (all features)
run: cargo build --all-features
- name: Run Tests (default features)
run: cargo test
- name: Run Tests (no features)
run: cargo test --no-default-features
- name: Run Tests (all features)
run: cargo test --all-features

clippy:
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ repository = "https://github.com/rust-embedded-community/menu"
readme = "README.md"

[dependencies]

embedded-io = "0.6.1"
noline = { version = "0.5.0", optional = true }

[features]
default = ["echo"]
echo = []

[dev-dependencies]
noline = { version = "0.5.0", features = ["std"] }
pancurses = "0.16"
termion = "4.0.2"
menu = {path = ".", features = ["noline"]}
241 changes: 241 additions & 0 deletions examples/noline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
extern crate menu;

use embedded_io::{ErrorType, Read as EmbRead, Write as EmbWrite};
use menu::*;
use noline::builder::EditorBuilder;
use std::io::{self, Read as _, Stdin, Stdout, Write as _};
use termion::raw::IntoRawMode;

pub struct IOWrapper {
stdin: Stdin,
stdout: Stdout,
}

impl IOWrapper {
pub fn new() -> Self {
Self {
stdin: std::io::stdin(),
stdout: std::io::stdout(),
}
}
}

impl Default for IOWrapper {
fn default() -> Self {
Self::new()
}
}

impl ErrorType for IOWrapper {
type Error = embedded_io::ErrorKind;
}

impl EmbRead for IOWrapper {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
Ok(self.stdin.read(buf).map_err(|e| e.kind())?)
}
}

impl EmbWrite for IOWrapper {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
let mut written = 0;
let parts = buf.split(|b| *b == b'\n').collect::<Vec<_>>();

for (i, part) in parts.iter().enumerate() {
written += self.stdout.write(part).map_err(|e| e.kind())?;

if i != parts.len() - 1 {
let _ = self.stdout.write(b"\r\n").map_err(|e| e.kind())?;
written += 1;
}
}

Ok(written)
}

fn flush(&mut self) -> Result<(), Self::Error> {
Ok(self.stdout.flush().map_err(|e| e.kind())?)
}
}

#[derive(Default)]
struct Context {
_inner: u32,
}

const ROOT_MENU: Menu<IOWrapper, Context> = Menu {
label: "root",
items: &[
&Item {
item_type: ItemType::Callback {
function: select_foo,
parameters: &[
Parameter::Mandatory {
parameter_name: "a",
help: Some("This is the help text for 'a'"),
},
Parameter::Optional {
parameter_name: "b",
help: None,
},
Parameter::Named {
parameter_name: "verbose",
help: None,
},
Parameter::NamedValue {
parameter_name: "level",
argument_name: "INT",
help: Some("Set the level of the dangle"),
},
],
},
command: "foo",
help: Some(
"Makes a foo appear.
This is some extensive help text.
It contains multiple paragraphs and should be preceeded by the parameter list.
",
),
},
&Item {
item_type: ItemType::Callback {
function: select_bar,
parameters: &[],
},
command: "bar",
help: Some("fandoggles a bar"),
},
&Item {
item_type: ItemType::Menu(&Menu {
label: "sub",
items: &[
&Item {
item_type: ItemType::Callback {
function: select_baz,
parameters: &[],
},
command: "baz",
help: Some("thingamobob a baz"),
},
&Item {
item_type: ItemType::Callback {
function: select_quux,
parameters: &[],
},
command: "quux",
help: Some("maximum quux"),
},
],
entry: Some(enter_sub),
exit: Some(exit_sub),
}),
command: "sub",
help: Some("enter sub-menu"),
},
],
entry: Some(enter_root),
exit: Some(exit_root),
};

fn main() {
let _stdout = io::stdout().into_raw_mode().unwrap();

let mut io = IOWrapper::new();
let mut editor = EditorBuilder::new_unbounded()
.with_unbounded_history()
.build_sync(&mut io)
.unwrap();

let mut context = Context::default();
let mut r = Runner::new(ROOT_MENU, &mut editor, io, &mut context);

while let Ok(_) = r.input_line(&mut context) {}
}

fn enter_root(_menu: &Menu<IOWrapper, Context>, interface: &mut IOWrapper, _context: &mut Context) {
writeln!(interface, "In enter_root").unwrap();
}

fn exit_root(_menu: &Menu<IOWrapper, Context>, interface: &mut IOWrapper, _context: &mut Context) {
writeln!(interface, "In exit_root").unwrap();
}

fn select_foo(
_menu: &Menu<IOWrapper, Context>,
item: &Item<IOWrapper, Context>,
args: &[&str],
interface: &mut IOWrapper,
_context: &mut Context,
) {
writeln!(interface, "In select_foo. Args = {:?}", args).unwrap();
writeln!(
interface,
"a = {:?}",
::menu::argument_finder(item, args, "a")
)
.unwrap();
writeln!(
interface,
"b = {:?}",
::menu::argument_finder(item, args, "b")
)
.unwrap();
writeln!(
interface,
"verbose = {:?}",
::menu::argument_finder(item, args, "verbose")
)
.unwrap();
writeln!(
interface,
"level = {:?}",
::menu::argument_finder(item, args, "level")
)
.unwrap();
writeln!(
interface,
"no_such_arg = {:?}",
::menu::argument_finder(item, args, "no_such_arg")
)
.unwrap();
}

fn select_bar(
_menu: &Menu<IOWrapper, Context>,
_item: &Item<IOWrapper, Context>,
args: &[&str],
interface: &mut IOWrapper,
_context: &mut Context,
) {
writeln!(interface, "In select_bar. Args = {:?}", args).unwrap();
}

fn enter_sub(_menu: &Menu<IOWrapper, Context>, interface: &mut IOWrapper, _context: &mut Context) {
writeln!(interface, "In enter_sub").unwrap();
}

fn exit_sub(_menu: &Menu<IOWrapper, Context>, interface: &mut IOWrapper, _context: &mut Context) {
writeln!(interface, "In exit_sub").unwrap();
}

fn select_baz(
_menu: &Menu<IOWrapper, Context>,
_item: &Item<IOWrapper, Context>,
args: &[&str],
interface: &mut IOWrapper,
_context: &mut Context,
) {
writeln!(interface, "In select_baz: Args = {:?}", args).unwrap();
}

fn select_quux(
_menu: &Menu<IOWrapper, Context>,
_item: &Item<IOWrapper, Context>,
args: &[&str],
interface: &mut IOWrapper,
_context: &mut Context,
) {
writeln!(interface, "In select_quux: Args = {:?}", args).unwrap();
}
17 changes: 13 additions & 4 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
extern crate menu;

use embedded_io::Write;
use menu::*;
use pancurses::{endwin, initscr, noecho, Input};
use std::fmt::Write;
use std::convert::Infallible;

#[derive(Default)]
struct Context {
Expand Down Expand Up @@ -87,9 +88,17 @@ It contains multiple paragraphs and should be preceeded by the parameter list.

struct Output(pancurses::Window);

impl std::fmt::Write for Output {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
self.0.printw(s);
impl embedded_io::ErrorType for Output {
type Error = Infallible;
}

impl Write for Output {
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
self.0.printw(core::str::from_utf8(buf).unwrap());
Ok(buf.len())
}

fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
Expand Down
Loading

0 comments on commit d3bfe52

Please sign in to comment.