Skip to content

Commit

Permalink
feat: add tab completion and history hint
Browse files Browse the repository at this point in the history
  • Loading branch information
RinChanNOWWW committed Sep 14, 2021
1 parent aba2473 commit ea6268d
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 14 deletions.
13 changes: 12 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ authors = ["RinChanNOW <https://github.com/RinChanNOWWW>"]
description = "ruborute is a command-line tool to get asphyxia@sdvx gaming data."
edition = "2018"
name = "ruborute"
version = "0.1.1"
version = "0.1.2"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -16,5 +16,6 @@ prettytable-rs = "0.8.0"
quick-xml = {version = "0.22.0", features = ["encoding", "serialize"]}
rust-fuzzy-search = "0.1.1"
rustyline = "9.0.0"
rustyline-derive = "0.5.0"
serde = {version = "1.0.129", features = ["derive"]}
serde_json = "1.0.66"
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ruborute

**Are you 暴龍天 ?**. The ruborute is a interactive command-line tool to get asphyxia@sdvx gaming data.
**Are you 暴龍天 ?**. The ruborute is an interactive command-line tool to get asphyxia@sdvx gaming data.

asphyxia-core/plugins: https://github.com/asphyxia-core/plugins

Expand Down Expand Up @@ -57,8 +57,6 @@ Music 1226: <Black night>
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
| #4 | 1139 | Decoy | MXM | 17 | 9929078 | S | HC | 18.077 |
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
| #5 | 933 | 見世物ライフ | MXM | 17 | 9925958 | S | HC | 18.072 |
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
....
+------+----------+----------------+------------+-------+---------+-------+------------+----------+
50 record(s) founded.
Expand Down Expand Up @@ -90,7 +88,7 @@ Your Volforce: 17.714
+-------+---+------+-----+-----+----+-----+----+--------------+
```

You can type Ctrl-C or Ctrl-D to exit.
You can type Ctrl-D to exit.

## Features

Expand All @@ -99,6 +97,9 @@ You can type Ctrl-C or Ctrl-D to exit.
- [x] Compute VF.
- [x] Get the best 50 records.
- [x] Collect more detail statistics (Such as count of a clear type).
- [x] Press "Tab" button to complete the commands.
- [x] History hints supported.
- [x] Type Ctrl-C to interrupt current input.
- [ ] Range get records in VF order.
- [ ] Get music infomation by music id.
- [ ] Get music informaton by music name.
Expand Down
114 changes: 107 additions & 7 deletions src/cmdline.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use std::{collections::HashMap, rc::Rc};
use std::{collections::HashMap, rc::Rc, vec};

use clap::Clap;
use prettytable::{cell, row, Cell, Row, Table};
use rustyline::{error::ReadlineError, Editor};
use rustyline::{
completion::Completer,
error::ReadlineError,
hint::{Hinter, HistoryHinter},
Config, Editor,
};
use rustyline_derive::{Helper, Highlighter, Validator};

use crate::{command::*, storage, Result};

Expand Down Expand Up @@ -32,9 +38,77 @@ pub struct Opt {
music_path: String,
}

struct CmdCompleter {
commands: Vec<String>,
}

impl CmdCompleter {
fn new() -> Self {
CmdCompleter {
commands: Vec::new(),
}
}
fn add_command(&mut self, cmd: String) {
self.commands.push(cmd)
}
}

impl Completer for CmdCompleter {
type Candidate = String;

fn complete(
&self,
line: &str,
_pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let res = self
.commands
.iter()
.filter(|s| s.starts_with(line))
.map(|s| s.clone() + " ")
.collect::<Vec<String>>();
Ok((0, res))
}
}

#[derive(Helper, Highlighter, Validator)]
struct CmdlineHelper {
cmd_completer: CmdCompleter,
cmd_hinter: HistoryHinter,
}

impl CmdlineHelper {
fn add_command(&mut self, cmd: String) {
self.cmd_completer.add_command(cmd);
}
}

impl Completer for CmdlineHelper {
type Candidate = String;

fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
self.cmd_completer.complete(line, pos, ctx)
}
}

impl Hinter for CmdlineHelper {
type Hint = String;

fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
self.cmd_hinter.hint(line, pos, ctx)
}
}

pub struct Cmdline {
help_table: Table,
cmds: HashMap<String, Box<dyn Cmd>>,
rl: Editor<CmdlineHelper>,
}

impl Cmdline {
Expand All @@ -44,11 +118,30 @@ impl Cmdline {
opt.record_path,
opt.music_path,
)?);

let config = Config::builder()
.history_ignore_dups(true)
.history_ignore_space(true)
.completion_type(rustyline::CompletionType::List)
.output_stream(rustyline::OutputStreamType::Stdout)
.build();

let mut helper = CmdlineHelper {
cmd_completer: CmdCompleter::new(),
cmd_hinter: HistoryHinter {},
};
let cmds: HashMap<String, Box<dyn Cmd>> = HashMap::new();
let mut help_table = Table::new();
help_table.add_row(row!["name", "usage", "description"]);
help_table.add_row(row!["help", "help", "show the help information."]);
let mut cmdline = Cmdline { cmds, help_table };
helper.add_command(String::from("help"));
let mut rl = Editor::with_config(config);
rl.set_helper(Some(helper));
let mut cmdline = Cmdline {
cmds,
help_table,
rl,
};

// add commands
cmdline.add_command(Box::new(CmdRecord::new(Rc::clone(&store))));
Expand All @@ -65,20 +158,23 @@ impl Cmdline {
Cell::new(cmd.usage()),
Cell::new(cmd.description()),
]));
if let Some(helper) = self.rl.helper_mut() {
helper.add_command(cmd.name().to_string());
}
self.cmds.insert(cmd.name().to_string(), cmd);
}

fn help(&self) {
self.help_table.printstd();
}

pub fn run(&self) -> Result<()> {
pub fn run(&mut self) -> Result<()> {
// run interactive cmdline
let mut rl = Editor::<()>::new();
loop {
let readline = rl.readline(">> ");
let readline = self.rl.readline(">> ");
match readline {
Ok(line) => {
self.rl.add_history_entry(line.as_str());
let cmds: Vec<String> = line
.trim()
.split(" ")
Expand All @@ -87,7 +183,11 @@ impl Cmdline {
.collect();
self.interact(cmds);
}
Err(ReadlineError::Eof) | Err(ReadlineError::Interrupted) => {
Err(ReadlineError::Interrupted) => {
println!("<Keyboard Interrupted>");
continue;
}
Err(ReadlineError::Eof) => {
println!("Bye");
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use ruborute::{Cmdline, Opt};
fn main() {
let cmdline = Cmdline::new(Opt::parse());
match cmdline {
Ok(cl) => {
Ok(mut cl) => {
if let Err(e) = cl.run() {
eprintln!("{}", e);
exit(1)
Expand Down

0 comments on commit ea6268d

Please sign in to comment.