mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-23 05:19:48 -04:00
parent
e91c149619
commit
a8f436894c
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1399,7 +1399,7 @@ dependencies = [
|
||||
"futures",
|
||||
"ironcalc",
|
||||
"ratatui",
|
||||
"slice-cursor",
|
||||
"slice-utils",
|
||||
"thiserror",
|
||||
"tui-prompts",
|
||||
"tui-textarea",
|
||||
@ -1457,9 +1457,9 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slice-cursor"
|
||||
name = "slice-utils"
|
||||
version = "0.1.0"
|
||||
source = "git+https://dev.zaphar.net/zaphar/slice-cursor-rs.git#562a78eb3f06ac2a9729af7aa211a070f8ed9c39"
|
||||
source = "git+https://dev.zaphar.net/zaphar/slice-cursor-rs.git#699df1c4c9d50e0c2ac2801723f8f2238b4f8c3b"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
|
@ -16,4 +16,4 @@ ratatui = "0.29.0"
|
||||
thiserror = "1.0.65"
|
||||
tui-textarea = "0.7.0"
|
||||
tui-prompts = "0.5.0"
|
||||
slice-cursor = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git" }
|
||||
slice-utils = { git = "https://dev.zaphar.net/zaphar/slice-cursor-rs.git", ref = "main" }
|
||||
|
182
src/ui/cmd.rs
182
src/ui/cmd.rs
@ -1,5 +1,5 @@
|
||||
//! Command mode command parsers.
|
||||
use slice_cursor::{Cursor, Seekable, Span, SpanRange, StrCursor};
|
||||
use slice_utils::{Measured, Peekable, Seekable, Span, StrCursor};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Cmd<'a> {
|
||||
@ -8,73 +8,94 @@ pub enum Cmd<'a> {
|
||||
InsertColumns(usize),
|
||||
Edit(&'a str),
|
||||
Help(Option<&'a str>),
|
||||
Quit,
|
||||
}
|
||||
|
||||
pub fn parse<'cmd, 'i: 'cmd>(input: &'i str) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
let cursor = StrCursor::new(input);
|
||||
// try consume write command.
|
||||
if let Some(cmd) = try_consume_write(cursor.clone()) {
|
||||
if let Some(cmd) = try_consume_write(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
// try consume insert-row command.
|
||||
if let Some(cmd) = try_consume_insert_row(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
// try consume insert-col command.
|
||||
if let Some(cmd) = try_consume_insert_column(cursor.clone()) {
|
||||
//// try consume insert-col command.
|
||||
if let Some(cmd) = try_consume_insert_column(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
// try consume edit command.
|
||||
if let Some(cmd) = try_consume_edit(cursor.clone()) {
|
||||
if let Some(cmd) = try_consume_edit(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
// try consume help command.
|
||||
if let Some(cmd) = try_consume_help(cursor.clone()) {
|
||||
if let Some(cmd) = try_consume_help(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
// try consume quit command.
|
||||
if let Some(cmd) = try_consume_quit(cursor.clone())? {
|
||||
return Ok(Some(cmd));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
const WRITE: &'static str = "write";
|
||||
|
||||
pub fn try_consume_write<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option<Cmd<'cmd>> {
|
||||
let prefix_len = WRITE.len();
|
||||
let full_length = dbg!(input.span(..).len());
|
||||
let arg = if full_length >= prefix_len && input.span(..prefix_len) == WRITE {
|
||||
input.seek(prefix_len);
|
||||
// Should we check for whitespace?
|
||||
input.span(prefix_len..)
|
||||
} else if full_length >= 2 && input.span(..2) == "w " {
|
||||
input.span(2..)
|
||||
// Should we check for whitespace?
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
.trim();
|
||||
return Some(Cmd::Write(if arg.is_empty() { None } else { Some(arg) }));
|
||||
fn compare<'i>(input: StrCursor<'i>, compare: &str) -> bool {
|
||||
input.remaining() >= compare.len() && input.span(0..compare.len()) == compare
|
||||
}
|
||||
|
||||
const IR: &'static str = "ir";
|
||||
const INSERT_ROW: &'static str = "insert-row";
|
||||
fn is_ws<'r, 'i: 'r>(input: &'r mut StrCursor<'i>) -> bool {
|
||||
match input.peek_next() {
|
||||
Some(b) => {
|
||||
if *b == (' ' as u8) || *b == ('\t' as u8) || *b == ('\n' as u8) || *b == ('\r' as u8) {
|
||||
input.next();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_consume_insert_row<'cmd, 'i: 'cmd>(
|
||||
fn try_consume_write<'cmd, 'i: 'cmd>(
|
||||
mut input: StrCursor<'i>,
|
||||
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
let prefix_len = INSERT_ROW.len();
|
||||
let second_prefix_len = IR.len();
|
||||
let full_length = input.span(..).len();
|
||||
let arg =
|
||||
if full_length >= prefix_len && input.span(..prefix_len) == INSERT_ROW {
|
||||
input.seek(prefix_len);
|
||||
// Should we check for whitespace?
|
||||
input.span(prefix_len..)
|
||||
} else if full_length >= second_prefix_len && input.span(..second_prefix_len) == IR {
|
||||
input.span(second_prefix_len..)
|
||||
const SHORT: &'static str = "w";
|
||||
const LONG: &'static str = "write";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
// Should we check for whitespace?
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
.trim();
|
||||
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||
return Err("Invalid command: Did you mean to type `write <arg>`?");
|
||||
}
|
||||
let arg = input.span(0..).trim();
|
||||
return Ok(Some(Cmd::Write(if arg.is_empty() { None } else { Some(arg) })));
|
||||
}
|
||||
|
||||
fn try_consume_insert_row<'cmd, 'i: 'cmd>(
|
||||
mut input: StrCursor<'i>,
|
||||
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
const SHORT: &'static str = "ir";
|
||||
const LONG: &'static str = "insert-rows";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||
return Err("Invalid command: Did you mean to type `insert-rows <arg>`?");
|
||||
}
|
||||
let arg = input.span(0..).trim();
|
||||
return Ok(Some(Cmd::InsertRow(if arg.is_empty() {
|
||||
1
|
||||
} else {
|
||||
@ -86,14 +107,89 @@ pub fn try_consume_insert_row<'cmd, 'i: 'cmd>(
|
||||
})));
|
||||
}
|
||||
|
||||
pub fn try_consume_insert_column<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option<Cmd<'cmd>> {
|
||||
todo!("insert-column not yet implemented")
|
||||
fn try_consume_insert_column<'cmd, 'i: 'cmd>(
|
||||
mut input: StrCursor<'i>,
|
||||
) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
const SHORT: &'static str = "ic";
|
||||
const LONG: &'static str = "insert-cols";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||
return Err("Invalid command: Did you mean to type `insert-cols <arg>`?");
|
||||
}
|
||||
let arg = input.span(0..).trim();
|
||||
return Ok(Some(Cmd::InsertColumns(if arg.is_empty() {
|
||||
1
|
||||
} else {
|
||||
if let Ok(count) = arg.parse() {
|
||||
count
|
||||
} else {
|
||||
return Err("You must pass in a non negative number for the row count");
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
pub fn try_consume_edit<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option<Cmd<'cmd>> {
|
||||
todo!("edit not yet implemented")
|
||||
fn try_consume_edit<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
const SHORT: &'static str = "e";
|
||||
const LONG: &'static str = "edit";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||
return Err("Invalid command: Did you mean to type `edit <arg>`?");
|
||||
}
|
||||
let arg = input.span(0..).trim();
|
||||
return Ok(Some(Cmd::Edit(if arg.is_empty() {
|
||||
return Err("You must pass in a path to edit");
|
||||
} else {
|
||||
arg
|
||||
})));
|
||||
}
|
||||
|
||||
pub fn try_consume_help<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Option<Cmd<'cmd>> {
|
||||
todo!("help not yet implemented")
|
||||
fn try_consume_help<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
const SHORT: &'static str = "?";
|
||||
const LONG: &'static str = "help";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
// Should we check for whitespace?
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
if input.remaining() > 0 && !is_ws(&mut input) {
|
||||
return Err("Invalid command: Did you mean to type `help <arg>`?");
|
||||
}
|
||||
let arg = input.span(0..).trim();
|
||||
return Ok(Some(Cmd::Help(if arg.is_empty() { None } else { Some(arg) })));
|
||||
}
|
||||
|
||||
fn try_consume_quit<'cmd, 'i: 'cmd>(mut input: StrCursor<'i>) -> Result<Option<Cmd<'cmd>>, &'static str> {
|
||||
const SHORT: &'static str = "q";
|
||||
const LONG: &'static str = "quit";
|
||||
|
||||
if compare(input.clone(), LONG) {
|
||||
input.seek(LONG.len());
|
||||
} else if compare(input.clone(), SHORT) {
|
||||
input.seek(SHORT.len());
|
||||
// Should we check for whitespace?
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
if input.remaining() > 0 {
|
||||
return Err("Invalid command: Quit does not take an argument");
|
||||
}
|
||||
return Ok(Some(Cmd::Quit));
|
||||
}
|
||||
|
125
src/ui/mod.rs
125
src/ui/mod.rs
@ -7,7 +7,13 @@ use crate::book::Book;
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||
use ratatui::{
|
||||
self, buffer::Buffer, layout::{Constraint, Flex, Layout, Rect}, style::{Color, Modifier, Style, Stylize}, text::{Line, Text}, widgets::{Block, Cell, Paragraph, Row, Table, TableState, Widget}, Frame
|
||||
self,
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Text},
|
||||
widgets::{Block, Cell, Paragraph, Row, Table, TableState, Widget},
|
||||
Frame,
|
||||
};
|
||||
use tui_prompts::{State, Status, TextPrompt, TextState};
|
||||
use tui_textarea::{CursorMove, TextArea};
|
||||
@ -16,6 +22,8 @@ mod cmd;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use cmd::Cmd;
|
||||
|
||||
#[derive(Default, Debug, PartialEq)]
|
||||
pub enum Modality {
|
||||
#[default]
|
||||
@ -79,14 +87,19 @@ impl<'ws> Workspace<'ws> {
|
||||
}
|
||||
|
||||
pub fn load(path: &PathBuf, locale: &str, tz: &str) -> Result<Self> {
|
||||
let book = if path.exists() {
|
||||
Book::new_from_xlsx_with_locale(&path.to_string_lossy().to_string(), locale, tz)?
|
||||
} else {
|
||||
Book::default()
|
||||
};
|
||||
let book = load_book(path, locale, tz)?;
|
||||
Ok(Workspace::new(book, path.clone()))
|
||||
}
|
||||
|
||||
pub fn load_into<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
|
||||
let path: PathBuf = path.into();
|
||||
// FIXME(zaphar): This should be managed better.
|
||||
let book = load_book(&path, "en", "America/New_York")?;
|
||||
self.book = book;
|
||||
self.name = path;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_down(&mut self) -> Result<()> {
|
||||
let mut loc = self.book.location.clone();
|
||||
let (row_count, _) = self.book.get_size()?;
|
||||
@ -197,19 +210,47 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_command(&mut self, cmd: String) -> Result<bool> {
|
||||
if cmd.is_empty() {
|
||||
fn handle_command(&mut self, cmd_text: String) -> Result<bool> {
|
||||
if cmd_text.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
match cmd.as_str() {
|
||||
"w" | "write" => {
|
||||
self.save_file()?;
|
||||
match cmd::parse(&cmd_text) {
|
||||
Ok(Some(Cmd::Edit(path))) => {
|
||||
self.load_into(path)?;
|
||||
Ok(true)
|
||||
}
|
||||
Ok(Some(Cmd::Help(_maybe_topic))) => {
|
||||
// TODO(jeremy): Modal dialogs?
|
||||
Ok(true)
|
||||
}
|
||||
Ok(Some(Cmd::Write(maybe_path))) => {
|
||||
if let Some(path) = maybe_path {
|
||||
self.save_to(path)?;
|
||||
} else {
|
||||
self.save_file()?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Ok(Some(Cmd::InsertColumns(count))) => {
|
||||
self.book.insert_columns(self.book.location.col, count)?;
|
||||
self.book.evaluate();
|
||||
Ok(true)
|
||||
},
|
||||
_ => {
|
||||
// noop?
|
||||
Ok(Some(Cmd::InsertRow(count))) => {
|
||||
self.book.insert_rows(self.book.location.row, count)?;
|
||||
self.book.evaluate();
|
||||
Ok(true)
|
||||
},
|
||||
Ok(Some(Cmd::Quit)) => {
|
||||
// TODO(zaphar): We probably need to do better than this
|
||||
std::process::exit(0);
|
||||
},
|
||||
Ok(None) => Ok(false),
|
||||
Err(_msg) => {
|
||||
// TODO(jeremy): Modal dialogs?
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_navigation_input(&mut self, key: event::KeyEvent) -> Result<Option<ExitCode>> {
|
||||
@ -343,12 +384,17 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_render_parts(&mut self, area: Rect) -> Vec<(Rect, Box<dyn Fn(Rect, &mut Buffer, &mut Self)>)> {
|
||||
fn save_to<S: Into<String>>(&self, path: S) -> Result<()> {
|
||||
self.book.save_to_xlsx(path.into().as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_render_parts(
|
||||
&mut self,
|
||||
area: Rect,
|
||||
) -> Vec<(Rect, Box<dyn Fn(Rect, &mut Buffer, &mut Self)>)> {
|
||||
use ratatui::widgets::StatefulWidget;
|
||||
let mut cs = vec![
|
||||
Constraint::Fill(4),
|
||||
Constraint::Fill(30),
|
||||
];
|
||||
let mut cs = vec![Constraint::Fill(4), Constraint::Fill(30)];
|
||||
let Address { row, col } = self.book.location;
|
||||
let mut rs: Vec<Box<dyn Fn(Rect, &mut Buffer, &mut Self)>> = vec![
|
||||
Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| ws.text_area.render(rect, buf)),
|
||||
@ -374,22 +420,40 @@ impl<'ws> Workspace<'ws> {
|
||||
}
|
||||
if self.state.modality == Modality::Command {
|
||||
cs.push(Constraint::Max(1));
|
||||
rs.push(Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| StatefulWidget::render(
|
||||
rs.push(Box::new(|rect: Rect, buf: &mut Buffer, ws: &mut Self| {
|
||||
StatefulWidget::render(
|
||||
TextPrompt::from("Command"),
|
||||
rect,
|
||||
buf,
|
||||
&mut ws.state.command_state)
|
||||
));
|
||||
&mut ws.state.command_state,
|
||||
)
|
||||
}));
|
||||
}
|
||||
let rects: Vec<Rect> = Vec::from(Layout::vertical(cs)
|
||||
.vertical_margin(2)
|
||||
.horizontal_margin(2)
|
||||
.flex(Flex::Legacy)
|
||||
.split(area.clone()).as_ref());
|
||||
rects.into_iter().zip(rs.into_iter()).map(|(rect, f)| (rect, f)).collect()
|
||||
let rects: Vec<Rect> = Vec::from(
|
||||
Layout::vertical(cs)
|
||||
.vertical_margin(2)
|
||||
.horizontal_margin(2)
|
||||
.flex(Flex::Legacy)
|
||||
.split(area.clone())
|
||||
.as_ref(),
|
||||
);
|
||||
rects
|
||||
.into_iter()
|
||||
.zip(rs.into_iter())
|
||||
.map(|(rect, f)| (rect, f))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn load_book(path: &PathBuf, locale: &str, tz: &str) -> Result<Book, anyhow::Error> {
|
||||
let book = if path.exists() {
|
||||
Book::new_from_xlsx_with_locale(&path.to_string_lossy().to_string(), locale, tz)?
|
||||
} else {
|
||||
Book::default()
|
||||
};
|
||||
Ok(book)
|
||||
}
|
||||
|
||||
fn reset_text_area<'a>(content: String) -> TextArea<'a> {
|
||||
let mut text_area = TextArea::from(content.lines());
|
||||
text_area.set_cursor_line_style(Style::default());
|
||||
@ -422,13 +486,12 @@ impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> {
|
||||
))
|
||||
.right_aligned(),
|
||||
);
|
||||
|
||||
|
||||
for (rect, f) in self.get_render_parts(area.clone()) {
|
||||
f(rect, buf, self);
|
||||
f(rect, buf, self);
|
||||
}
|
||||
|
||||
outer_block.render(area, buf);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,8 +23,8 @@ fn test_short_write_cmd() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_row_cmd() {
|
||||
let input = "insert-row 1";
|
||||
fn test_insert_rows_cmd() {
|
||||
let input = "insert-rows 1";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
@ -34,7 +34,7 @@ fn test_insert_row_cmd() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_row_cmd_short() {
|
||||
fn test_insert_rows_cmd_short() {
|
||||
let input = "ir 1";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
@ -43,3 +43,90 @@ fn test_insert_row_cmd_short() {
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::InsertRow(1));
|
||||
}
|
||||
|
||||
fn test_insert_cols_cmd() {
|
||||
let input = "insert-cols 1";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::InsertColumns(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_cols_cmd_short() {
|
||||
let input = "ic 1";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::InsertColumns(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_cmd() {
|
||||
let input = "edit path.txt";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Edit("path.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_cmd_short() {
|
||||
let input = "e path.txt";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Edit("path.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_help_cmd() {
|
||||
let input = "help topic";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Help(Some("topic")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_help_cmd_short() {
|
||||
let input = "? topic";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Help(Some("topic")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quit_cmd_short() {
|
||||
let input = "q";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Quit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quit_cmd() {
|
||||
let input = "quit";
|
||||
let result = parse(input);
|
||||
assert!(result.is_ok());
|
||||
let output = result.unwrap();
|
||||
assert!(output.is_some());
|
||||
let cmd = output.unwrap();
|
||||
assert_eq!(cmd, Cmd::Quit);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user