From 06fd2f21574bf954415f1dbfb08969c780c3cd36 Mon Sep 17 00:00:00 2001 From: Allen Chow Date: Fri, 8 Jul 2022 04:41:08 +0000 Subject: [PATCH 1/3] support auto-completion for console cmd --- Cargo.lock | 11 ++ commons/scmd/Cargo.toml | 3 +- commons/scmd/src/console.rs | 273 ++++++++++++++++++++++++++++++++++++ commons/scmd/src/context.rs | 11 +- commons/scmd/src/lib.rs | 2 + 5 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 commons/scmd/src/console.rs diff --git a/Cargo.lock b/Cargo.lock index 070453729f..2a2f22a550 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8059,6 +8059,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rustyline-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb35a55ab810b5c0fe31606fe9b47d1354e4dc519bec0a102655f78ea2b38057" +dependencies = [ + "quote 1.0.17", + "syn 1.0.90", +] + [[package]] name = "rw-stream-sink" version = "0.2.1" @@ -8182,6 +8192,7 @@ dependencies = [ "rand 0.8.5", "rust-flatten-json", "rustyline", + "rustyline-derive", "serde 1.0.136", "serde_json", "thiserror", diff --git a/commons/scmd/Cargo.toml b/commons/scmd/Cargo.toml index 3651448f45..49c4d9c96b 100644 --- a/commons/scmd/Cargo.toml +++ b/commons/scmd/Cargo.toml @@ -10,7 +10,8 @@ edition = "2021" anyhow = "1.0.41" thiserror = "1.0" serde = { version = "1.0.130", features = ["derive"] } -rustyline = "9.0.0" +rustyline = "9.1.2" +rustyline-derive = "0.6.0" clap = { version = "3", features = ["derive"] } serde_json = { version="1.0", features = ["arbitrary_precision"]} rust-flatten-json = "0.2.0" diff --git a/commons/scmd/src/console.rs b/commons/scmd/src/console.rs new file mode 100644 index 0000000000..255645a58b --- /dev/null +++ b/commons/scmd/src/console.rs @@ -0,0 +1,273 @@ +use std::borrow::Cow::{self, Borrowed, Owned}; + +// use rustyline::Result; +use rustyline::completion::{extract_word, Completer, FilenameCompleter, Pair}; +use rustyline::error::ReadlineError; +use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; +use rustyline::hint::Hinter; +use rustyline::validate::{self, MatchingBracketValidator, Validator}; +use rustyline::Context; +use rustyline_derive::Helper; +use std::collections::HashSet; + +const DEFAULT_BREAK_CHARS: [u8; 3] = [b' ', b'\t', b'\n']; + +#[derive(Hash, Debug, PartialEq, Eq)] +struct Command { + cmd: String, + pre_cmd: String, +} + +impl Command { + fn new(cmd: &str, pre_cmd: &str) -> Self { + Self { + cmd: cmd.into(), + pre_cmd: pre_cmd.into(), + } + } +} +struct CommandCompleter { + cmds: HashSet, +} + +impl CommandCompleter { + pub fn find_matches(&self, line: &str, pos: usize) -> rustyline::Result<(usize, Vec)> { + let (start, word) = extract_word(line, pos, None, &DEFAULT_BREAK_CHARS); + let pre_cmd = line[..start].trim(); + + let matches = self + .cmds + .iter() + .filter_map(|hint| { + if hint.cmd.starts_with(word) && pre_cmd == hint.pre_cmd { + let mut replacement = hint.cmd.clone(); + replacement += " "; + Some(Pair { + display: hint.cmd.to_string(), + replacement: replacement.to_string(), + }) + } else { + None + } + }) + .collect(); + Ok((start, matches)) + } +} + +impl Completer for CommandCompleter { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &Context<'_>, + ) -> rustyline::Result<(usize, Vec)> { + self.find_matches(line, pos) + } +} +impl Hinter for CommandCompleter { + type Hint = String; + + fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { + None + } +} + +#[derive(Helper)] +pub(crate) struct RLHelper { + file_completer: FilenameCompleter, + cmd_completer: CommandCompleter, + highlighter: MatchingBracketHighlighter, + validator: MatchingBracketValidator, +} + +impl Completer for RLHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + match self.cmd_completer.complete(line, pos, ctx) { + Ok((start, matches)) => { + if matches.is_empty() { + self.file_completer.complete(line, pos, ctx) + } else { + Ok((start, matches)) + } + } + Err(e) => Err(e), + } + } +} + +impl Hinter for RLHelper { + type Hint = String; + + fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { + None + } +} + +impl Highlighter for RLHelper { + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + _default: bool, + ) -> Cow<'b, str> { + Borrowed(prompt) + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Owned("\x1b[1m".to_owned() + hint + "\x1b[m") + } + + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } + + fn highlight_char(&self, line: &str, pos: usize) -> bool { + self.highlighter.highlight_char(line, pos) + } +} + +impl Validator for RLHelper { + fn validate( + &self, + ctx: &mut validate::ValidationContext, + ) -> rustyline::Result { + self.validator.validate(ctx) + } + + fn validate_while_typing(&self) -> bool { + self.validator.validate_while_typing() + } +} + +// Commands need to be auto-completed +// TODO: auto fetch commands from Clap +fn cmd_sets() -> HashSet { + let mut set = HashSet::new(); + set.insert(Command::new("account", "")); + set.insert(Command::new("state", "")); + set.insert(Command::new("node", "")); + set.insert(Command::new("chain", "")); + set.insert(Command::new("txpool", "")); + set.insert(Command::new("dev", "")); + set.insert(Command::new("contract", "")); + set.insert(Command::new("version", "")); + set.insert(Command::new("output", "")); + set.insert(Command::new("history", "")); + set.insert(Command::new("quit", "")); + set.insert(Command::new("console", "")); + set.insert(Command::new("help", "")); + + // Subcommand of account + set.insert(Command::new("create", "account")); + set.insert(Command::new("show", "account")); + set.insert(Command::new("transfer", "account")); + set.insert(Command::new("accept-token", "account")); + set.insert(Command::new("list", "account")); + set.insert(Command::new("import-multisig", "account")); + set.insert(Command::new("change-password", "account")); + set.insert(Command::new("default", "account")); + set.insert(Command::new("remove", "account")); + set.insert(Command::new("lock", "account")); + set.insert(Command::new("unlock", "account")); + set.insert(Command::new("export", "account")); + set.insert(Command::new("import", "account")); + set.insert(Command::new("import-readonly", "account")); + set.insert(Command::new("execute-function", "account")); + set.insert(Command::new("execute-script", "account")); + set.insert(Command::new("sign-multisig-txn", "account")); + set.insert(Command::new("submit-txn", "account")); + set.insert(Command::new("sign-message", "account")); + set.insert(Command::new("verify-sign-message", "account")); + set.insert(Command::new("derive-address", "account")); + set.insert(Command::new("receipt-identifier", "account")); + set.insert(Command::new("generate-keypair", "account")); + set.insert(Command::new("rotate-authentication-key", "account")); + set.insert(Command::new("nft", "account")); + set.insert(Command::new("help", "account")); + + // Subcommad of state + set.insert(Command::new("list", "state")); + set.insert(Command::new("get", "state")); + set.insert(Command::new("get-proof", "state")); + set.insert(Command::new("get-root", "state")); + set.insert(Command::new("help", "state")); + + // Subcommad of node + set.insert(Command::new("info", "node")); + set.insert(Command::new("peers", "node")); + set.insert(Command::new("metrics", "node")); + set.insert(Command::new("manager", "node")); + set.insert(Command::new("service", "node")); + set.insert(Command::new("sync", "node")); + set.insert(Command::new("network", "node")); + set.insert(Command::new("help", "node")); + + // Subcommad of chain + set.insert(Command::new("info", "chain")); + set.insert(Command::new("get-block", "chain")); + set.insert(Command::new("list-block", "chain")); + set.insert(Command::new("get-txn", "chain")); + set.insert(Command::new("get-txn-infos", "chain")); + set.insert(Command::new("get-txn-info", "chain")); + set.insert(Command::new("get-events", "chain")); + set.insert(Command::new("epoch-info", "chain")); + set.insert(Command::new("get-txn-info-list", "chain")); + set.insert(Command::new("get-txn-proof", "chain")); + set.insert(Command::new("get-block-info", "chain")); + set.insert(Command::new("help", "chain")); + + // Subcommad of txpool + set.insert(Command::new("pending-txn", "txpool")); + set.insert(Command::new("pending-txns", "txpool")); + set.insert(Command::new("status", "txpool")); + set.insert(Command::new("help", "txpool")); + + // Subcommad of dev + set.insert(Command::new("get-coin", "dev")); + set.insert(Command::new("move-explain", "dev")); + set.insert(Command::new("compile", "dev")); + set.insert(Command::new("deploy", "dev")); + set.insert(Command::new("module-proposal", "dev")); + set.insert(Command::new("module-plan", "dev")); + set.insert(Command::new("module-queue", "dev")); + set.insert(Command::new("module-exe", "dev")); + set.insert(Command::new("vm-config-proposal", "dev")); + set.insert(Command::new("package", "dev")); + set.insert(Command::new("call", "dev")); + set.insert(Command::new("resolve", "dev")); + set.insert(Command::new("call-api", "dev")); + set.insert(Command::new("subscribe", "dev")); + set.insert(Command::new("log", "dev")); + set.insert(Command::new("panic", "dev")); + set.insert(Command::new("sleep", "dev")); + set.insert(Command::new("gen-block", "dev")); + set.insert(Command::new("help", "dev")); + + // Subcommad of contract + set.insert(Command::new("get", "contract")); + set.insert(Command::new("help", "contract")); + // Subcommad of version + // Subcommad of output + // Subcommad of history + // Subcommad of quit + // Subcommad of console + set +} + +pub(crate) fn init_helper() -> RLHelper { + RLHelper { + file_completer: FilenameCompleter::new(), + cmd_completer: CommandCompleter { cmds: cmd_sets() }, + highlighter: MatchingBracketHighlighter::new(), + validator: MatchingBracketValidator::new(), + } +} diff --git a/commons/scmd/src/context.rs b/commons/scmd/src/context.rs index fa90e9a769..b46aa0f412 100644 --- a/commons/scmd/src/context.rs +++ b/commons/scmd/src/context.rs @@ -3,7 +3,8 @@ use crate::error::CmdError; use crate::{ - print_action_result, CommandAction, CommandExec, CustomCommand, HistoryOp, OutputFormat, + init_helper, print_action_result, CommandAction, CommandExec, CustomCommand, HistoryOp, + OutputFormat, RLHelper, }; use anyhow::Result; use clap::{Arg, Command}; @@ -20,7 +21,7 @@ use std::sync::Arc; pub use rustyline::{ config::CompletionType, error::ReadlineError, ColorMode, Config as ConsoleConfig, EditMode, - Editor, + Editor, OutputStreamType, }; pub static G_DEFAULT_CONSOLE_CONFIG: Lazy = Lazy::new(|| { @@ -32,6 +33,7 @@ pub static G_DEFAULT_CONSOLE_CONFIG: Lazy = Lazy::new(|| { .auto_add_history(false) .edit_mode(EditMode::Emacs) .color_mode(ColorMode::Enabled) + .output_stream(OutputStreamType::Stdout) .build() }); @@ -306,7 +308,8 @@ where let global_opt = Arc::new(global_opt); let state = Arc::new(state); let (config, history_file) = init_action(&app, global_opt.clone(), state.clone()); - let mut rl = Editor::<()>::with_config(config); + let mut rl = Editor::with_config(config); + rl.set_helper(Some(init_helper())); if let Some(history_file) = history_file.as_ref() { if !history_file.exists() { if let Err(e) = File::create(history_file.as_path()) { @@ -464,7 +467,7 @@ where global_opt: Arc, state: Arc, quit_action: Box, - mut rl: Editor<()>, + mut rl: Editor, history_file: Option, ) { let global_opt = Arc::try_unwrap(global_opt) diff --git a/commons/scmd/src/lib.rs b/commons/scmd/src/lib.rs index 280a41e9d0..6d492c7c12 100644 --- a/commons/scmd/src/lib.rs +++ b/commons/scmd/src/lib.rs @@ -3,11 +3,13 @@ mod action; mod command; +mod console; mod context; pub mod error; mod result; pub use action::*; pub use command::*; +pub(crate) use console::*; pub use context::*; pub use result::*; From c2e2531cdc334dd167978ebc93d5df8f442f1709 Mon Sep 17 00:00:00 2001 From: Allen Chow Date: Sat, 9 Jul 2022 07:48:12 +0000 Subject: [PATCH 2/3] refactor code --- commons/scmd/src/console.rs | 151 +++++++----------------------------- commons/scmd/src/context.rs | 143 ++++++++++++++++++++++++++++------ commons/scmd/src/lib.rs | 1 - 3 files changed, 149 insertions(+), 146 deletions(-) diff --git a/commons/scmd/src/console.rs b/commons/scmd/src/console.rs index 255645a58b..e9ba8fa2f9 100644 --- a/commons/scmd/src/console.rs +++ b/commons/scmd/src/console.rs @@ -1,8 +1,10 @@ +// Copyright (c) The Starcoin Core Contributors +// SPDX-License-Identifier: Apache-2.0 + use std::borrow::Cow::{self, Borrowed, Owned}; -// use rustyline::Result; +use once_cell::sync::Lazy; use rustyline::completion::{extract_word, Completer, FilenameCompleter, Pair}; -use rustyline::error::ReadlineError; use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; use rustyline::hint::Hinter; use rustyline::validate::{self, MatchingBracketValidator, Validator}; @@ -10,16 +12,34 @@ use rustyline::Context; use rustyline_derive::Helper; use std::collections::HashSet; +use rustyline::{ + config::CompletionType, error::ReadlineError, ColorMode, Config as ConsoleConfig, EditMode, + OutputStreamType, +}; + +pub static G_DEFAULT_CONSOLE_CONFIG: Lazy = Lazy::new(|| { + ConsoleConfig::builder() + .max_history_size(1000) + .history_ignore_space(true) + .history_ignore_dups(true) + .completion_type(CompletionType::List) + .auto_add_history(false) + .edit_mode(EditMode::Emacs) + .color_mode(ColorMode::Enabled) + .output_stream(OutputStreamType::Stdout) + .build() +}); + const DEFAULT_BREAK_CHARS: [u8; 3] = [b' ', b'\t', b'\n']; #[derive(Hash, Debug, PartialEq, Eq)] -struct Command { +pub(crate) struct CommandName { cmd: String, pre_cmd: String, } -impl Command { - fn new(cmd: &str, pre_cmd: &str) -> Self { +impl CommandName { + pub(crate) fn new(cmd: &str, pre_cmd: &str) -> Self { Self { cmd: cmd.into(), pre_cmd: pre_cmd.into(), @@ -27,7 +47,7 @@ impl Command { } } struct CommandCompleter { - cmds: HashSet, + cmds: HashSet, } impl CommandCompleter { @@ -148,125 +168,10 @@ impl Validator for RLHelper { } } -// Commands need to be auto-completed -// TODO: auto fetch commands from Clap -fn cmd_sets() -> HashSet { - let mut set = HashSet::new(); - set.insert(Command::new("account", "")); - set.insert(Command::new("state", "")); - set.insert(Command::new("node", "")); - set.insert(Command::new("chain", "")); - set.insert(Command::new("txpool", "")); - set.insert(Command::new("dev", "")); - set.insert(Command::new("contract", "")); - set.insert(Command::new("version", "")); - set.insert(Command::new("output", "")); - set.insert(Command::new("history", "")); - set.insert(Command::new("quit", "")); - set.insert(Command::new("console", "")); - set.insert(Command::new("help", "")); - - // Subcommand of account - set.insert(Command::new("create", "account")); - set.insert(Command::new("show", "account")); - set.insert(Command::new("transfer", "account")); - set.insert(Command::new("accept-token", "account")); - set.insert(Command::new("list", "account")); - set.insert(Command::new("import-multisig", "account")); - set.insert(Command::new("change-password", "account")); - set.insert(Command::new("default", "account")); - set.insert(Command::new("remove", "account")); - set.insert(Command::new("lock", "account")); - set.insert(Command::new("unlock", "account")); - set.insert(Command::new("export", "account")); - set.insert(Command::new("import", "account")); - set.insert(Command::new("import-readonly", "account")); - set.insert(Command::new("execute-function", "account")); - set.insert(Command::new("execute-script", "account")); - set.insert(Command::new("sign-multisig-txn", "account")); - set.insert(Command::new("submit-txn", "account")); - set.insert(Command::new("sign-message", "account")); - set.insert(Command::new("verify-sign-message", "account")); - set.insert(Command::new("derive-address", "account")); - set.insert(Command::new("receipt-identifier", "account")); - set.insert(Command::new("generate-keypair", "account")); - set.insert(Command::new("rotate-authentication-key", "account")); - set.insert(Command::new("nft", "account")); - set.insert(Command::new("help", "account")); - - // Subcommad of state - set.insert(Command::new("list", "state")); - set.insert(Command::new("get", "state")); - set.insert(Command::new("get-proof", "state")); - set.insert(Command::new("get-root", "state")); - set.insert(Command::new("help", "state")); - - // Subcommad of node - set.insert(Command::new("info", "node")); - set.insert(Command::new("peers", "node")); - set.insert(Command::new("metrics", "node")); - set.insert(Command::new("manager", "node")); - set.insert(Command::new("service", "node")); - set.insert(Command::new("sync", "node")); - set.insert(Command::new("network", "node")); - set.insert(Command::new("help", "node")); - - // Subcommad of chain - set.insert(Command::new("info", "chain")); - set.insert(Command::new("get-block", "chain")); - set.insert(Command::new("list-block", "chain")); - set.insert(Command::new("get-txn", "chain")); - set.insert(Command::new("get-txn-infos", "chain")); - set.insert(Command::new("get-txn-info", "chain")); - set.insert(Command::new("get-events", "chain")); - set.insert(Command::new("epoch-info", "chain")); - set.insert(Command::new("get-txn-info-list", "chain")); - set.insert(Command::new("get-txn-proof", "chain")); - set.insert(Command::new("get-block-info", "chain")); - set.insert(Command::new("help", "chain")); - - // Subcommad of txpool - set.insert(Command::new("pending-txn", "txpool")); - set.insert(Command::new("pending-txns", "txpool")); - set.insert(Command::new("status", "txpool")); - set.insert(Command::new("help", "txpool")); - - // Subcommad of dev - set.insert(Command::new("get-coin", "dev")); - set.insert(Command::new("move-explain", "dev")); - set.insert(Command::new("compile", "dev")); - set.insert(Command::new("deploy", "dev")); - set.insert(Command::new("module-proposal", "dev")); - set.insert(Command::new("module-plan", "dev")); - set.insert(Command::new("module-queue", "dev")); - set.insert(Command::new("module-exe", "dev")); - set.insert(Command::new("vm-config-proposal", "dev")); - set.insert(Command::new("package", "dev")); - set.insert(Command::new("call", "dev")); - set.insert(Command::new("resolve", "dev")); - set.insert(Command::new("call-api", "dev")); - set.insert(Command::new("subscribe", "dev")); - set.insert(Command::new("log", "dev")); - set.insert(Command::new("panic", "dev")); - set.insert(Command::new("sleep", "dev")); - set.insert(Command::new("gen-block", "dev")); - set.insert(Command::new("help", "dev")); - - // Subcommad of contract - set.insert(Command::new("get", "contract")); - set.insert(Command::new("help", "contract")); - // Subcommad of version - // Subcommad of output - // Subcommad of history - // Subcommad of quit - // Subcommad of console - set -} - -pub(crate) fn init_helper() -> RLHelper { +pub(crate) fn init_helper(cmds: HashSet) -> RLHelper { RLHelper { file_completer: FilenameCompleter::new(), - cmd_completer: CommandCompleter { cmds: cmd_sets() }, + cmd_completer: CommandCompleter { cmds }, highlighter: MatchingBracketHighlighter::new(), validator: MatchingBracketValidator::new(), } diff --git a/commons/scmd/src/context.rs b/commons/scmd/src/context.rs index b46aa0f412..c28db37c06 100644 --- a/commons/scmd/src/context.rs +++ b/commons/scmd/src/context.rs @@ -1,17 +1,19 @@ // Copyright (c) The Starcoin Core Contributors // SPDX-License-Identifier: Apache-2.0 +pub use crate::console::G_DEFAULT_CONSOLE_CONFIG; +use crate::console::{init_helper, CommandName, RLHelper}; use crate::error::CmdError; use crate::{ - init_helper, print_action_result, CommandAction, CommandExec, CustomCommand, HistoryOp, - OutputFormat, RLHelper, + print_action_result, CommandAction, CommandExec, CustomCommand, HistoryOp, OutputFormat, }; use anyhow::Result; use clap::{Arg, Command}; use clap::{ErrorKind, Parser}; -use once_cell::sync::Lazy; +use rustyline::{error::ReadlineError, Config as ConsoleConfig, Editor}; use serde_json::Value; use std::collections::HashMap; +use std::collections::HashSet; use std::ffi::OsString; use std::fs::File; use std::io::prelude::*; @@ -19,25 +21,122 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; -pub use rustyline::{ - config::CompletionType, error::ReadlineError, ColorMode, Config as ConsoleConfig, EditMode, - Editor, OutputStreamType, -}; +static G_OUTPUT_FORMAT_ARG: &str = "output-format"; -pub static G_DEFAULT_CONSOLE_CONFIG: Lazy = Lazy::new(|| { - ConsoleConfig::builder() - .max_history_size(1000) - .history_ignore_space(true) - .history_ignore_dups(true) - .completion_type(CompletionType::List) - .auto_add_history(false) - .edit_mode(EditMode::Emacs) - .color_mode(ColorMode::Enabled) - .output_stream(OutputStreamType::Stdout) - .build() -}); +// Commands need to be auto-completed +// TODO: auto fetch commands from Clap +fn cmd_sets() -> HashSet { + let mut set = HashSet::new(); + set.insert(CommandName::new("account", "")); + set.insert(CommandName::new("state", "")); + set.insert(CommandName::new("node", "")); + set.insert(CommandName::new("chain", "")); + set.insert(CommandName::new("txpool", "")); + set.insert(CommandName::new("dev", "")); + set.insert(CommandName::new("contract", "")); + set.insert(CommandName::new("version", "")); + set.insert(CommandName::new("output", "")); + set.insert(CommandName::new("history", "")); + set.insert(CommandName::new("quit", "")); + set.insert(CommandName::new("console", "")); + set.insert(CommandName::new("help", "")); -static G_OUTPUT_FORMAT_ARG: &str = "output-format"; + // Subcommand of account + set.insert(CommandName::new("create", "account")); + set.insert(CommandName::new("show", "account")); + set.insert(CommandName::new("transfer", "account")); + set.insert(CommandName::new("accept-token", "account")); + set.insert(CommandName::new("list", "account")); + set.insert(CommandName::new("import-multisig", "account")); + set.insert(CommandName::new("change-password", "account")); + set.insert(CommandName::new("default", "account")); + set.insert(CommandName::new("remove", "account")); + set.insert(CommandName::new("lock", "account")); + set.insert(CommandName::new("unlock", "account")); + set.insert(CommandName::new("export", "account")); + set.insert(CommandName::new("import", "account")); + set.insert(CommandName::new("import-readonly", "account")); + set.insert(CommandName::new("execute-function", "account")); + set.insert(CommandName::new("execute-script", "account")); + set.insert(CommandName::new("sign-multisig-txn", "account")); + set.insert(CommandName::new("submit-txn", "account")); + set.insert(CommandName::new("sign-message", "account")); + set.insert(CommandName::new("verify-sign-message", "account")); + set.insert(CommandName::new("derive-address", "account")); + set.insert(CommandName::new("receipt-identifier", "account")); + set.insert(CommandName::new("generate-keypair", "account")); + set.insert(CommandName::new("rotate-authentication-key", "account")); + set.insert(CommandName::new("nft", "account")); + set.insert(CommandName::new("help", "account")); + + // Subcommad of state + set.insert(CommandName::new("list", "state")); + set.insert(CommandName::new("get", "state")); + set.insert(CommandName::new("get-proof", "state")); + set.insert(CommandName::new("get-root", "state")); + set.insert(CommandName::new("help", "state")); + + // Subcommad of node + set.insert(CommandName::new("info", "node")); + set.insert(CommandName::new("peers", "node")); + set.insert(CommandName::new("metrics", "node")); + set.insert(CommandName::new("manager", "node")); + set.insert(CommandName::new("service", "node")); + set.insert(CommandName::new("sync", "node")); + set.insert(CommandName::new("network", "node")); + set.insert(CommandName::new("help", "node")); + + // Subcommad of chain + set.insert(CommandName::new("info", "chain")); + set.insert(CommandName::new("get-block", "chain")); + set.insert(CommandName::new("list-block", "chain")); + set.insert(CommandName::new("get-txn", "chain")); + set.insert(CommandName::new("get-txn-infos", "chain")); + set.insert(CommandName::new("get-txn-info", "chain")); + set.insert(CommandName::new("get-events", "chain")); + set.insert(CommandName::new("epoch-info", "chain")); + set.insert(CommandName::new("get-txn-info-list", "chain")); + set.insert(CommandName::new("get-txn-proof", "chain")); + set.insert(CommandName::new("get-block-info", "chain")); + set.insert(CommandName::new("help", "chain")); + + // Subcommad of txpool + set.insert(CommandName::new("pending-txn", "txpool")); + set.insert(CommandName::new("pending-txns", "txpool")); + set.insert(CommandName::new("status", "txpool")); + set.insert(CommandName::new("help", "txpool")); + + // Subcommad of dev + set.insert(CommandName::new("get-coin", "dev")); + set.insert(CommandName::new("move-explain", "dev")); + set.insert(CommandName::new("compile", "dev")); + set.insert(CommandName::new("deploy", "dev")); + set.insert(CommandName::new("module-proposal", "dev")); + set.insert(CommandName::new("module-plan", "dev")); + set.insert(CommandName::new("module-queue", "dev")); + set.insert(CommandName::new("module-exe", "dev")); + set.insert(CommandName::new("vm-config-proposal", "dev")); + set.insert(CommandName::new("package", "dev")); + set.insert(CommandName::new("call", "dev")); + set.insert(CommandName::new("resolve", "dev")); + set.insert(CommandName::new("call-api", "dev")); + set.insert(CommandName::new("subscribe", "dev")); + set.insert(CommandName::new("log", "dev")); + set.insert(CommandName::new("panic", "dev")); + set.insert(CommandName::new("sleep", "dev")); + set.insert(CommandName::new("gen-block", "dev")); + set.insert(CommandName::new("help", "dev")); + + // Subcommad of contract + set.insert(CommandName::new("get", "contract")); + set.insert(CommandName::new("help", "contract")); + // Subcommad of version + // Subcommad of output + // Subcommad of history + // Subcommad of quit + // Subcommad of console + set +} pub struct CmdContext where @@ -308,8 +407,8 @@ where let global_opt = Arc::new(global_opt); let state = Arc::new(state); let (config, history_file) = init_action(&app, global_opt.clone(), state.clone()); - let mut rl = Editor::with_config(config); - rl.set_helper(Some(init_helper())); + let mut rl = Editor::::with_config(config); + rl.set_helper(Some(init_helper(cmd_sets()))); if let Some(history_file) = history_file.as_ref() { if !history_file.exists() { if let Err(e) = File::create(history_file.as_path()) { diff --git a/commons/scmd/src/lib.rs b/commons/scmd/src/lib.rs index 6d492c7c12..ddfe1f7667 100644 --- a/commons/scmd/src/lib.rs +++ b/commons/scmd/src/lib.rs @@ -10,6 +10,5 @@ mod result; pub use action::*; pub use command::*; -pub(crate) use console::*; pub use context::*; pub use result::*; From 977d1052c1d74b9dffc6ffeff1934033c0bba54b Mon Sep 17 00:00:00 2001 From: Allen Chow Date: Sat, 9 Jul 2022 15:23:17 +0000 Subject: [PATCH 3/3] Get command names from Clap API --- commons/scmd/src/console.rs | 7 +- commons/scmd/src/context.rs | 151 +++++++++--------------------------- 2 files changed, 37 insertions(+), 121 deletions(-) diff --git a/commons/scmd/src/console.rs b/commons/scmd/src/console.rs index e9ba8fa2f9..1628f713f5 100644 --- a/commons/scmd/src/console.rs +++ b/commons/scmd/src/console.rs @@ -39,11 +39,8 @@ pub(crate) struct CommandName { } impl CommandName { - pub(crate) fn new(cmd: &str, pre_cmd: &str) -> Self { - Self { - cmd: cmd.into(), - pre_cmd: pre_cmd.into(), - } + pub(crate) fn new(cmd: String, pre_cmd: String) -> Self { + Self { cmd, pre_cmd } } } struct CommandCompleter { diff --git a/commons/scmd/src/context.rs b/commons/scmd/src/context.rs index c28db37c06..5b046c0453 100644 --- a/commons/scmd/src/context.rs +++ b/commons/scmd/src/context.rs @@ -23,121 +23,6 @@ use std::sync::Arc; static G_OUTPUT_FORMAT_ARG: &str = "output-format"; -// Commands need to be auto-completed -// TODO: auto fetch commands from Clap -fn cmd_sets() -> HashSet { - let mut set = HashSet::new(); - set.insert(CommandName::new("account", "")); - set.insert(CommandName::new("state", "")); - set.insert(CommandName::new("node", "")); - set.insert(CommandName::new("chain", "")); - set.insert(CommandName::new("txpool", "")); - set.insert(CommandName::new("dev", "")); - set.insert(CommandName::new("contract", "")); - set.insert(CommandName::new("version", "")); - set.insert(CommandName::new("output", "")); - set.insert(CommandName::new("history", "")); - set.insert(CommandName::new("quit", "")); - set.insert(CommandName::new("console", "")); - set.insert(CommandName::new("help", "")); - - // Subcommand of account - set.insert(CommandName::new("create", "account")); - set.insert(CommandName::new("show", "account")); - set.insert(CommandName::new("transfer", "account")); - set.insert(CommandName::new("accept-token", "account")); - set.insert(CommandName::new("list", "account")); - set.insert(CommandName::new("import-multisig", "account")); - set.insert(CommandName::new("change-password", "account")); - set.insert(CommandName::new("default", "account")); - set.insert(CommandName::new("remove", "account")); - set.insert(CommandName::new("lock", "account")); - set.insert(CommandName::new("unlock", "account")); - set.insert(CommandName::new("export", "account")); - set.insert(CommandName::new("import", "account")); - set.insert(CommandName::new("import-readonly", "account")); - set.insert(CommandName::new("execute-function", "account")); - set.insert(CommandName::new("execute-script", "account")); - set.insert(CommandName::new("sign-multisig-txn", "account")); - set.insert(CommandName::new("submit-txn", "account")); - set.insert(CommandName::new("sign-message", "account")); - set.insert(CommandName::new("verify-sign-message", "account")); - set.insert(CommandName::new("derive-address", "account")); - set.insert(CommandName::new("receipt-identifier", "account")); - set.insert(CommandName::new("generate-keypair", "account")); - set.insert(CommandName::new("rotate-authentication-key", "account")); - set.insert(CommandName::new("nft", "account")); - set.insert(CommandName::new("help", "account")); - - // Subcommad of state - set.insert(CommandName::new("list", "state")); - set.insert(CommandName::new("get", "state")); - set.insert(CommandName::new("get-proof", "state")); - set.insert(CommandName::new("get-root", "state")); - set.insert(CommandName::new("help", "state")); - - // Subcommad of node - set.insert(CommandName::new("info", "node")); - set.insert(CommandName::new("peers", "node")); - set.insert(CommandName::new("metrics", "node")); - set.insert(CommandName::new("manager", "node")); - set.insert(CommandName::new("service", "node")); - set.insert(CommandName::new("sync", "node")); - set.insert(CommandName::new("network", "node")); - set.insert(CommandName::new("help", "node")); - - // Subcommad of chain - set.insert(CommandName::new("info", "chain")); - set.insert(CommandName::new("get-block", "chain")); - set.insert(CommandName::new("list-block", "chain")); - set.insert(CommandName::new("get-txn", "chain")); - set.insert(CommandName::new("get-txn-infos", "chain")); - set.insert(CommandName::new("get-txn-info", "chain")); - set.insert(CommandName::new("get-events", "chain")); - set.insert(CommandName::new("epoch-info", "chain")); - set.insert(CommandName::new("get-txn-info-list", "chain")); - set.insert(CommandName::new("get-txn-proof", "chain")); - set.insert(CommandName::new("get-block-info", "chain")); - set.insert(CommandName::new("help", "chain")); - - // Subcommad of txpool - set.insert(CommandName::new("pending-txn", "txpool")); - set.insert(CommandName::new("pending-txns", "txpool")); - set.insert(CommandName::new("status", "txpool")); - set.insert(CommandName::new("help", "txpool")); - - // Subcommad of dev - set.insert(CommandName::new("get-coin", "dev")); - set.insert(CommandName::new("move-explain", "dev")); - set.insert(CommandName::new("compile", "dev")); - set.insert(CommandName::new("deploy", "dev")); - set.insert(CommandName::new("module-proposal", "dev")); - set.insert(CommandName::new("module-plan", "dev")); - set.insert(CommandName::new("module-queue", "dev")); - set.insert(CommandName::new("module-exe", "dev")); - set.insert(CommandName::new("vm-config-proposal", "dev")); - set.insert(CommandName::new("package", "dev")); - set.insert(CommandName::new("call", "dev")); - set.insert(CommandName::new("resolve", "dev")); - set.insert(CommandName::new("call-api", "dev")); - set.insert(CommandName::new("subscribe", "dev")); - set.insert(CommandName::new("log", "dev")); - set.insert(CommandName::new("panic", "dev")); - set.insert(CommandName::new("sleep", "dev")); - set.insert(CommandName::new("gen-block", "dev")); - set.insert(CommandName::new("help", "dev")); - - // Subcommad of contract - set.insert(CommandName::new("get", "contract")); - set.insert(CommandName::new("help", "contract")); - // Subcommad of version - // Subcommad of output - // Subcommad of history - // Subcommad of quit - // Subcommad of console - set -} - pub struct CmdContext where State: 'static, @@ -408,7 +293,17 @@ where let state = Arc::new(state); let (config, history_file) = init_action(&app, global_opt.clone(), state.clone()); let mut rl = Editor::::with_config(config); - rl.set_helper(Some(init_helper(cmd_sets()))); + let cmd_sets = Self::get_command_names_recursively(&app, "".to_string(), 3) + .iter() + .map(|(a, b)| { + CommandName::new( + a.to_string(), + b.replace(&app_name[..], "").trim().to_string(), + ) + }) + .collect(); + + rl.set_helper(Some(init_helper(cmd_sets))); if let Some(history_file) = history_file.as_ref() { if !history_file.exists() { if let Err(e) = File::create(history_file.as_path()) { @@ -582,4 +477,28 @@ where } quit_action(app, global_opt, state); } + + fn get_command_names_recursively( + app: &Command, + prepositive: String, + max_depth: u32, + ) -> HashSet<(String, String)> { + if max_depth == 0 { + return HashSet::<(String, String)>::new(); + } + let name = app.get_name(); + let mut pre = prepositive; + if !pre.is_empty() { + pre.push(' '); + } + pre.push_str(name); + + let mut set = HashSet::new(); + for sub_app in app.get_subcommands() { + set.insert((sub_app.get_name().to_owned(), pre.clone())); + let sub_set = Self::get_command_names_recursively(sub_app, pre.clone(), max_depth - 1); + set.extend(sub_set); + } + set + } }