From 52be0dd5edad51973b8cf584c725aa8eb3d367f2 Mon Sep 17 00:00:00 2001 From: vms Date: Fri, 5 Feb 2021 00:28:12 +0300 Subject: [PATCH] Improve various stuff in REPL (#61) --- Cargo.lock | 46 ++--- fluence-app-service/src/service_interface.rs | 2 +- fluence-faas/src/logger/logger_filter.rs | 106 ++++++++++ tools/cli/Cargo.toml | 2 +- tools/cli/src/args.rs | 4 +- tools/repl/Cargo.toml | 4 +- tools/repl/src/editor.rs | 166 +++++++++++++++ tools/repl/src/logger.rs | 53 +++++ tools/repl/src/main.rs | 201 ++----------------- tools/repl/src/repl.rs | 100 ++++----- tools/repl/src/repl/print_state.rs | 59 ++++++ 11 files changed, 468 insertions(+), 275 deletions(-) create mode 100644 tools/repl/src/editor.rs create mode 100644 tools/repl/src/logger.rs create mode 100644 tools/repl/src/repl/print_state.rs diff --git a/Cargo.lock b/Cargo.lock index 82b360dc..5edc1a0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,9 +167,9 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" [[package]] name = "bumpalo" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59" +checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" [[package]] name = "byteorder" @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10bcb9d7dcbf7002aaffbb53eac22906b64cdcc127971dcc387d8eb7c95d5560" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" dependencies = [ "quote", "syn", @@ -543,9 +543,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding_rs" -version = "0.8.26" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ "cfg-if 1.0.0", ] @@ -1033,7 +1033,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.1+wasi-snapshot-preview1", + "wasi 0.10.2+wasi-snapshot-preview1", ] [[package]] @@ -1136,9 +1136,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.3.4" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" [[package]] name = "httpdate" @@ -1171,7 +1171,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project 1.0.4", + "pin-project 1.0.5", "socket2", "tokio", "tower-service", @@ -1363,9 +1363,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0c4e9c72ee9d69b767adebc5f4788462a3b45624acd919475c92597bcaf4f" +checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" [[package]] name = "local_storage" @@ -1754,11 +1754,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2" +checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63" dependencies = [ - "pin-project-internal 1.0.4", + "pin-project-internal 1.0.5", ] [[package]] @@ -1774,9 +1774,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71" +checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" dependencies = [ "proc-macro2", "quote", @@ -2121,9 +2121,9 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "safe-transmute" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b8b2cd387f744f69469aaed197954ba4c0ecdb31e02edf99b023e0df11178a" +checksum = "1d95e7284b4bd97e24af76023904cd0157c9cc9da0310beb4139a1e88a748d47" [[package]] name = "schannel" @@ -2474,9 +2474,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ "bytes 0.5.6", "fnv", @@ -2776,9 +2776,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.1+wasi-snapshot-preview1" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" diff --git a/fluence-app-service/src/service_interface.rs b/fluence-app-service/src/service_interface.rs index e6b97a42..461f2238 100644 --- a/fluence-app-service/src/service_interface.rs +++ b/fluence-app-service/src/service_interface.rs @@ -87,7 +87,7 @@ fn serialize_function_signature( } } -fn serialize_record_type<'a, 'b>( +fn serialize_record_type( id: u64, record: Rc, record_types: &RecordTypes, diff --git a/fluence-faas/src/logger/logger_filter.rs b/fluence-faas/src/logger/logger_filter.rs index c24e1abd..8a96a577 100644 --- a/fluence-faas/src/logger/logger_filter.rs +++ b/fluence-faas/src/logger/logger_filter.rs @@ -100,3 +100,109 @@ impl<'env_string> LoggerFilter<'env_string> { .map_or_else(|| self.default_log_level, |l| Some(*l)) } } + +#[cfg(test)] +mod tests { + use super::LoggerFilter; + use log::LevelFilter; + + fn test_one_level_filter(unparsed_level: &str, expected_level: LevelFilter) { + let logger_filter = LoggerFilter::from_env_string(unparsed_level); + + let actual_level = logger_filter + .module_level("some_module_name") + .expect("global option should work"); + assert_eq!(actual_level, expected_level); + } + + #[test] + fn one_default_filter() { + use LevelFilter::*; + + test_one_level_filter("off", Off); + test_one_level_filter("error", Error); + test_one_level_filter("warn", Warn); + test_one_level_filter("info", Info); + test_one_level_filter("debug", Debug); + test_one_level_filter("trace", Trace); + } + + #[test] + fn module_levels() { + use LevelFilter::*; + let logger_filter = LoggerFilter::from_env_string( + "module_1=off,module_2=error,module_3=warn,module_4=info,module_5=debug,module_6=trace", + ); + + let actual_level = logger_filter + .module_level("module_1") + .expect("module option should work"); + assert_eq!(actual_level, Off); + + let actual_level = logger_filter + .module_level("module_2") + .expect("module option should work"); + assert_eq!(actual_level, Error); + + let actual_level = logger_filter + .module_level("module_3") + .expect("module option should work"); + assert_eq!(actual_level, Warn); + + let actual_level = logger_filter + .module_level("module_4") + .expect("module option should work"); + assert_eq!(actual_level, Info); + + let actual_level = logger_filter + .module_level("module_5") + .expect("module option should work"); + assert_eq!(actual_level, Debug); + + let actual_level = logger_filter + .module_level("module_6") + .expect("module option should work"); + assert_eq!(actual_level, Trace); + } + + #[test] + fn mixed_default_and_module_levels() { + use LevelFilter::*; + let logger_filter = LoggerFilter::from_env_string("module_1=off,module_2=error,module_3=warn,module_4=info,module_5=debug,module_6=trace,off"); + + let actual_level = logger_filter + .module_level("module_1") + .expect("module option should work"); + assert_eq!(actual_level, Off); + + let actual_level = logger_filter + .module_level("module_2") + .expect("module option should work"); + assert_eq!(actual_level, Error); + + let actual_level = logger_filter + .module_level("module_3") + .expect("module option should work"); + assert_eq!(actual_level, Warn); + + let actual_level = logger_filter + .module_level("module_4") + .expect("module option should work"); + assert_eq!(actual_level, Info); + + let actual_level = logger_filter + .module_level("module_5") + .expect("module option should work"); + assert_eq!(actual_level, Debug); + + let actual_level = logger_filter + .module_level("module_6") + .expect("module option should work"); + assert_eq!(actual_level, Trace); + + let actual_level = logger_filter + .module_level("some_module_name") + .expect("global option should work"); + assert_eq!(actual_level, Off); + } +} diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index 8abbc273..9f1d7e24 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "fcli" description = "Fluence FCE command line tool" -version = "0.1.30" +version = "0.1.31" authors = ["Fluence Labs"] repository = "https://github.com/fluencelabs/fce/tools/cli" license = "Apache-2.0" diff --git a/tools/cli/src/args.rs b/tools/cli/src/args.rs index df04b7dd..a2df7583 100644 --- a/tools/cli/src/args.rs +++ b/tools/cli/src/args.rs @@ -34,7 +34,7 @@ pub fn build<'a, 'b>() -> App<'a, 'b> { pub fn embed_wit<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("embed") - .about("Embed WIT to a provided Wasm file") + .about("Embed IT to a provided Wasm file") .args(&[ Arg::with_name(IN_WASM_PATH) .required(true) @@ -55,7 +55,7 @@ pub fn embed_wit<'a, 'b>() -> App<'a, 'b> { pub fn show_wit<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("show") - .about("Show WIT in provided Wasm file") + .about("Show IT of provided Wasm file") .setting(clap::AppSettings::ArgRequiredElseHelp) .args(&[Arg::with_name(IN_WASM_PATH) .required(true) diff --git a/tools/repl/Cargo.toml b/tools/repl/Cargo.toml index 39f621d0..ef9ff426 100644 --- a/tools/repl/Cargo.toml +++ b/tools/repl/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frepl" description = "Fluence FCE REPL intended for testing purposes" -version = "0.1.30" +version = "0.1.31" authors = ["Fluence Labs"] repository = "https://github.com/fluencelabs/fce/tools/repl" license = "Apache-2.0" @@ -24,6 +24,6 @@ env_logger = "0.7.1" log = "0.4.11" rustyline = { version = "6.1.2", features = ["with-fuzzy"] } rustyline-derive = "0.3.1" -rustop = "1.1.0" +rustop = "1.1.1" itertools = "0.9.0" uuid = { version = "0.8.1", features = ["v4"] } diff --git a/tools/repl/src/editor.rs b/tools/repl/src/editor.rs new file mode 100644 index 00000000..9d163218 --- /dev/null +++ b/tools/repl/src/editor.rs @@ -0,0 +1,166 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use rustyline::completion::{Completer, FilenameCompleter, Pair}; +use rustyline::config::OutputStreamType; +use rustyline::error::ReadlineError; +use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; +use rustyline::hint::{Hinter, HistoryHinter}; +use rustyline::validate::{self, MatchingBracketValidator, Validator}; +use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress}; +use rustyline_derive::Helper; + +use std::borrow::Cow::{self, Borrowed, Owned}; +use std::collections::HashSet; + +pub(super) fn init_editor() -> Editor { + let config = Config::builder() + .history_ignore_space(true) + .completion_type(CompletionType::Fuzzy) + .edit_mode(EditMode::Emacs) + .output_stream(OutputStreamType::Stdout) + .build(); + + let repl_hinter = REPLHinter { + commands_hints: commands_hints(), + history_hinter: HistoryHinter {}, + }; + let repl_helper = REPLHelper { + completer: FilenameCompleter::new(), + highlighter: MatchingBracketHighlighter::new(), + hinter: repl_hinter, + colored_prompt: "".to_owned(), + validator: MatchingBracketValidator::new(), + }; + + let mut rl = Editor::with_config(config); + rl.set_helper(Some(repl_helper)); + rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); + rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); + + rl +} + +#[derive(Helper)] +pub(super) struct REPLHelper { + completer: FilenameCompleter, + highlighter: MatchingBracketHighlighter, + validator: MatchingBracketValidator, + hinter: REPLHinter, + colored_prompt: String, +} + +impl REPLHelper { + pub(super) fn set_prompt_color(&mut self, color: String) { + self.colored_prompt = color; + } +} + +/// Tries to find hint from history if its failed from supported command list. +pub(super) struct REPLHinter { + commands_hints: HashSet, + history_hinter: HistoryHinter, +} + +impl Completer for REPLHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> std::result::Result<(usize, Vec), ReadlineError> { + self.completer.complete(line, pos, ctx) + } +} + +impl Hinter for REPLHelper { + fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + if pos < line.len() { + return None; + } + + if let Some(hint) = self.hinter.history_hinter.hint(line, pos, ctx) { + return Some(hint); + } + + self.hinter + .commands_hints + .iter() + .filter_map(|hint| { + // expect hint after word complete, like redis cli, add condition: + // line.ends_with(" ") + if pos > 0 && hint.starts_with(&line[..pos]) { + Some(hint[pos..].to_owned()) + } else { + None + } + }) + .next() + } +} + +impl Highlighter for REPLHelper { + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } + + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + default: bool, + ) -> Cow<'b, str> { + if default { + Borrowed(&self.colored_prompt) + } else { + Borrowed(prompt) + } + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + Owned("\x1b[1m".to_owned() + hint + "\x1b[m") + } + + fn highlight_char(&self, line: &str, pos: usize) -> bool { + self.highlighter.highlight_char(line, pos) + } +} + +impl Validator for REPLHelper { + fn validate( + &self, + ctx: &mut validate::ValidationContext<'_>, + ) -> rustyline::Result { + self.validator.validate(ctx) + } + + fn validate_while_typing(&self) -> bool { + self.validator.validate_while_typing() + } +} + +fn commands_hints() -> HashSet { + let mut set = HashSet::new(); + set.insert(String::from("load")); + set.insert(String::from("unload")); + set.insert(String::from("call")); + set.insert(String::from("envs")); + set.insert(String::from("fs")); + set.insert(String::from("interface")); + set.insert(String::from("help")); + set +} diff --git a/tools/repl/src/logger.rs b/tools/repl/src/logger.rs new file mode 100644 index 00000000..c6611c8a --- /dev/null +++ b/tools/repl/src/logger.rs @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const WIT_MODULE_PATH: &str = "wasmer_interface_types_fl"; +const RUST_LOG_ENV_NAME: &str = "RUST_LOG"; + +pub(super) fn init_logger() { + use std::io::Write; + use std::env::var; + use log::LevelFilter::Info; + use fluence_sdk_main::WASM_LOG_ENV_NAME; + + match (var(RUST_LOG_ENV_NAME), var(WASM_LOG_ENV_NAME)) { + (Ok(_), _) => {} + (Err(_), Ok(wasm_log_env)) if !wasm_log_env.starts_with("off") => { + std::env::set_var(RUST_LOG_ENV_NAME, "trace") + } + _ => return, + }; + + env_logger::builder() + .format(|buf, record| { + match record.module_path() { + Some(module_path) if module_path.starts_with(WIT_MODULE_PATH) => { + writeln!(buf, "[host] {}", record.args()) + } + // because of the log_utf8_string implementation, log from a wasm module always has module path + None => writeln!(buf, "[host] {}", record.args()), + Some(module_path) => writeln!(buf, "[{}] {}", module_path, record.args()), + } + }) + // set a default level Info for Wasmer components + .filter(Some("cranelift_codegen"), Info) + .filter(Some("wasmer_wasi"), Info) + .filter(Some(WIT_MODULE_PATH), Info) + // the same for rustyline and fce + .filter(Some("rustyline"), Info) + .filter(Some("fce"), Info) + .init(); +} diff --git a/tools/repl/src/main.rs b/tools/repl/src/main.rs index 1d3daaaf..eda9b995 100644 --- a/tools/repl/src/main.rs +++ b/tools/repl/src/main.rs @@ -24,29 +24,24 @@ )] #![warn(rust_2018_idioms)] -/// Command-line tool intended to test Fluence FaaS. +/// Command-line tool intended to test Fluence App services. +mod editor; +mod logger; mod repl; +use logger::init_logger; +use editor::init_editor; + use repl::REPL; -use rustyline::completion::{Completer, FilenameCompleter, Pair}; -use rustyline::config::OutputStreamType; use rustyline::error::ReadlineError; -use rustyline::highlight::{Highlighter, MatchingBracketHighlighter}; -use rustyline::hint::{Hinter, HistoryHinter}; -use rustyline::validate::{self, MatchingBracketValidator, Validator}; -use rustyline::{Cmd, CompletionType, Config, Context, EditMode, Editor, KeyPress}; -use rustyline_derive::Helper; - -use std::borrow::Cow::{self, Borrowed, Owned}; -use std::collections::HashSet; const HISTORY_FILE_PATH: &str = ".repl_history"; -pub(crate) type Result = std::result::Result; +pub(crate) type ReplResult = std::result::Result; -fn main() -> Result<()> { +fn main() -> ReplResult<()> { init_logger(); let (args, _) = rustop::opts! { @@ -56,44 +51,29 @@ fn main() -> Result<()> { } .parse_or_exit(); - let config = Config::builder() - .history_ignore_space(true) - .completion_type(CompletionType::Fuzzy) - .edit_mode(EditMode::Emacs) - .output_stream(OutputStreamType::Stdout) - .build(); - - let repl_hinter = REPLHinter { - commands_hints: commands_hints(), - history_hinter: HistoryHinter {}, - }; - let repl_helper = REPLHelper { - completer: FilenameCompleter::new(), - highlighter: MatchingBracketHighlighter::new(), - hinter: repl_hinter, - colored_prompt: "".to_owned(), - validator: MatchingBracketValidator::new(), - }; - - let mut rl = Editor::with_config(config); - rl.set_helper(Some(repl_helper)); - rl.bind_sequence(KeyPress::Meta('N'), Cmd::HistorySearchForward); - rl.bind_sequence(KeyPress::Meta('P'), Cmd::HistorySearchBackward); + let mut rl = init_editor(); let _ = rl.load_history(HISTORY_FILE_PATH); - println!("Welcome to the Fluence FaaS REPL"); + println!( + "Welcome to the FCE REPL (version {})", + env!("CARGO_PKG_VERSION") + ); let mut repl = REPL::new(args.config_file_path)?; let mut count = 1; loop { let p = format!("\n{}> ", count); - rl.helper_mut().expect("No helper").colored_prompt = format!("\x1b[1;32m{}\x1b[0m", p); + rl.helper_mut() + .expect("No helper") + .set_prompt_color(format!("\x1b[1;32m{}\x1b[0m", p)); let readline = rl.readline(&p); match readline { Ok(line) => { rl.add_history_entry(line.as_str()); - repl.execute(line.split_whitespace()); + if !repl.execute(line.split_whitespace()) { + break; + } } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); @@ -117,146 +97,3 @@ fn main() -> Result<()> { Ok(()) } - -fn init_logger() { - use std::io::Write; - use std::env::var; - use log::LevelFilter::Info; - use fluence_sdk_main::WASM_LOG_ENV_NAME; - - const WIT_MODULE_PATH: &str = "wasmer_interface_types_fl"; - const RUST_LOG_ENV_NAME: &str = "RUST_LOG"; - - match (var(RUST_LOG_ENV_NAME), var(WASM_LOG_ENV_NAME)) { - (Ok(_), _) => {} - (Err(_), Ok(wasm_log_env)) if !wasm_log_env.starts_with("off") => { - std::env::set_var(RUST_LOG_ENV_NAME, "trace") - } - _ => return, - }; - - env_logger::builder() - .format(|buf, record| { - match record.module_path() { - Some(module_path) if module_path.starts_with(WIT_MODULE_PATH) => { - writeln!(buf, "[host] {}", record.args()) - } - // because of the log_utf8_string implementation, log from a wasm module always has module path - None => writeln!(buf, "[host] {}", record.args()), - Some(module_path) => writeln!(buf, "[{}] {}", module_path, record.args()), - } - }) - // set a default level Info for Wasmer components - .filter(Some("cranelift_codegen"), Info) - .filter(Some("wasmer_wasi"), Info) - .filter(Some(WIT_MODULE_PATH), Info) - // the same for rustyline and fce - .filter(Some("rustyline"), Info) - .filter(Some("fce"), Info) - .init(); -} - -#[derive(Helper)] -struct REPLHelper { - completer: FilenameCompleter, - highlighter: MatchingBracketHighlighter, - validator: MatchingBracketValidator, - hinter: REPLHinter, - colored_prompt: String, -} - -/// Tries to find hint from history if its failed from supported command list. -struct REPLHinter { - commands_hints: HashSet, - history_hinter: HistoryHinter, -} - -impl Completer for REPLHelper { - type Candidate = Pair; - - fn complete( - &self, - line: &str, - pos: usize, - ctx: &Context<'_>, - ) -> std::result::Result<(usize, Vec), ReadlineError> { - self.completer.complete(line, pos, ctx) - } -} - -impl Hinter for REPLHelper { - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { - if pos < line.len() { - return None; - } - - if let Some(hint) = self.hinter.history_hinter.hint(line, pos, ctx) { - return Some(hint); - } - - self.hinter - .commands_hints - .iter() - .filter_map(|hint| { - // expect hint after word complete, like redis cli, add condition: - // line.ends_with(" ") - if pos > 0 && hint.starts_with(&line[..pos]) { - Some(hint[pos..].to_owned()) - } else { - None - } - }) - .next() - } -} - -impl Highlighter for REPLHelper { - fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { - self.highlighter.highlight(line, pos) - } - - fn highlight_prompt<'b, 's: 'b, 'p: 'b>( - &'s self, - prompt: &'p str, - default: bool, - ) -> Cow<'b, str> { - if default { - Borrowed(&self.colored_prompt) - } else { - Borrowed(prompt) - } - } - - fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - Owned("\x1b[1m".to_owned() + hint + "\x1b[m") - } - - fn highlight_char(&self, line: &str, pos: usize) -> bool { - self.highlighter.highlight_char(line, pos) - } -} - -impl Validator for REPLHelper { - fn validate( - &self, - ctx: &mut validate::ValidationContext<'_>, - ) -> rustyline::Result { - self.validator.validate(ctx) - } - - fn validate_while_typing(&self) -> bool { - self.validator.validate_while_typing() - } -} - -fn commands_hints() -> HashSet { - let mut set = HashSet::new(); - set.insert(String::from("load")); - set.insert(String::from("unload")); - set.insert(String::from("call")); - set.insert(String::from("envs")); - set.insert(String::from("fs")); - set.insert(String::from("interface")); - set.insert(String::from("help")); - set -} diff --git a/tools/repl/src/repl.rs b/tools/repl/src/repl.rs index f559783f..840ff377 100644 --- a/tools/repl/src/repl.rs +++ b/tools/repl/src/repl.rs @@ -14,10 +14,14 @@ * limitations under the License. */ -use crate::Result; +mod print_state; + +use crate::ReplResult; use fluence_app_service::AppService; use fluence_app_service::TomlAppServiceConfig; +use print_state::print_envs; +use print_state::print_fs_state; use std::collections::HashMap; use std::fs; @@ -30,7 +34,7 @@ macro_rules! next_argument { $arg_name } else { println!($error_msg); - return; + return true; }; }; } @@ -40,12 +44,13 @@ pub(super) struct REPL { } impl REPL { - pub fn new>(config_file_path: Option) -> Result { + pub fn new>(config_file_path: Option) -> ReplResult { let app_service = Self::create_app_service(config_file_path)?; Ok(Self { app_service }) } - pub fn execute<'a>(&mut self, mut args: impl Iterator) { + /// Returns true, it should be the last executed command. + pub fn execute<'a>(&mut self, mut args: impl Iterator) -> bool { match args.next() { Some("new") => { match Self::create_app_service(args.next()) { @@ -60,7 +65,7 @@ impl REPL { let wasm_bytes = fs::read(module_path); if let Err(e) = wasm_bytes { println!("failed to read wasm module: {}", e); - return; + return true; } let start = Instant::now(); @@ -109,7 +114,7 @@ impl REPL { Ok(module_arg) => module_arg, Err(e) => { println!("incorrect arguments {}", e); - return; + return true; } }; @@ -132,14 +137,14 @@ impl REPL { Some("envs") => { next_argument!(module_name, args, "Module name should be specified"); match self.app_service.get_wasi_state(module_name) { - Ok(wasi_state) => Self::print_envs(wasi_state), + Ok(wasi_state) => print_envs(module_name, wasi_state), Err(e) => println!("{}", e), }; } Some("fs") => { next_argument!(module_name, args, "Module name should be specified"); match self.app_service.get_wasi_state(module_name) { - Ok(wasi_state) => Self::print_fs_state(wasi_state), + Ok(wasi_state) => print_fs_state(wasi_state), Err(e) => println!("{}", e), }; } @@ -147,27 +152,17 @@ impl REPL { let interface = self.app_service.get_full_interface(); print!("Application service interface:\n{}", interface); } - Some("h") | Some("help") | None => { - println!( - "Enter:\n\ - new [config_path] - to create a new AppService (current will be removed)\n\ - load - to load a new Wasm module into App service\n\ - unload - to unload Wasm module from AppService\n\ - call [args] - to call function with func_name of module with module_name\n\ - interface - to print public interface of current AppService\n\ - envs - to print environment variables of module with module_name\n\ - fs - to print filesystem state of module with module_name\n\ - h/help - to print this message\n\ - Ctrl-C - to exit" - ); - } - _ => { - println!("unsupported command"); + Some("q") | Some("quit") => { + return false; } + + _ => print_help(), } + + true } - fn create_app_service>(config_file_path: Option) -> Result { + fn create_app_service>(config_file_path: Option) -> ReplResult { let tmp_path: String = std::env::temp_dir().to_string_lossy().into(); let service_id = uuid::Uuid::new_v4().to_string(); @@ -184,48 +179,25 @@ impl REPL { let duration = start.elapsed(); println!( - "app service's created with service id = {}\nelapsed time {:?}", + "app service was created with service id = {}\nelapsed time {:?}", service_id, duration ); Ok(app_service) } - - fn print_envs(wasi_state: &wasmer_wasi::state::WasiState) { - let envs = &wasi_state.envs; - - println!("Environment variables:"); - for env in envs.iter() { - match String::from_utf8(env.clone()) { - Ok(string) => println!("{}", string), - Err(_) => println!("{:?}", env), - } - } - } - - fn print_fs_state(wasi_state: &wasmer_wasi::state::WasiState) { - let wasi_fs = &wasi_state.fs; - - println!("preopened file descriptors:\n{:?}\n", wasi_fs.preopen_fds); - - println!("name map:"); - for (name, inode) in &wasi_fs.name_map { - println!("{} - {:?}", name, inode); - } - - println!("\nfile descriptors map:"); - for (id, fd) in &wasi_fs.fd_map { - println!("{} - {:?}", id, fd); - } - - println!("\norphan file descriptors:"); - for (fd, inode) in &wasi_fs.orphan_fds { - println!("{:?} - {:?}", fd, inode); - } - - println!("\ninodes:"); - for (id, inode) in wasi_fs.inodes.iter().enumerate() { - println!("{}: {:?}", id, inode); - } - } +} + +fn print_help() { + println!( + "Commands:\n\n\ + new [config_path] create a new service (current will be removed)\n\ + load load a new Wasm module\n\ + unload unload a Wasm module\n\ + call [args] call function with given name from given module\n\ + interface print public interface of all loaded modules\n\ + envs print environment variables of a module\n\ + fs print filesystem state of a module\n\ + h/help print this message\n\ + q/quit/Ctrl-C exit" + ); } diff --git a/tools/repl/src/repl/print_state.rs b/tools/repl/src/repl/print_state.rs new file mode 100644 index 00000000..2c9b2932 --- /dev/null +++ b/tools/repl/src/repl/print_state.rs @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use wasmer_wasi::state::WasiState; + +pub(super) fn print_envs(module_name: &str, wasi_state: &WasiState) { + let envs = &wasi_state.envs; + if envs.is_empty() { + println!("{} don't have environment variables", module_name); + return; + } + + println!("Environment variables:"); + for env in envs.iter() { + match String::from_utf8(env.clone()) { + Ok(string) => println!("{}", string), + Err(_) => println!("{:?}", env), + } + } +} + +pub(super) fn print_fs_state(wasi_state: &WasiState) { + let wasi_fs = &wasi_state.fs; + + println!("preopened file descriptors:\n{:?}\n", wasi_fs.preopen_fds); + + println!("name map:"); + for (name, inode) in &wasi_fs.name_map { + println!("{} - {:?}", name, inode); + } + + println!("\nfile descriptors map:"); + for (id, fd) in &wasi_fs.fd_map { + println!("{} - {:?}", id, fd); + } + + println!("\norphan file descriptors:"); + for (fd, inode) in &wasi_fs.orphan_fds { + println!("{:?} - {:?}", fd, inode); + } + + println!("\ninodes:"); + for (id, inode) in wasi_fs.inodes.iter().enumerate() { + println!("{}: {:?}", id, inode); + } +}