From eea6f672584c3a1a5484ef99504460eb6133207d Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 09:59:52 +0200 Subject: [PATCH 01/32] Reverse search --- src/runtime/mod.rs | 19 +++++++ src/runtime/props.rs | 121 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 130 insertions(+), 10 deletions(-) diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index eb6e087..a639e97 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -312,6 +312,17 @@ fn print_out(out: String, to_cyrillic: bool, processor: &IOProcessor) { }; } +/// ### console_fmt +/// +/// Format console message + +fn console_fmt(out: String, to_cyrillic: bool, processor: &IOProcessor) -> String { + match to_cyrillic { + true => format!("{}", processor.text_to_cyrillic(&out)), + false => format!("{}", out) + } +} + /// ### shellsignal_to_signal /// /// Converts a signal received on prompt to a UnixSignal @@ -420,6 +431,14 @@ mod tests { print_err(String::from("Hello"), false, &iop); } + #[test] + fn test_runtime_console_fmt() { + let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian)); + //Out + assert_eq!(console_fmt(String::from("Hello"), true, &iop), String::from("Хэлло")); + assert_eq!(console_fmt(String::from("Hello"), false, &iop), String::from("Hello")); + } + #[test] fn test_runtime_shellsignal() { assert_eq!(shellsignal_to_signal(3).unwrap(), UnixSignal::Sigint); diff --git a/src/runtime/props.rs b/src/runtime/props.rs index c45c37d..accb6d5 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -23,7 +23,7 @@ * */ -use super::{print_err, print_out, resolve_command}; +use super::{print_err, print_out, console_fmt, resolve_command}; use crate::config::Config; use crate::shell::Shell; @@ -43,7 +43,8 @@ pub(super) struct RuntimeProps { interactive: bool, last_state: ShellState, state_changed: bool, - rev_search: bool, + rev_search: Option, // Reverse search match + rev_search_idx: usize, // Reverse search last match index history_index: usize } @@ -60,7 +61,8 @@ impl RuntimeProps { interactive: interactive, last_state: ShellState::Unknown, state_changed: true, - rev_search: false, + rev_search: None, + rev_search_idx: 0, history_index: 0 } } @@ -215,6 +217,8 @@ impl RuntimeProps { self.clear_buffer(); //Reset history index self.reset_history_index(); + // Unset reverse search + self.rev_search = None; console::println(String::new()); console::print(format!("{} ", shell.get_promptline(&self.processor))); }, @@ -231,7 +235,13 @@ impl RuntimeProps { self.move_right(); }, 7 => { //CTRL + G - //TODO: exit rev search + // exit rev search (and clear buffer) + self.rev_search = None; + self.rev_search_idx = 0; + //Abort input and go to newline + self.clear_buffer(); + console::println(String::new()); + console::print(format!("{} ", shell.get_promptline(&self.processor))); }, 8 => { //CTRL + H self.backspace(); @@ -248,7 +258,27 @@ impl RuntimeProps { console::print(format!("{} {}", shell.get_promptline(&self.processor), buffer::chars_to_string(&self.input_buffer))); }, 18 => { // CTRL + R - //TODO: rev search + // If reverse search is empty, set reverse search match + if self.rev_search.is_none() { + // Set reverse search to current input buffer + let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); + self.rev_search = Some(curr_stdin.clone()); + // Set index to first element (0) + self.rev_search_idx = 0; + // Write reverse-i-search prompt + console::rewrite(format!("{}`{}': ", console_fmt(String::from("(reverse-i-search)"), self.config.output_config.translate_output, &self.processor), curr_stdin), curr_stdin.len()); + } + // Find current input in history starting from bottom + if let Some(matched) = self.search_reverse(shell) { + // Set matched as current input + let prev_length: usize = self.input_buffer.len(); + self.input_buffer.clear(); + self.input_buffer = matched.chars().collect(); + // Set cursor to new length + self.input_buffer_cursor = self.input_buffer.len(); + // Print prompt + console::rewrite(matched, prev_length); + } }, _ => {} //Unhandled } @@ -273,6 +303,12 @@ impl RuntimeProps { self.input_buffer.insert(self.input_buffer_cursor, ch); self.input_buffer_cursor += 1; } + // If rev search, put new input buffer to reverse search + if self.rev_search.is_some() { + // Set reverse search to current input buffer + let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); + self.rev_search = Some(curr_stdin.clone()); + } //Print key console::print(k); }, @@ -293,6 +329,8 @@ impl RuntimeProps { fn perform_interactive_enter(&mut self, shell: &mut Shell) { //Reset history index self.reset_history_index(); + // Exit reverse search + self.rev_search = None; //Newline first console::println(String::new()); //Convert input buffer to string @@ -494,6 +532,32 @@ impl RuntimeProps { format!("{}", index) } } + + /// ### search_reverse + /// + /// Perform reverse search + /// Returns matched command in history + + fn search_reverse(&mut self, shell: &Shell) -> Option { + let current_match: String = match &self.rev_search { + Some(s) => s.clone(), + None => return None + }; + // Iterate over history + for i in self.rev_search_idx..shell.history.len() { + // Check if element at index matches (and is different than previous match) + if let Some(check_match) = shell.history.at(i) { + if check_match.contains(current_match.as_str()) { + // Update index + self.rev_search_idx = i + 1; // i + 1, in order to avoid same result at next cycle + // Return match + return Some(check_match.clone()) + } + } + } + // Return None if not found + None + } } #[cfg(test)] @@ -518,7 +582,8 @@ mod tests { assert_eq!(props.interactive, true); assert_eq!(props.last_state, ShellState::Unknown); assert_eq!(props.state_changed, true); - assert_eq!(props.rev_search, false); + assert_eq!(props.rev_search, None); + assert_eq!(props.rev_search_idx, 0); assert_eq!(props.history_index, 0); } @@ -668,6 +733,21 @@ mod tests { assert_eq!(props.input_buffer.len(), 0); assert_eq!(props.input_buffer_cursor, 0); assert_eq!(props.history_index, 0); //Reset history index + //CTRL R ( reverse search; set input buffer to ifc) + props.input_buffer = vec!['i', 'f', 'c']; + props.input_buffer_cursor = 3; + shell.history.push(String::from("ifconfig eth0")); + props.handle_input_event(InputEvent::Ctrl(18), &mut shell); + // Input buffer should now be 'ifconfig eth' + assert_eq!(props.input_buffer, vec!['i', 'f', 'c', 'o', 'n', 'f', 'i', 'g', ' ', 'e', 't', 'h', '0']); + assert_eq!(props.rev_search, Some(String::from("ifc"))); + assert_eq!(props.rev_search_idx, 1); // 0 + 1 + //CTRL G ( exit rev-search ) + props.handle_input_event(InputEvent::Ctrl(7), &mut shell); + assert_eq!(props.input_buffer.len(), 0); + assert_eq!(props.input_buffer_cursor, 0); + assert_eq!(props.rev_search, None); + assert_eq!(props.rev_search_idx, 0); // 0 //CTRL D props.input_buffer = vec!['l', 's', ' ', '-', 'l']; props.input_buffer_cursor = 5; @@ -682,8 +762,6 @@ mod tests { props.input_buffer_cursor = 1; props.handle_input_event(InputEvent::Ctrl(6), &mut shell); assert_eq!(props.input_buffer_cursor, 2); - //CTRL G TODO: rev search - props.handle_input_event(InputEvent::Ctrl(7), &mut shell); //CTRL H props.handle_input_event(InputEvent::Ctrl(8), &mut shell); assert_eq!(props.input_buffer, vec!['l', ' ', '-']); @@ -696,8 +774,6 @@ mod tests { props.handle_input_event(InputEvent::Ctrl(12), &mut shell); assert_eq!(props.input_buffer, vec!['l']); assert_eq!(props.input_buffer_cursor, 1); - //CTRL R TODO: rev search - props.handle_input_event(InputEvent::Ctrl(18), &mut shell); //Unhandled ctrl key props.handle_input_event(InputEvent::Ctrl(255), &mut shell); assert_eq!(props.input_buffer, vec!['l']); @@ -843,6 +919,31 @@ mod tests { assert_eq!(props.indent_history_index(1000), String::from("1000")); } + #[test] + fn test_runtimeprops_reverse_search() { + let mut props: RuntimeProps = new_runtime_props(true); + let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + props.update_state(ShellState::Idle); + //Prepare history + shell.history.push(String::from("pwd")); + shell.history.push(String::from("ifconfig")); + shell.history.push(String::from("ls -l")); + shell.history.push(String::from("ls")); + shell.history.push(String::from("ls -la")); + shell.history.push(String::from("lsd")); // Newer ls match + shell.history.push(String::from("if")); // Newer if match + // Perform reverse search + props.rev_search = Some(String::from("ls")); + props.rev_search_idx = 0; + assert_eq!(props.search_reverse(&mut shell), Some(String::from("lsd"))); + assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls -la"))); + assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls"))); + assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls -l"))); + assert_eq!(props.search_reverse(&mut shell), None); + assert_eq!(props.search_reverse(&mut shell), None); // No panic? + } + fn new_runtime_props(interactive: bool) -> RuntimeProps { RuntimeProps::new(interactive, Config::default(), IOProcessor::new(Language::Russian, new_translator(Language::Russian))) } From 1e3384c4c020ad61ca687fd185334fe85a5b0bd9 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 10:10:42 +0200 Subject: [PATCH 02/32] Allow failures on beta too --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index be5737d..00b833b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ rust: jobs: allow_failures: - rust: nightly + - rust: beta before_cache: | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then cargo install cargo-tarpaulin From 82645a02e1127a7dc48d05e8eea07e141d283f06 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 10:19:43 +0200 Subject: [PATCH 03/32] Updated dependencies --- CHANGELOG.md | 20 ++++- Cargo.lock | 162 +++++++++++++++++--------------------- Cargo.toml | 8 +- src/shell/mod.rs | 19 ++++- src/shell/proc/process.rs | 2 +- 5 files changed, 112 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b60b550..46ae466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,25 @@ # Changelog - [Changelog](#changelog) - - [Pyc 0.2.0 (27/06/2020)](#pyc-020-27062020) + - [Pyc 0.3.0](#pyc-030) + - [Pyc 0.2.0](#pyc-020) -## Pyc 0.2.0 (27/06/2020) +## Pyc 0.3.0 + +Released on 27.06.2020 + +- Reverse search for prompt + - KeyBinding: CTRL+R (enter reverse search) + - KeyBinding: CTRL+G (exit reverse search) +- Updated dependencies + - nix: 0.19.0 + - dirs: 3.0.1 + - whoami: 0.9.0 + - libgit2: 0.13.12 + +## Pyc 0.2.0 + +Released on 27.06.2020 - Prompt improvements - Added left/right arrows handler to move cursor diff --git a/Cargo.lock b/Cargo.lock index f9dd869..177c08a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.9" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" dependencies = [ "memchr", ] @@ -32,15 +32,15 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "base64" -version = "0.11.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" [[package]] name = "bitflags" @@ -59,20 +59,11 @@ dependencies = [ "constant_time_eq", ] -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - [[package]] name = "cc" -version = "1.0.50" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" dependencies = [ "jobserver", ] @@ -102,21 +93,19 @@ dependencies = [ [[package]] name = "dirs" -version = "2.0.2" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" dependencies = [ - "cfg-if", "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" dependencies = [ - "cfg-if", "libc", "redox_users", "winapi", @@ -133,9 +122,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if", "libc", @@ -144,9 +133,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.12.0" +version = "0.13.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26e07ef27260a78f7e8d218ebac2c72f2c4db50493741b190b6e8eade1da7c68" +checksum = "ca6f1a0238d7f8f8fd5ee642f4ebac4dbc03e03d1f78fbe7a3ede35dcf7e2224" dependencies = [ "bitflags", "libc", @@ -185,15 +174,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" [[package]] name = "libgit2-sys" -version = "0.11.0+0.99.0" +version = "0.12.14+1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d1459353d397a029fb18862166338de938e6be976606bd056cf8f1a912ecf" +checksum = "8f25af58e6495f7caf2919d08f212de550cfa3ed2f5e744988938ea292b9f549" dependencies = [ "cc", "libc", @@ -205,9 +194,9 @@ dependencies = [ [[package]] name = "libssh2-sys" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb70f29dc7c31d32c97577f13f41221af981b31248083e347b7f2c39225a6bc" +checksum = "ca46220853ba1c512fc82826d0834d87b06bcd3c2a42241b7de72f3d2fe17056" dependencies = [ "cc", "libc", @@ -219,9 +208,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.0.25" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" dependencies = [ "cc", "libc", @@ -231,15 +220,15 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] @@ -258,15 +247,14 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "nix" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +checksum = "85db2feff6bf70ebc3a4793191517d5f0331100a2f10f9bf93b5e5214f32b7b7" dependencies = [ "bitflags", "cc", "cfg-if", "libc", - "void", ] [[package]] @@ -277,9 +265,9 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.54" +version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ "autocfg", "cc", @@ -296,15 +284,15 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "ppv-lite86" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "pyc-shell" @@ -339,11 +327,11 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ - "c2-chacha", + "ppv-lite86", "rand_core", ] @@ -367,15 +355,15 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_users" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ "getrandom", "redox_syscall", @@ -384,9 +372,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.4" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" dependencies = [ "aho-corasick", "memchr", @@ -396,24 +384,24 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.16" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "rust-argon2" -version = "0.7.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" dependencies = [ "base64", "blake2b_simd", @@ -421,12 +409,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "smallvec" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" - [[package]] name = "tempfile" version = "3.1.0" @@ -443,9 +425,9 @@ dependencies = [ [[package]] name = "termios" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0fcee7b24a25675de40d5bb4de6e41b0df07bc9856295e7e2b3a3600c400c2" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" dependencies = [ "libc", ] @@ -459,6 +441,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -470,18 +458,18 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" dependencies = [ - "smallvec", + "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "url" @@ -505,15 +493,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" - -[[package]] -name = "void" -version = "1.0.2" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" [[package]] name = "wasi" @@ -523,15 +505,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "whoami" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08eb844b158ea881e81b94556eede7f7e306e4c7b976aad88f49e6e36dec391" +checksum = "7884773ab69074615cb8f8425d0e53f11710786158704fca70f53e71b0e05504" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", @@ -551,9 +533,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] diff --git a/Cargo.toml b/Cargo.toml index d1b1d69..5fc7f20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,13 +17,13 @@ readme = "README.md" [dependencies] yaml-rust = "0.4.3" getopts = "0.2.21" -nix = "0.17.0" -dirs = "2.0.2" +nix = "0.19.0" +dirs = "3.0.1" ansi_term = "0.12.1" regex = "1.3.4" lazy_static = "1.4.0" -whoami = "0.8.1" -git2 = "0.12.0" +whoami = "0.9.0" +git2 = "0.13.12" uuid = { version = "0.8.1", features = ["v4"] } tempfile = "3" termios = "0.3.2" diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 5fe4824..36da38b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -81,7 +81,7 @@ impl Shell { //Get process username let user: String = whoami::username(); //Get hostname - let hostname: String = whoami::host(); + let hostname: String = Shell::get_hostname(); let wrkdir: PathBuf = shell_process.wrkdir.clone(); Ok(Shell { process: shell_process, @@ -136,7 +136,7 @@ impl Shell { /// Refresh Shell Environment information pub fn refresh_env(&mut self) { self.props.username = whoami::username(); - self.props.hostname = whoami::host(); + self.props.hostname = Shell::get_hostname(); self.props.wrkdir = self.process.wrkdir.clone(); self.props.exit_status = self.process.exit_status; self.props.elapsed_time = self.process.exec_time; @@ -148,6 +148,16 @@ impl Shell { pub fn get_promptline(&mut self, processor: &IOProcessor) -> String { self.prompt.get_line(&self.props, processor) } + + /// ### get_hostname + /// + /// Get hostname without domain + fn get_hostname() -> String { + let full_hostname: String = whoami::hostname(); + let tokens: Vec<&str> = full_hostname.split(".").collect(); + String::from(*tokens.get(0).unwrap()) + } + } //@! Shell Props @@ -320,4 +330,9 @@ mod tests { //Verify exitcode to be 0 assert_eq!(shell_env.stop().unwrap(), 2); } + + #[test] + fn test_shell_hostname() { + assert_ne!(Shell::get_hostname(), String::from("")); + } } diff --git a/src/shell/proc/process.rs b/src/shell/proc/process.rs index d211fbd..26b32d8 100644 --- a/src/shell/proc/process.rs +++ b/src/shell/proc/process.rs @@ -62,7 +62,7 @@ impl ShellProc { Err(err) => return Err(err) }; //Fork process - match nix::unistd::fork() { + match unsafe {nix::unistd::fork()} { Ok(nix::unistd::ForkResult::Parent { child, .. }) => { //Prepare echo command //FIXME: handle fish $status From 9c7e6860fb8251088905c950b96bf3249501a012 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 10:20:43 +0200 Subject: [PATCH 04/32] Working on 0.3.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 177c08a..f7e4cb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,7 +296,7 @@ checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" [[package]] name = "pyc-shell" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ansi_term", "dirs", diff --git a/Cargo.toml b/Cargo.toml index 5fc7f20..8b55bae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyc-shell" -version = "0.2.0" +version = "0.3.0" authors = ["Christian Visintin"] edition = "2018" license = "GPL-3.0" diff --git a/README.md b/README.md index 0a2ff73..f6a7621 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Pyc -[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Stars](https://img.shields.io/github/stars/ChristianVisintin/Pyc.svg)](https://github.com/ChristianVisintin/Pyc) [![Issues](https://img.shields.io/github/issues/ChristianVisintin/Pyc.svg)](https://github.com/ChristianVisintin/Pyc/issues) [![Crates.io](https://img.shields.io/badge/crates.io-v0.2.0-orange.svg)](https://crates.io/crates/pyc-shell) [![Build](https://api.travis-ci.org/ChristianVisintin/Pyc.svg?branch=master)](https://travis-ci.org/ChristianVisintin/Pyc) [![codecov](https://codecov.io/gh/ChristianVisintin/Pyc/branch/master/graph/badge.svg)](https://codecov.io/gh/ChristianVisintin/Pyc) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Stars](https://img.shields.io/github/stars/ChristianVisintin/Pyc.svg)](https://github.com/ChristianVisintin/Pyc) [![Issues](https://img.shields.io/github/issues/ChristianVisintin/Pyc.svg)](https://github.com/ChristianVisintin/Pyc/issues) [![Crates.io](https://img.shields.io/badge/crates.io-v0.3.0-orange.svg)](https://crates.io/crates/pyc-shell) [![Build](https://api.travis-ci.org/ChristianVisintin/Pyc.svg?branch=master)](https://travis-ci.org/ChristianVisintin/Pyc) [![codecov](https://codecov.io/gh/ChristianVisintin/Pyc/branch/master/graph/badge.svg)](https://codecov.io/gh/ChristianVisintin/Pyc) ~ Use your alphabet with your favourite shell ~ Developed by Christian Visintin -Current version: [0.2.0 (27/06/2020)](./CHANGELOG.md#pyc-020-27062020) +Current version: [0.3.0 (??/??/2020)](./CHANGELOG.md#pyc-030) --- From b8f3723482f5f6c8f7fd07ec8f6fc803c47f62c4 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 10:52:04 +0200 Subject: [PATCH 05/32] New configuration keys: commit_prepend, commit_append --- CHANGELOG.md | 2 ++ README.md | 4 +++ pyc.yml | 2 ++ src/config/mod.rs | 24 ++++++++++++++++- src/shell/prompt/mod.rs | 58 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ae466..f831b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Released on 27.06.2020 +- Prompt configuration: + - new ```commit_prepend``` and ```commit_append``` keys - Reverse search for prompt - KeyBinding: CTRL+R (enter reverse search) - KeyBinding: CTRL+G (exit reverse search) diff --git a/README.md b/README.md index f6a7621..32377cb 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,8 @@ prompt: git: branch: "on  " commit_ref_len: 8 + commit_prepend: "(" + commit_append: ")" ``` - shell: Shell configuration @@ -180,6 +182,8 @@ prompt: - git: git module - branch: string to write before writing branch name - commit_ref_len: length of commit reference + - commit_prepend: string to prepend to commit ref + - commit_append: string to append to commit ref ### Prompt Line Configuration diff --git a/pyc.yml b/pyc.yml index 09c9619..f2302b8 100644 --- a/pyc.yml +++ b/pyc.yml @@ -22,3 +22,5 @@ prompt: git: branch: "on  " commit_ref_len: 8 + commit_prepend: "(" + commit_append: ")" diff --git a/src/config/mod.rs b/src/config/mod.rs index 7cd06d7..83fdaff 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -63,6 +63,8 @@ pub struct PromptConfig { pub rc_err: String, pub git_branch: String, pub git_commit_ref: usize, + pub git_commit_prepend: Option, + pub git_commit_append: Option } #[derive(Copy, Clone, PartialEq, fmt::Debug)] @@ -323,6 +325,8 @@ impl PromptConfig { rc_err: String::from("✖"), git_branch: String::from("on "), git_commit_ref: 8, + git_commit_append: None, + git_commit_prepend: None } } @@ -406,6 +410,18 @@ impl PromptConfig { Ok(ret) => ret, Err(err) => return Err(err), }; + //Git commit prepend + let git_commit_prepend: Option = + match ConfigParser::get_string(&git, String::from("commit_prepend")) { + Ok(ret) => Some(ret), + Err(_) => None, + }; + //Git commit append + let git_commit_append: Option = + match ConfigParser::get_string(&git, String::from("commit_append")) { + Ok(ret) => Some(ret), + Err(_) => None, + }; Ok(PromptConfig { prompt_line: prompt_line, history_size: history_size, @@ -417,6 +433,8 @@ impl PromptConfig { rc_err: rc_err, git_branch: git_branch, git_commit_ref: git_commit_ref, + git_commit_append: git_commit_append, + git_commit_prepend: git_commit_prepend }) } } @@ -438,6 +456,8 @@ mod tests { assert_eq!(prompt_config.break_str, String::from("❯")); assert_eq!(prompt_config.git_branch, String::from("on ")); assert_eq!(prompt_config.git_commit_ref, 8); + assert_eq!(prompt_config.git_commit_prepend, None); + assert_eq!(prompt_config.git_commit_append, None); assert_eq!(prompt_config.history_size, 256); assert_eq!(prompt_config.min_duration, 2000); assert_eq!(prompt_config.rc_err, String::from("✖")); @@ -614,7 +634,7 @@ mod tests { #[test] fn test_config_prompt() { - let config: String = String::from("prompt:\n prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n history_size: 1024\n translate: true\n break:\n enabled: false\n with: \">\"\n duration:\n min_elapsed_time: 5000\n rc:\n ok: \"^_^\"\n error: \"x_x\"\n git:\n branch: \"on \"\n commit_ref_len: 4\n"); + let config: String = String::from("prompt:\n prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n history_size: 1024\n translate: true\n break:\n enabled: false\n with: \">\"\n duration:\n min_elapsed_time: 5000\n rc:\n ok: \"^_^\"\n error: \"x_x\"\n git:\n branch: \"on \"\n commit_ref_len: 4\n commit_prepend: \"(\"\n commit_append: \")\"\n"); let config: Config = Config::parse_config_str(config).ok().unwrap(); //Verify config parameters let prompt_config: PromptConfig = config.prompt_config; @@ -623,6 +643,8 @@ mod tests { assert_eq!(prompt_config.break_str, String::from(">")); assert_eq!(prompt_config.git_branch, String::from("on ")); assert_eq!(prompt_config.git_commit_ref, 4); + assert_eq!(prompt_config.git_commit_prepend, Some(String::from("("))); + assert_eq!(prompt_config.git_commit_append, Some(String::from(")"))); assert_eq!(prompt_config.history_size, 1024); assert_eq!(prompt_config.min_duration, 5000); assert_eq!(prompt_config.rc_err, String::from("x_x")); diff --git a/src/shell/prompt/mod.rs b/src/shell/prompt/mod.rs index 8f2ed31..7528dc1 100644 --- a/src/shell/prompt/mod.rs +++ b/src/shell/prompt/mod.rs @@ -86,6 +86,8 @@ struct RcOptions { struct GitOptions { pub branch: String, pub commit_ref_len: usize, + pub commit_ref_prepend: Option, + pub commit_ref_append: Option } impl ShellPrompt { @@ -110,6 +112,8 @@ impl ShellPrompt { true => Some(GitOptions::new( &prompt_opt.git_branch, prompt_opt.git_commit_ref, + &prompt_opt.git_commit_prepend, + &prompt_opt.git_commit_append )), false => None, }; @@ -231,7 +235,18 @@ impl ShellPrompt { self.cache.get_cached_git().unwrap(), self.git_opt.as_ref().unwrap().commit_ref_len, ) { - Some(commit) => commit, + Some(commit) => { + // Format commit + let commit_prepend: String = match &self.git_opt.as_ref().unwrap().commit_ref_prepend { + Some(s) => s.clone(), + None => String::from("") + }; + let commit_append: String = match &self.git_opt.as_ref().unwrap().commit_ref_append { + Some(s) => s.clone(), + None => String::from("") + }; + format!("{}{}{}", commit_prepend, commit, commit_append) + }, None => String::from(""), } } @@ -311,10 +326,12 @@ impl GitOptions { /// ### new /// /// Instantiate a new GitOptions with the provided parameters - pub fn new(branch: &String, commit: usize) -> GitOptions { + pub fn new(branch: &String, commit: usize, commit_prepend: &Option, commit_append: &Option) -> GitOptions { GitOptions { branch: branch.clone(), commit_ref_len: commit, + commit_ref_prepend: commit_prepend.clone(), + commit_ref_append: commit_append.clone() } } } @@ -439,11 +456,38 @@ mod tests { //Branch should be none let branch: String = git::get_branch(&repo).unwrap(); let commit: String = git::get_commit(&repo, 8).unwrap(); - let mut prompt_config_default = PromptConfig::default(); + let mut prompt_config = PromptConfig::default(); //Update prompt line - prompt_config_default.prompt_line = - String::from("${USER}@${HOSTNAME}:${WRKDIR} ${GIT_BRANCH}:${GIT_COMMIT}"); - let mut prompt: ShellPrompt = ShellPrompt::new(&prompt_config_default); + prompt_config.prompt_line = + String::from("${USER}@${HOSTNAME}:${WRKDIR} ${GIT_BRANCH} ${GIT_COMMIT}"); + let mut prompt: ShellPrompt = ShellPrompt::new(&prompt_config); + let iop: IOProcessor = get_ioprocessor(); + let mut shellenv: ShellProps = get_shellenv(); + shellenv.elapsed_time = Duration::from_millis(5100); + shellenv.wrkdir = PathBuf::from("./"); + //Print first in latin + let _ = prompt.get_line(&shellenv, &iop); + prompt.translate = true; + //Then in cyrillic + let _ = prompt.get_line(&shellenv, &iop); + //Get prompt line + let prompt_line: String = prompt.process_prompt(&shellenv, &iop); + let expected_prompt_line = String::from(format!( + "{}@{}:{} on {} {}", + shellenv.username.clone(), + shellenv.hostname.clone(), + shellenv.wrkdir.display(), + branch, + commit + )); + assert_eq!(prompt_line, expected_prompt_line); + //Terminate shell at the end of a test + //terminate_shell(&mut shellenv); + println!("\n"); + // @! Set prepend / append + prompt_config.git_commit_append = Some(String::from(")")); + prompt_config.git_commit_prepend = Some(String::from("(")); + let mut prompt: ShellPrompt = ShellPrompt::new(&prompt_config); let iop: IOProcessor = get_ioprocessor(); let mut shellenv: ShellProps = get_shellenv(); shellenv.elapsed_time = Duration::from_millis(5100); @@ -456,7 +500,7 @@ mod tests { //Get prompt line let prompt_line: String = prompt.process_prompt(&shellenv, &iop); let expected_prompt_line = String::from(format!( - "{}@{}:{} on {}:{}", + "{}@{}:{} on {} ({})", shellenv.username.clone(), shellenv.hostname.clone(), shellenv.wrkdir.display(), From 04b6fcba719f8c3890e3c804b862a11eb65296c1 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 10:59:25 +0200 Subject: [PATCH 06/32] Updated releases plan --- README.md | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 32377cb..6c7890a 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,11 @@ Current version: [0.3.0 (??/??/2020)](./CHANGELOG.md#pyc-030) - [Fish doesn't work](#fish-doesnt-work) - [Shell alias not working](#shell-alias-not-working) - [Text editors dont' work](#text-editors-dont-work) - - [Upcoming Features](#upcoming-features) - - [Pyc 0.3.0](#pyc-030) - - [Others](#others) + - [Upcoming Features and Releases](#upcoming-features-and-releases) + - [Development Status](#development-status) + - [Planned releases](#planned-releases) + - [Pyc 0.3.0](#pyc-030) + - [Pyc 0.4.0](#pyc-040) - [Contributions](#contributions) - [Changelog](#changelog) - [License](#license) @@ -297,21 +299,37 @@ I will try to fix this issue --- -## Upcoming Features +## Upcoming Features and Releases -### Pyc 0.3.0 +### Development Status -- New translators: - - Devanagari - - Serbian - - Ukrainian -- Fish support +Pyc-shell is an active project, development effort is minimum at the moment due to my spare time and to the little interest from the community. In addition I'm working on other bigger projects. + +### Planned releases + +No new releases are planned for 2020. + +#### Pyc 0.3.0 -### Others +Planned for December 2020 -- other translators: +- translators: + - devanagari + - hangul + - ukrainian + - serbian +- reverse search +- new configurations keys + +#### Pyc 0.4.0 + +Planned for 2021 + +- translators: - Macedonian - Montenegrin +- Fish support +- Text editors support ## Contributions From af77019b281f0b950c9c5a56ec78ee8d5ab36f41 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 11:00:49 +0200 Subject: [PATCH 07/32] typo --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 6c7890a..19a2cf8 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,6 @@ Pyc-shell is an active project, development effort is minimum at the moment due ### Planned releases -No new releases are planned for 2020. - #### Pyc 0.3.0 Planned for December 2020 From 973e43943ad78fe76a53e870cdec8e4de6daa820 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 11:07:28 +0200 Subject: [PATCH 08/32] Contributing updated --- CONTRIBUTING.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f8f6cf..aa29f6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -171,7 +171,18 @@ As we've seen before, a translator is a struct which implements the ```Translato Let's see the steps to implement the translators, imagine we're going to implement the Emoji alphabet: -1. Define translator +1. Define translation table + + Define the translations rules as did for example with [Russian](docs/translators/ru.md) and create a markdown document with exact same syntax and reporting the standard used to create the translator. + + To implement a Translator there are basically 4 rules: + + - If a standard for transliteration exists, please follow it as much it's possible. + - All characters from source alphabet must be transliterated to latin + - All latin characters must have a transliteration to source alphabet + - Write tests to cover all its combinations. + +2. Define translator Go to ```src/translator/lang/mod.rs``` @@ -207,7 +218,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme mod emoji; ``` -2. Add it to translator constructor +3. Add it to translator constructor Move to ```src/translator/mod.rs``` Add it to new_translator @@ -221,9 +232,10 @@ Let's see the steps to implement the translators, imagine we're going to impleme } ``` -3. Implement Translator +4. Implement Translator First create a new file ```src/translator/lang/{ALPHABET}.rs```. + Then following the rules you've defined at point 1, implement it. ```rs use super::{Emoji, Translator}; @@ -238,16 +250,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme } ``` - To implement a Translator there are basically 4 rules: - - - If a standard for transliteration exists, please follow it as much it's possible. - - All characters from source alphabet must be transliterated to latin - - All latin characters must have a transliteration to source alphabet - - Write tests to cover all its combinations. - - To see how a Translator is implemented consider [Russian.rs](https://github.com/ChristianVisintin/Pyc/blob/master/src/translator/russian.rs) as example. - -4. Add it to Prompt Language module +5. Add it to Prompt Language module Move to ```src/shell/prompt/modules/language.rs``` @@ -268,7 +271,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme } ``` -5. Add it to str_to_language in main +6. Add it to str_to_language in main Move to ```src/main.rs``` @@ -291,7 +294,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme } ``` -6. Write documentation +7. Write documentation Add the alphabet in the README in the ```Supported alphabets```. @@ -299,7 +302,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme - ![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Emoji.png) Emoji - According to emoji standard [EMOJI-2020](https://en.wikipedia.org/wiki/EMOJI-2020) with some differences ([See here](./docs/translators/emoji.md)) ``` - Finally write the Emoji doc in ```docs/translators/emoji.md``` + Finally review the Emoji doc in ```docs/translators/emoji.md``` Here you have to write a lookup table which describes how you transliterate the characters from the two alphabets. ### Implement Prompt Modules From 19056ec8158c2964a26af102844447f8a522a277 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 11:26:20 +0200 Subject: [PATCH 09/32] Contributing updated --- CONTRIBUTING.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aa29f6b..d443b87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -175,12 +175,11 @@ Let's see the steps to implement the translators, imagine we're going to impleme Define the translations rules as did for example with [Russian](docs/translators/ru.md) and create a markdown document with exact same syntax and reporting the standard used to create the translator. - To implement a Translator there are basically 4 rules: + To implement a Translator there are basically 3 rules: - If a standard for transliteration exists, please follow it as much it's possible. - All characters from source alphabet must be transliterated to latin - All latin characters must have a transliteration to source alphabet - - Write tests to cover all its combinations. 2. Define translator @@ -235,7 +234,7 @@ Let's see the steps to implement the translators, imagine we're going to impleme 4. Implement Translator First create a new file ```src/translator/lang/{ALPHABET}.rs```. - Then following the rules you've defined at point 1, implement it. + Then following the rules you've defined at point 1, implement it and write tests to cover all its combinations. ```rs use super::{Emoji, Translator}; From ba01e4733e5ee82c50bb4f801198639d7997f151 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 11:33:49 +0200 Subject: [PATCH 10/32] Working on ukrainian --- CHANGELOG.md | 4 +- README.md | 26 ++++-------- docs/translators/ua.md | 94 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 docs/translators/ua.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f831b57..92a2d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ ## Pyc 0.3.0 -Released on 27.06.2020 +Released on ?? +- New translators: + - 🇺🇦 Ukrainian - Prompt configuration: - new ```commit_prepend``` and ```commit_append``` keys - Reverse search for prompt diff --git a/README.md b/README.md index 19a2cf8..19305a1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ Current version: [0.3.0 (??/??/2020)](./CHANGELOG.md#pyc-030) - [Upcoming Features and Releases](#upcoming-features-and-releases) - [Development Status](#development-status) - [Planned releases](#planned-releases) - - [Pyc 0.3.0](#pyc-030) - [Pyc 0.4.0](#pyc-040) - [Contributions](#contributions) - [Changelog](#changelog) @@ -71,18 +70,18 @@ Basically Рус is a shell interface, which means that it reads the user input, ## Supported alphabets -- ![by](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Belarus.png) Belarusian Cyrillic - According to russian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/by.md)) -- ![bg](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bulgaria.png) Bulgarian Cyrillic - According to russian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ru.md)) +- ![by](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Belarus.png) Belarusian Cyrillic - According to belarusian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/by.md)) +- ![bg](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bulgaria.png) Bulgarian Cyrillic - According to bulgarian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ru.md)) - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) Russian Cyrillic - According to russian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ru.md)) +- ![ua](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Ukraine.png) Ukrainian Cyrillic - According to ukrainian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ua.md)) ### Planned alphabets - ![in](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/India.png) Devanagari - *Coming soon (0.3.0)* -- ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) Hangŭl - According to [Revised Romanization of Korean](https://en.wikipedia.org/wiki/Revised_Romanization_of_Korean) *Coming soon (0.3.0)* -- ![mk](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Macedonia.png) Macedonian Cyrillic - *TBD* -- ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *TBD* +- ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) Hangŭl - According to [Revised Romanization of Korean](https://en.wikipedia.org/wiki/Revised_Romanization_of_Korean) - *Coming soon (0.3.0)* +- ![mk](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Macedonia.png) Macedonian Cyrillic - *Coming soon (0.4.0)* +- ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *Coming soon (0.4.0)* - ![rs](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Serbia.png)![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bosnia-and-Herzegovina.png) Serbian Cyrillic - *Coming soon (0.3.0)* -- ![ua](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Ukraine.png) Ukrainian Cyrillic - *Coming soon (0.3.0)* Other alphabets are not planned for the moment. @@ -167,6 +166,7 @@ prompt: - **Belarusian**: by | бел - **Bulgarian**: bg | бг | блг - **Russian**: ru | рус + - **Ukrainian** : ua | укр - output: output configuration - translate: indicates to pyc whether the output has to be converted to cyrillic or not - prompt: Prompt configuration (See [Prompt Configuration](#prompt-line-configuration)) @@ -307,18 +307,6 @@ Pyc-shell is an active project, development effort is minimum at the moment due ### Planned releases -#### Pyc 0.3.0 - -Planned for December 2020 - -- translators: - - devanagari - - hangul - - ukrainian - - serbian -- reverse search -- new configurations keys - #### Pyc 0.4.0 Planned for 2021 diff --git a/docs/translators/ua.md b/docs/translators/ua.md new file mode 100644 index 0000000..767975a --- /dev/null +++ b/docs/translators/ua.md @@ -0,0 +1,94 @@ +# Ukrainian Transliteration + +- [Ukrainian Transliteration](#ukrainian-transliteration) + - [Cyrillic to latin](#cyrillic-to-latin) + - [Latin to Cyrillic](#latin-to-cyrillic) + +🇺🇦 This document contains the documentation for the rules used to transliterate Ukrainian Cyrillic 🇺🇦 + +## Cyrillic to latin + +The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) standard with some differences. The entire conversion table is illustrated here below: + +| Ukrainian | Latin | Notes | +|-----------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| А | A | | +| Б | B | | +| К | C | K is translated into C, only when not followed ```'Е','Э','И','Й','Ы','ъ'```, or it is preceeded by ```'К','А','И','О'```. You can force a 'C' using ```'Кь'``` | +| Ч | CH | | +| Ц | Z | | +| Д | D | | +| Е | E | | +| Ф | F | | +| Г, Ґ | G | | +| Х | H | | +| И, І | I | | +| Ї | YI | | +| Ж | J | | +| Й | J | | +| Ё | JO | | +| К | K | K is converted to latin K only when followed by ```'Е','Э','И','Й','Ы','ъ'``` ,or it is NOT preceeded by ```'К','А','И','О'``` .You can force a K using ```'КЪ'``` | +| Л | L | | +| М | M | | +| Н | N | | +| О | O | | +| П | P | | +| Кю | Q | | +| Р | R | | +| С | S | | +| Ш | SH | | +| Щ | SHH | | +| Т | T | | +| У | U | | +| В | V | | +| Вь | W | | +| КС | X | | +| Ы | Y | | +| Я | YA | | +| Є | YE | | +| Ю | YU | | +| З | Z | | +| ʼ | ' | | +| Ь | ` | | +| № | # | | + +## Latin to Cyrillic + +| Latin | Ukrainian | Notes | +|-------|-----------|---------------------------------------------------| +| А | A | | +| B | Б | | +| C | К | Unless if followed by H | +| CH | Ч | | +| Ч | CH | | +| D | Д | | +| E | Е | Unkess if preceeded by 'Y' | +| F | Ф | | +| G | Г | | +| G | ДЖ | If g is followed by Y, E, I | +| H | Х | | +| I | И | Unless if followed be U, A, O or preceeded by 'y' | +| IU | Ю | | +| IA | Я | | +| IO | Ё | | +| J | Ж | | +| K | К | | +| L | Л | | +| M | М | | +| N | Н | | +| O | О | | +| P | П | | +| Q | КЮ | | +| R | Р | | +| S | С | Unless if followed by H | +| Sh | Ш | | +| T | Т | | +| TS | Ц | Unless if followed by S | +| U | У | | +| V | В | | +| W | У | | +| X | КС | | +| Y | Ы | Unless if followed by E | +| YE | Є | | +| YI | Ї | | +| Z | З | | From f3af55858153a65d580d4f8d721823ecff757301 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 12:10:53 +0200 Subject: [PATCH 11/32] Ukrainian translator --- docs/translators/ua.md | 9 +- src/main.rs | 1 + src/shell/prompt/modules/language.rs | 14 + src/translator/lang/mod.rs | 7 +- src/translator/lang/ukrainian.rs | 754 +++++++++++++++++++++++++++ src/translator/mod.rs | 2 + 6 files changed, 780 insertions(+), 7 deletions(-) create mode 100644 src/translator/lang/ukrainian.rs diff --git a/docs/translators/ua.md b/docs/translators/ua.md index 767975a..cae6677 100644 --- a/docs/translators/ua.md +++ b/docs/translators/ua.md @@ -25,8 +25,6 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | И, І | I | | | Ї | YI | | | Ж | J | | -| Й | J | | -| Ё | JO | | | К | K | K is converted to latin K only when followed by ```'Е','Э','И','Й','Ы','ъ'``` ,or it is NOT preceeded by ```'К','А','И','О'``` .You can force a K using ```'КЪ'``` | | Л | L | | | М | M | | @@ -43,7 +41,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | В | V | | | Вь | W | | | КС | X | | -| Ы | Y | | +| Й | Y | | | Я | YA | | | Є | YE | | | Ю | YU | | @@ -67,10 +65,9 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | G | Г | | | G | ДЖ | If g is followed by Y, E, I | | H | Х | | -| I | И | Unless if followed be U, A, O or preceeded by 'y' | +| I | И | Unless if followed be U, A or preceeded by 'y' | | IU | Ю | | | IA | Я | | -| IO | Ё | | | J | Ж | | | K | К | | | L | Л | | @@ -88,7 +85,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | V | В | | | W | У | | | X | КС | | -| Y | Ы | Unless if followed by E | +| Y | Й | Unless if followed by E | | YE | Є | | | YI | Ї | | | Z | З | | diff --git a/src/main.rs b/src/main.rs index 969cd77..c58d9eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,7 @@ fn str_to_language(lang: String) -> Language { "ru" | "рус" => Language::Russian, "by" | "бел" => Language::Belarusian, "bg" | "бг" | "блг" => Language::Bulgarian, + "ua" | "укр" => Language::Ukrainian, _ => { eprintln!( "{}", diff --git a/src/shell/prompt/modules/language.rs b/src/shell/prompt/modules/language.rs index 6a912bd..6407d38 100644 --- a/src/shell/prompt/modules/language.rs +++ b/src/shell/prompt/modules/language.rs @@ -64,6 +64,16 @@ pub fn language_to_str(language: Language) -> String { PromptColor::Red.to_string(), lang_str.chars().nth(2).unwrap_or(' '), PromptColor::Reset.to_string() + )), + Language::Ukrainian => String::from(format!( + "{}{}{}{}{}{}{}", + PromptColor::Cyan.to_string(), + lang_str.chars().nth(0).unwrap_or(' '), + PromptColor::Yellow.to_string(), + lang_str.chars().nth(1).unwrap_or(' '), + PromptColor::Cyan.to_string(), + lang_str.chars().nth(2).unwrap_or(' '), + PromptColor::Reset.to_string() )) } } @@ -87,5 +97,9 @@ mod tests { let expected_str = String::from("\x1b[37mр\x1b[34mу\x1b[31mс\x1b[0m"); println!("{}", language_to_str(Language::Russian)); assert_eq!(language_to_str(Language::Russian), expected_str); + // Ukrainian + let expected_str = String::from("\x1b[36mу\x1b[33mк\x1b[36mр\x1b[0m"); + println!("{}", language_to_str(Language::Ukrainian)); + assert_eq!(language_to_str(Language::Ukrainian), expected_str); } } diff --git a/src/translator/lang/mod.rs b/src/translator/lang/mod.rs index 4bf7c69..d33e009 100644 --- a/src/translator/lang/mod.rs +++ b/src/translator/lang/mod.rs @@ -32,6 +32,7 @@ pub enum Language { Belarusian, Bulgarian, Russian, + Ukrainian, } /// ## Languages @@ -42,16 +43,19 @@ pub enum Language { pub(crate) struct Belarusian {} pub(crate) struct Bulgarian {} pub(crate) struct Russian {} +pub(crate) struct Ukrainian {} mod belarusian; mod bulgarian; mod russian; +mod ukrainian; impl ToString for Language { fn to_string(&self) -> String { match self { Language::Belarusian => String::from("бел"), Language::Bulgarian => String::from("блг"), - Language::Russian => String::from("рус") + Language::Russian => String::from("рус"), + Language::Ukrainian => String::from("укр") } } } @@ -66,6 +70,7 @@ mod tests { assert_eq!(Language::Belarusian.to_string(), String::from("бел")); assert_eq!(Language::Bulgarian.to_string(), String::from("блг")); assert_eq!(Language::Russian.to_string(), String::from("рус")); + assert_eq!(Language::Ukrainian.to_string(), String::from("укр")); } } diff --git a/src/translator/lang/ukrainian.rs b/src/translator/lang/ukrainian.rs new file mode 100644 index 0000000..63ad076 --- /dev/null +++ b/src/translator/lang/ukrainian.rs @@ -0,0 +1,754 @@ +//! ### Ukrainian +//! +//! `ukrainian` language implementation of Translator trait + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use super::Ukrainian; +use super::super::Translator; + +impl Translator for Ukrainian { + /// ### Ukrainian translator + + /// Converts a string which contains ukrainian cyrillic characters into a latin string. + /// Characters between '"' (quotes) are escaped, expressions inside escaped blocks are translitarated anyway + /// Transliteration according to GOST 7.79-2000 + fn to_latin(&self, input: &String) -> String { + let mut output = String::new(); + let mut skip_counter: usize = 0; + for (i, c) in input.chars().enumerate() { + if skip_counter > 0 { + //Skip cycles + skip_counter -= 1; //Decrement skip counter + continue; + } + //Push transliterated character + let unchanged_str: String; + output.push_str(match c { + 'А' => "A", + 'а' => "a", + 'Б' => "B", + 'б' => "b", + 'В' => { + //If following character is 'ь', then is always W + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'ь' | 'Ь' => { + skip_counter += 1; //Skip character + "W" + } + _ => "V", + } + } + None => "V", + } + } + 'в' => + //If following character is 'ь', then is always W + { + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'ь' | 'Ь' => { + skip_counter += 1; //Skip character + "w" + } + _ => "v", + } + } + None => "v", + } + } + 'Г' | 'Ґ' => "G", + 'г' | 'ґ' => "g", + 'Д' => "D", + 'д' => "d", + 'Е' => "E", + 'е' => "e", + 'Є' => "YE", + 'є' => "ye", + 'Ж' => "J", + 'ж' => "j", + 'З' => "Z", + 'з' => "z", + 'И' | 'І' => "I", + 'и' | 'і' => "i", + 'Ї' => "YI", + 'ї' => "yi", + 'К' => { + //K is very complex, sometimes it is C, sometimes is K or even Q or X + //If following letter is in (E, I, Y), then is K + //If following character is 'ʼ', then is always K + //If following character is 'ь', then is always C + //If following character is 'y', then is always Q + //If follwing character is 'с', then is always X + match input.chars().nth(i + 1) { + Some(ch) => { + //Check following character + match ch { + 'Є' | 'Е' | 'И' | 'Й' | 'є' | 'е' | 'и' | 'й' => "K", + ' ' => { + //Check previous character + match i { + 0 => "K", + _ => match input.chars().nth(i - 1) { + Some(ch) => match ch { + 'К' | 'А' | 'И' | 'О' | 'к' | 'а' | 'и' | 'о' | ' ' => "K", + _ => "C", + }, + None => "K", + }, + } + } + 'Ю' | 'ю' => { + skip_counter += 1; + "Q" + } + 'с' | 'С' => { + skip_counter += 1; + "X" + } + 'ʼ' => { + skip_counter += 1; //Skip next character + "K" + } + 'ь' | 'Ь' => { + skip_counter += 1; //Skip character + "C" + } + _ => "C", + } + } + None => { + //Check previous character + match i { + 0 => "K", + _ => match input.chars().nth(i - 1) { + //Check previous character + Some(ch) => match ch { + 'К' | 'А' | 'И' | 'О' | 'У' | 'к' | 'а' | 'и' | 'о' | 'у' | ' ' => { + "K" + } + _ => "C", + }, + None => "K", + }, + } + } + } + } + 'к' => { + //K is very complex, sometimes it is C and sometimes is K + //If following letter is in (E, I, Y), then is K + match input.chars().nth(i + 1) { + Some(ch) => { + //Check following character + match ch { + 'Є' | 'Е' | 'И' | 'Й' | 'є' | 'е' | 'и' | 'й' => "k", + ' ' => { + match i { + 0 => "k", + _ => match input.chars().nth(i - 1) { + //Check previous character + Some(ch) => match ch { + 'К' | 'А' | 'И' | 'О' | 'к' | 'а' | 'и' | 'о' | ' ' => "k", + _ => "c", + }, + None => "k", + }, + } + } + 'Ю' | 'ю' => { + skip_counter += 1; + "q" + } + 'с' | 'С' => { + skip_counter += 1; + "x" + } + 'ʼ' => { + skip_counter += 1; //Skip next character + "k" + } + 'ь' | 'Ь' => { + skip_counter += 1; //Skip character + "c" + } + _ => "c", + } + } + None => { + //Check previous character + match i { + 0 => "k", + _ => match input.chars().nth(i - 1) { + Some(ch) => match ch { + 'К' | 'А' | 'И' | 'О' | 'У' | 'к' | 'а' | 'и' | 'о' | 'у' | ' ' => { + "k" + } + _ => "c", + }, + None => "k", + }, + } + } + } + } + 'Л' => "L", + 'л' => "l", + 'М' => "M", + 'м' => "m", + 'Н' => "N", + 'н' => "n", + 'О' => "O", + 'о' => "o", + 'П' => "P", + 'п' => "p", + 'Р' => "R", + 'р' => "r", + 'С' => "S", + 'с' => "s", + 'Т' => "T", + 'т' => "t", + 'У' => "U", + 'у' => "u", + 'Ф' => "F", + 'ф' => "f", + 'Х' => "H", + 'х' => "h", + 'Ч' => "CH", + 'ч' => "ch", + 'Ш' => "SH", + 'ш' => "sh", + 'Щ' => "SHH", + 'щ' => "shh", + 'ʼ' => "'", + 'Й' => "Y", + 'й' => "y", + 'Ь' => "`", + 'ь' => "`", + 'Ю' => "YU", + 'ю' => "yu", + 'Я' => "YA", + 'я' => "ya", + 'Ц' => "Z", + 'ц' => "z", + '№' => "#", + _ => { + unchanged_str = c.to_string(); + unchanged_str.as_str() + } + }); + } + output + } + + /// Converts a string which contains latin characters into a ukrainian cyrillic string. + /// Characters between quotes are escapes + fn to_cyrillic(&self, input: &String) -> String { + let mut output: String = String::new(); + let mut skip_cycles: usize = 0; + for (i, c) in input.chars().enumerate() { + if skip_cycles > 0 { + skip_cycles -= 1; + continue; + } + let unchanged_str: String; + output.push_str(match c { + 'A' => "А", + 'a' => "а", + 'B' => "Б", + 'b' => "б", + 'C' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "Ч" + } + _ => "К", + }, + None => "К", + }, + 'c' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "ч" + } + _ => "к", + }, + None => "к", + }, + 'D' => "Д", + 'd' => "д", + 'E' => "Е", + 'e' => "е", + 'F' => "Ф", + 'f' => "ф", + 'G' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'y' | 'Y' | 'e' | 'E' | 'i' | 'I' => "ДЖ", + _ => "Г", + }, + None => "Г", + }, + 'g' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'y' | 'Y' | 'e' | 'E' | 'i' | 'I' => "дж", + _ => "г", + }, + None => "г", + }, + 'H' => "Х", + 'h' => "х", + 'I' => match input.chars().nth(i + 1) { // Match following character + Some(ch) => match ch { + 'u' | 'U' => { + skip_cycles += 1; + "Ю" + } + 'a' | 'A' => { + skip_cycles += 1; + "Я" + } + _ => "И", + }, + None => "И", + }, + 'i' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'u' | 'U' => { + skip_cycles += 1; + "ю" + } + 'a' | 'A' => { + skip_cycles += 1; + "я" + } + _ => "и", + }, + None => "и", + }, + 'J' => "Ж", + 'j' => "ж", + 'K' => "К", + 'k' => "к", + 'L' => "Л", + 'l' => "л", + 'M' => "М", + 'm' => "м", + 'N' => "Н", + 'n' => "н", + 'O' => "О", + 'o' => "о", + 'P' => "П", + 'p' => "п", + 'Q' => "КЮ", + 'q' => "кю", + 'R' => "Р", + 'r' => "р", + 'S' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "Ш" + } + _ => "С", + }, + None => "С", + }, + 's' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "ш" + } + _ => "с", + }, + None => "с", + }, + 'T' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 's' | 'S' => { + skip_cycles += 1; + "Ц" + } + _ => "Т", + }, + None => "Т", + }, + 't' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 's' | 'T' => { + skip_cycles += 1; + "ц" + } + _ => "т", + }, + None => "т", + }, + 'U' => "У", + 'u' => "у", + 'V' => "В", + 'v' => "в", + 'W' => "У", + 'w' => "у", + 'X' => "КС", + 'x' => "кс", + 'Y' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'e' | 'E' => { + skip_cycles += 1; + "Є" + } + _ => "Й", + }, + None => "Й", + }, + 'y' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'e' | 'E' => { + skip_cycles += 1; + "є" + } + _ => "й", + }, + None => "й", + }, + 'Z' => "З", + 'z' => "з", + _ => { + unchanged_str = c.to_string(); + unchanged_str.as_str() + } + }); + } + output + } +} + +//@! Tests + +#[cfg(test)] +mod tests { + + use super::*; + use crate::translator::{new_translator, Language}; + + #[test] + fn test_translator_lang_ukrainian_to_latin() { + //Simple commands + let translator: Box = new_translator(Language::Ukrainian); + //ls -l + let input: String = String::from("лс -л"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ls -l"); + //Echo hello + let input: String = String::from("екхо хелло"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "echo hello"); + //K vs C + let input: String = String::from("ифконфиг етх0 аддресс 192.168.1.30 нетмаскʼ 255.255.255.0"); //Use твёрдйй знак to force k in netmask + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!( + output, + "ifconfig eth0 address 192.168.1.30 netmask 255.255.255.0" + ); + let input: String = String::from("кат РЕАДМЕ.мд"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "cat README.md"); + //Test all letters (Lowercase) + let input: String = String::from("абкьдефгґхиіїжкʼлмнопкюрстуввьксйзшщюячц"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "abcdefgghiiyijklmnopqrstuvwxyzshshhyuyachz"); + //Test all letters (Uppercase) + let input: String = String::from("АБКЬДЕФГҐХИІЇЖКʼЛМНОПКЮРСТУВВЬКСЙЗШЩЮЯЧЦ"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ABCDEFGGHIIYIJKLMNOPQRSTUVWXYZSHSHHYUYACHZ"); + //Special cases 'Q' + let input: String = String::from("москюуитто_пуб"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "mosquitto_pub"); + let input: String = String::from("МОСКЮУИТТО_ПУБ"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "MOSQUITTO_PUB"); + //Special case: В as last character + let input: String = String::from("срв"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "srv"); + let input: String = String::from("СРВ"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "SRV"); + //Special case: Ye + let input: String = String::from("єлл"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "yell"); + let input: String = String::from("ЄЛЛ"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "YELL"); + //Special case: ck + let input: String = String::from("чекк чекк"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "check check"); + let input: String = String::from("ЧЕКК ЧЕКК"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "CHECK CHECK"); + //Special case: k as last character which becomes 'c' + let input: String = String::from("рек к к"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "rec k k"); + let input: String = String::from("РЕК К К"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "REC K K"); + //Special case: k as last character which becomes 'k' + let input: String = String::from("ок ок"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ok ok"); + let input: String = String::from("ОК ОК"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "OK OK"); + //Special case: k as first character + let input: String = String::from("к о"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "k o"); + let input: String = String::from("К О"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "K O"); + //Special case: k as last character, but preceeded by 'к' | 'а' | 'и' | 'о' + let input: String = String::from("как бар"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "cak bar"); + let input: String = String::from("КАК БАР"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "CAK BAR"); + let input: String = String::from("как"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "cak"); + let input: String = String::from("КАК"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "CAK"); + //Special case: k out of matches + let input: String = String::from("кд"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "cd"); + let input: String = String::from("КД"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "CD"); + //Backtick and quote + let input: String = String::from("ʼьʼЬ"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "'`'`"); + //Symbols + let input: String = String::from("№"); + let output = translator.to_latin(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "#"); + } + + #[test] + fn test_translator_lang_ukrainian_to_cyrillic() { + let translator: Box = new_translator(Language::Ukrainian); + //Test all + let input: String = String::from("a b c d e f g h i j k l m n o p q r s t u v w x y z"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!( + output, + "а б к д е ф г х и ж к л м н о п кю р с т у в у кс й з" + ); + let input: String = String::from("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!( + output, + "А Б К Д Е Ф Г Х И Ж К Л М Н О П КЮ Р С Т У В У КС Й З" + ); + //Test particular case (sh) + let input: String = String::from("shell"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "шелл"); + let input: String = String::from("SHELL"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ШЕЛЛ"); + //Test particular case (jo) Ё + let input: String = String::from("Option"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "Оптион"); + let input: String = String::from("OPTION"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ОПТИОН"); + //Test particular case (ts) + let input: String = String::from("tsunami"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "цунами"); + let input: String = String::from("TSUNAMI"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЦУНАМИ"); + //Test particular case (g) + let input: String = String::from("gin and games"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "джин анд гамес"); + let input: String = String::from("GIN AND GAMES"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ДЖИН АНД ГАМЕС"); + //Test particular case (iu) + let input: String = String::from("iuta"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "юта"); + let input: String = String::from("IUTA"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЮТА"); + //Test particular case (ye) + let input: String = String::from("yellow"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "єллоу"); + let input: String = String::from("YELLOW"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЄЛЛОУ"); + //Test particular case (giu) + (ia) + let input: String = String::from("giulia"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "джюля"); + let input: String = String::from("GIULIA"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ДЖЮЛЯ"); + //Test case 'ch' + let input: String = String::from("channel"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "чаннел"); + let input: String = String::from("CHANNEL"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЧАННЕЛ"); + //Test some words + let input: String = String::from("Usage: cat [OPTION]... [FILE]..."); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "Усадже: кат [ОПТИОН]... [ФИЛЕ]..."); + //Special cases: last character is 'c' + let input: String = String::from("chic"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "чик"); + let input: String = String::from("CHIC"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЧИК"); + //Special cases: last character is 'п' + let input: String = String::from("gag"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "гаг"); + let input: String = String::from("GAG"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ГАГ"); + //Special cases: last character is 'i' + let input: String = String::from("vi"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ви"); + let input: String = String::from("VI"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ВИ"); + //Special cases: last character is 's' + let input: String = String::from("less"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "лесс"); + let input: String = String::from("LESS"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЛЕСС"); + //Special cases: last character is 't' + let input: String = String::from("cat"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "кат"); + let input: String = String::from("CAT"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "КАТ"); + //Special cases: y + let input: String = String::from("yacc"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "йакк"); + let input: String = String::from("YACC"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "ЙАКК"); + //Special cases: y part 2 + let input: String = String::from("y"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "й"); + let input: String = String::from("Y"); + let output = translator.to_cyrillic(&input); + println!("\"{}\" => \"{}\"", input, output); + assert_eq!(output, "Й"); + } +} diff --git a/src/translator/mod.rs b/src/translator/mod.rs index 059f438..487fb20 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -54,6 +54,7 @@ pub fn new_translator(language: Language) -> Box { Language::Belarusian => Box::new(lang::Belarusian {}), Language::Bulgarian => Box::new(lang::Bulgarian {}), Language::Russian => Box::new(lang::Russian {}), + Language::Ukrainian => Box::new(lang::Ukrainian {}), } } @@ -68,6 +69,7 @@ mod tests { let _ = new_translator(Language::Belarusian); let _ = new_translator(Language::Bulgarian); let _ = new_translator(Language::Russian); + let _ = new_translator(Language::Ukrainian); } } From ed23c1ab47144004a53d87e118fb9661cfc4dc66 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 18 Oct 2020 15:01:35 +0200 Subject: [PATCH 12/32] Fixed docs --- docs/translators/bg.md | 82 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/translators/bg.md b/docs/translators/bg.md index 97c24bc..65a9fef 100644 --- a/docs/translators/bg.md +++ b/docs/translators/bg.md @@ -10,47 +10,47 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) standard with some differences. The entire conversion table is illustrated here below: -| Russian | Latin | Notes | -|---------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------| -| А | A | | -| Б | B | | -| К | C | K is translated into C, only when not followed ```'Е','Э','И','Й','Ы','ъ'```, or it is preceeded by ```'К','А','И','О'```. You can force a 'C' using ```'Кь'``` | -| Ч | CH | | -| Ц | Z | | -| Д | D | | -| Э | E | | -| Ф | F | | -| Г | G | | -| Х | H | | -| И | I | | -| Ж | J | | -| Й | J | | -| Ё | JO | | -| К | K | K is converted to latin K only when followed by ```'Е','Э','И','Й','Ы','ъ'``` ,or it is NOT preceeded by ```'К','А','И','О'``` .You can force a K using ```'КЪ'``` | -| Л | L | | -| М | M | | -| Н | N | | -| О | O | | -| П | P | | -| Кю | Q | | -| Р | R | | -| С | S | | -| Ш | SH | | -| Щ | SHT | | -| Т | T | | -| У | U | | -| В | V | | -| Вь | W | | -| КС | X | | -| Ы | Y | | -| Я | YA | | -| Е | YE | | -| Ю | YU | | -| З | Z | | -| € | $ | | -| Ъ | ' | | -| Ь | ` | | -| № | # | | +| Bulgarian | Latin | Notes | +|-----------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------| +| А | A | | +| Б | B | | +| К | C | K is translated into C, only when not followed ```'Е','Э','И','Й','Ы','ъ'```, or it is preceeded by ```'К','А','И','О'```. You can force a 'C' using ```'Кь'``` | +| Ч | CH | | +| Ц | Z | | +| Д | D | | +| Э | E | | +| Ф | F | | +| Г | G | | +| Х | H | | +| И | I | | +| Ж | J | | +| Й | J | | +| Ё | JO | | +| К | K | K is converted to latin K only when followed by ```'Е','Э','И','Й','Ы','ъ'``` ,or it is NOT preceeded by ```'К','А','И','О'``` .You can force a K using ```'КЪ'``` | +| Л | L | | +| М | M | | +| Н | N | | +| О | O | | +| П | P | | +| Кю | Q | | +| Р | R | | +| С | S | | +| Ш | SH | | +| Щ | SHT | | +| Т | T | | +| У | U | | +| В | V | | +| Вь | W | | +| КС | X | | +| Ы | Y | | +| Я | YA | | +| Е | YE | | +| Ю | YU | | +| З | Z | | +| € | $ | | +| Ъ | ' | | +| Ь | ` | | +| № | # | | ## Latin to Cyrillic From 675d07d07d955949a7ea51c1d91372dde57d045a Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 10:06:52 +0100 Subject: [PATCH 13/32] Added new color prompt keys: KBOLD, KBLINK, KSELECT --- CHANGELOG.md | 4 ++++ README.md | 25 ++++++++++++++----------- pyc.yml | 2 +- src/shell/prompt/mod.rs | 9 ++++++--- src/shell/prompt/modules/colors.rs | 24 ++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92a2d1c..8d01f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Released on ?? - 🇺🇦 Ukrainian - Prompt configuration: - new ```commit_prepend``` and ```commit_append``` keys + - New colors keys: + - KBOLD + - KBLINK + - KSELECT - Reverse search for prompt - KeyBinding: CTRL+R (enter reverse search) - KeyBinding: CTRL+G (exit reverse search) diff --git a/README.md b/README.md index 19305a1..cd39106 100644 --- a/README.md +++ b/README.md @@ -208,17 +208,20 @@ Each prompt line key must have the following syntax ```${VAR_NAME}``` #### Colors keys -| Key | Description | -|----------|-------------| -| KYEL | Yellow | -| KRED | Red | -| KBLU | Blue | -| KMAG | Magenta | -| KGRN | Green | -| KWHT | White | -| KBLK | Black | -| KGRY | Gray | -| KRST | Reset | +| Key | Description | +|----------|---------------| +| KYEL | Yellow | +| KRED | Red | +| KBLU | Blue | +| KMAG | Magenta | +| KGRN | Green | +| KWHT | White | +| KBLK | Black | +| KGRY | Gray | +| KBOLD | Bold text | +| KBLINK | Blinking text | +| KSELECT | Selected text | +| KRST | Reset | #### Git keys diff --git a/pyc.yml b/pyc.yml index f2302b8..b1667dc 100644 --- a/pyc.yml +++ b/pyc.yml @@ -8,7 +8,7 @@ alias: output: translate: true prompt: - prompt_line: "${KYEL}${USER}${KRST} on ${KGRN}${HOSTNAME}${KRST} in ${KCYN}${WRKDIR}${KRST} ${KMAG}${GIT_BRANCH} ${GIT_COMMIT}${KRST} ${KYEL}${CMD_TIME}${KRST}" + prompt_line: "${KBOLD}${KYEL}${USER}${KRST} on ${KBOLD}${KGRN}${HOSTNAME}${KRST} in ${KBOLD}${KCYN}${WRKDIR}${KRST} ${KBOLD}${KMAG}${GIT_BRANCH} ${GIT_COMMIT}${KRST} ${KYEL}${CMD_TIME}${KRST}" history_size: 256 translate: false break: diff --git a/src/shell/prompt/mod.rs b/src/shell/prompt/mod.rs index 7528dc1..21927d0 100644 --- a/src/shell/prompt/mod.rs +++ b/src/shell/prompt/mod.rs @@ -251,7 +251,7 @@ impl ShellPrompt { } } PROMPT_HOSTNAME => shell_props.hostname.clone(), - modules::colors::PROMPT_KBLK | modules::colors::PROMPT_KBLU | modules::colors::PROMPT_KCYN | modules::colors::PROMPT_KGRN | modules::colors::PROMPT_KGRY | modules::colors::PROMPT_KMAG | modules::colors::PROMPT_KRED | modules::colors::PROMPT_KRST | modules::colors::PROMPT_KWHT | modules::colors::PROMPT_KYEL => colors::PromptColor::from_key(key.as_str()).to_string(), + modules::colors::PROMPT_KBLINK | modules::colors::PROMPT_KBLK | modules::colors::PROMPT_KBLU | modules::colors::PROMPT_KBOLD | modules::colors::PROMPT_KCYN | modules::colors::PROMPT_KGRN | modules::colors::PROMPT_KGRY | modules::colors::PROMPT_KMAG | modules::colors::PROMPT_KRED | modules::colors::PROMPT_KRST | modules::colors::PROMPT_KSELECT | modules::colors::PROMPT_KWHT | modules::colors::PROMPT_KYEL => colors::PromptColor::from_key(key.as_str()).to_string(), modules::language::PROMPT_LANG => language::language_to_str(processor.language), PROMPT_RC => match &self.rc_opt { Some(opt) => match shell_props.exit_status { @@ -379,7 +379,7 @@ mod tests { fn test_prompt_colors() { let mut prompt_config_default = PromptConfig::default(); //Update prompt line - prompt_config_default.prompt_line = String::from("${KRED}RED${KYEL}YEL${KBLU}BLU${KGRN}GRN${KWHT}WHT${KGRY}GRY${KBLK}BLK${KMAG}MAG${KCYN}CYN${KRST}"); + prompt_config_default.prompt_line = String::from("${KRED}RED${KYEL}YEL${KBLU}BLU${KGRN}GRN${KWHT}WHT${KGRY}GRY${KBLK}BLK${KMAG}MAG${KCYN}CYN${KBOLD}BOLD${KBLINK}BLINK${KSELECT}SELECTED${KRST}"); let mut prompt: ShellPrompt = ShellPrompt::new(&prompt_config_default); let iop: IOProcessor = get_ioprocessor(); let shellenv: ShellProps = get_shellenv(); @@ -391,7 +391,7 @@ mod tests { //Get prompt line let prompt_line: String = prompt.process_prompt(&shellenv, &iop); let expected_prompt_line = String::from(format!( - "{}RED{}YEL{}BLU{}GRN{}WHT{}GRY{}BLK{}MAG{}CYN{}", + "{}RED{}YEL{}BLU{}GRN{}WHT{}GRY{}BLK{}MAG{}CYN{}BOLD{}BLINK{}SELECTED{}", PromptColor::Red.to_string(), PromptColor::Yellow.to_string(), PromptColor::Blue.to_string(), @@ -401,6 +401,9 @@ mod tests { PromptColor::Black.to_string(), PromptColor::Magenta.to_string(), PromptColor::Cyan.to_string(), + PromptColor::Bold.to_string(), + PromptColor::Blink.to_string(), + PromptColor::Select.to_string(), PromptColor::Reset.to_string() )); assert_eq!(prompt_line, expected_prompt_line); diff --git a/src/shell/prompt/modules/colors.rs b/src/shell/prompt/modules/colors.rs index 61cb6b9..8387aed 100644 --- a/src/shell/prompt/modules/colors.rs +++ b/src/shell/prompt/modules/colors.rs @@ -33,6 +33,9 @@ pub(crate) const PROMPT_KMAG: &str = "${KMAG}"; pub(crate) const PROMPT_KBLK: &str = "${KBLK}"; pub(crate) const PROMPT_KGRY: &str = "${KGRY}"; pub(crate) const PROMPT_KWHT: &str = "${KWHT}"; +pub(crate) const PROMPT_KBOLD: &str = "${KBOLD}"; +pub(crate) const PROMPT_KBLINK: &str = "${KBLINK}"; +pub(crate) const PROMPT_KSELECT: &str = "${KSELECT}"; pub(crate) const PROMPT_KRST: &str = "${KRST}"; //Colors @@ -45,6 +48,9 @@ const KMAG: &str = "\x1b[35m"; const KGRY: &str = "\x1b[90m"; const KBLK: &str = "\x1b[30m"; const KWHT: &str = "\x1b[37m"; +const KBOLD: &str = "\x1b[1m"; +const KBLINK: &str = "\x1b[5m"; +const KSELECT: &str = "\x1b[7m"; const KRST: &str = "\x1b[0m"; #[derive(Copy, Clone, PartialEq, std::fmt::Debug)] @@ -58,6 +64,9 @@ pub enum PromptColor { Black, Gray, White, + Bold, + Blink, + Select, Reset, } @@ -73,6 +82,9 @@ impl ToString for PromptColor { PromptColor::Black => String::from(KBLK), PromptColor::Gray => String::from(KGRY), PromptColor::White => String::from(KWHT), + PromptColor::Bold => String::from(KBOLD), + PromptColor::Blink => String::from(KBLINK), + PromptColor::Select => String::from(KSELECT), PromptColor::Reset => String::from(KRST), } } @@ -90,6 +102,9 @@ impl PromptColor { PROMPT_KMAG => PromptColor::Magenta, PROMPT_KBLK => PromptColor::Black, PROMPT_KWHT => PromptColor::White, + PROMPT_KBOLD => PromptColor::Bold, + PROMPT_KBLINK => PromptColor::Blink, + PROMPT_KSELECT => PromptColor::Select, PROMPT_KRST => PromptColor::Reset, _ => PromptColor::Reset, } @@ -112,6 +127,9 @@ mod tests { assert_eq!(PromptColor::from_key(PROMPT_KGRY), PromptColor::Gray); assert_eq!(PromptColor::from_key(PROMPT_KWHT), PromptColor::White); assert_eq!(PromptColor::from_key(PROMPT_KBLK), PromptColor::Black); + assert_eq!(PromptColor::from_key(PROMPT_KBOLD), PromptColor::Bold); + assert_eq!(PromptColor::from_key(PROMPT_KBLINK), PromptColor::Blink); + assert_eq!(PromptColor::from_key(PROMPT_KSELECT), PromptColor::Select); assert_eq!(PromptColor::from_key(PROMPT_KRST), PromptColor::Reset); assert_eq!(PromptColor::from_key("UnknownColor"), PromptColor::Reset); } @@ -136,6 +154,12 @@ mod tests { println!("{}White", PromptColor::White.to_string()); assert_eq!(PromptColor::Black.to_string(), KBLK); println!("{}Black", PromptColor::Black.to_string()); + assert_eq!(PromptColor::Bold.to_string(), KBOLD); + println!("{}Bold", PromptColor::Bold.to_string()); + assert_eq!(PromptColor::Blink.to_string(), KBLINK); + println!("{}Blink", PromptColor::Blink.to_string()); + assert_eq!(PromptColor::Select.to_string(), KSELECT); + println!("{}Selected", PromptColor::Select.to_string()); assert_eq!(PromptColor::Reset.to_string(), KRST); println!("{}Reset", PromptColor::Reset.to_string()); } From 0b9bfc19f064356c592a9111459d566efe222955 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 10:12:22 +0100 Subject: [PATCH 14/32] Do not allow failures on beta --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 00b833b..be5737d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,6 @@ rust: jobs: allow_failures: - rust: nightly - - rust: beta before_cache: | if [[ "$TRAVIS_RUST_VERSION" == stable ]]; then cargo install cargo-tarpaulin From 1fb0f85600f276e5e4a7738dea38c1a30c6b8d2e Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 10:15:24 +0100 Subject: [PATCH 15/32] Fixed directive to ignore macos test for tarpaulin --- src/shell/unixsignal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell/unixsignal.rs b/src/shell/unixsignal.rs index 8a337f3..ec7fb30 100644 --- a/src/shell/unixsignal.rs +++ b/src/shell/unixsignal.rs @@ -110,7 +110,7 @@ impl UnixSignal { } #[cfg(target_os = "macos")] - #[cfg_attr(tarpaulin, skip)] + #[cfg(not(tarpaulin_include))] /// ### to_nix_signal /// /// Converts a UnixSignal to a nix::signal From 70a402818438f34328f61973b39025e236b0f01b Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 10:21:13 +0100 Subject: [PATCH 16/32] Code coverage --- src/config/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 83fdaff..0a1adda 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -473,7 +473,29 @@ mod tests { let config_file: tempfile::NamedTempFile = write_config_file_en(); let config_file_path: PathBuf = PathBuf::from(config_file.path().to_str().unwrap()); println!("Generated config file: {}", config_file_path.display()); - assert!(Config::parse_config(config_file_path).is_ok()) + let config: Result =Config::parse_config(config_file_path); + assert!(config.is_ok()); + let config: Config = config.ok().unwrap(); + // Verify parameters + assert!(config.get_alias(&String::from("чд")).is_some()); + assert_eq!(config.output_config.translate_output, true); + assert_eq!(config.language, String::from("ru")); + let prompt_config: PromptConfig = config.prompt_config; + assert_eq!(prompt_config.prompt_line, String::from("${USER}@${HOSTNAME}:${WRKDIR}$")); + assert_eq!(prompt_config.break_enabled, false); + assert_eq!(prompt_config.break_str, String::from("❯")); + assert_eq!(prompt_config.git_branch, String::from("on ")); + assert_eq!(prompt_config.git_commit_ref, 8); + assert_eq!(prompt_config.git_commit_prepend, None); + assert_eq!(prompt_config.git_commit_append, None); + assert_eq!(prompt_config.history_size, 256); + assert_eq!(prompt_config.min_duration, 2000); + assert_eq!(prompt_config.rc_err, String::from("✖")); + assert_eq!(prompt_config.rc_ok, String::from("✔")); + assert_eq!(prompt_config.translate, false); + assert_eq!(config.shell_config.exec, String::from("bash")); + assert_eq!(config.shell_config.args.len(), 0); + } #[test] From 9df46d27af2078bee51dfcb70a08749be3b5c103 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 16:16:21 +0100 Subject: [PATCH 17/32] Serbian translator --- CHANGELOG.md | 1 + README.md | 20 +- docs/translators/rs.md | 86 +++++ docs/translators/ua.md | 2 +- pyc.yml | 2 +- src/main.rs | 1 + src/shell/prompt/modules/language.rs | 20 +- src/translator/lang/mod.rs | 5 + src/translator/lang/serbian.rs | 512 +++++++++++++++++++++++++++ src/translator/mod.rs | 2 + 10 files changed, 638 insertions(+), 13 deletions(-) create mode 100644 docs/translators/rs.md create mode 100644 src/translator/lang/serbian.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d01f3b..1877cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Released on ?? - New translators: + - 🇷🇸 Serbian - 🇺🇦 Ukrainian - Prompt configuration: - new ```commit_prepend``` and ```commit_append``` keys diff --git a/README.md b/README.md index cd39106..76ab401 100644 --- a/README.md +++ b/README.md @@ -72,22 +72,22 @@ Basically Рус is a shell interface, which means that it reads the user input, - ![by](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Belarus.png) Belarusian Cyrillic - According to belarusian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/by.md)) - ![bg](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bulgaria.png) Bulgarian Cyrillic - According to bulgarian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ru.md)) +- ![rs](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Serbia.png)![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bosnia-and-Herzegovina.png) Serbian Cyrillic - According to serbian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/rs.md)) - ![ru](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Russia.png) Russian Cyrillic - According to russian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ru.md)) - ![ua](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Ukraine.png) Ukrainian Cyrillic - According to ukrainian cyrillic [GOST 7.79-2000](https://en.wikipedia.org/wiki/GOST_7.79-2000) with some differences ([See here](./docs/translators/ua.md)) ### Planned alphabets -- ![in](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/India.png) Devanagari - *Coming soon (0.3.0)* - ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) Hangŭl - According to [Revised Romanization of Korean](https://en.wikipedia.org/wiki/Revised_Romanization_of_Korean) - *Coming soon (0.3.0)* -- ![mk](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Macedonia.png) Macedonian Cyrillic - *Coming soon (0.4.0)* -- ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *Coming soon (0.4.0)* -- ![rs](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Serbia.png)![br](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Bosnia-and-Herzegovina.png) Serbian Cyrillic - *Coming soon (0.3.0)* +- ![in](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/India.png) Devanagari - *Planned for 2021 (0.4.0)* +- ![mk](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Macedonia.png) Macedonian Cyrillic - *Planned for 2021 (0.4.0)* +- ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *Planned for 2021 (0.4.0)* Other alphabets are not planned for the moment. --- -Can't find yours? Contribute to the project implementing it 😀! [Read More](./CONTRIBUTING.md) +Can't find yours? Contribute to the project implementing it 😀 ! [Read More](./CONTRIBUTING.md) ## Installation @@ -166,6 +166,7 @@ prompt: - **Belarusian**: by | бел - **Bulgarian**: bg | бг | блг - **Russian**: ru | рус + - **Serbian**: rs | срб - **Ukrainian** : ua | укр - output: output configuration - translate: indicates to pyc whether the output has to be converted to cyrillic or not @@ -236,9 +237,11 @@ The developer documentation can be found on Rust Docs at Language { "ru" | "рус" => Language::Russian, "by" | "бел" => Language::Belarusian, "bg" | "бг" | "блг" => Language::Bulgarian, + "rs" | "срб" => Language::Serbian, "ua" | "укр" => Language::Ukrainian, _ => { eprintln!( diff --git a/src/shell/prompt/modules/language.rs b/src/shell/prompt/modules/language.rs index 6407d38..5daf6fb 100644 --- a/src/shell/prompt/modules/language.rs +++ b/src/shell/prompt/modules/language.rs @@ -65,6 +65,16 @@ pub fn language_to_str(language: Language) -> String { lang_str.chars().nth(2).unwrap_or(' '), PromptColor::Reset.to_string() )), + Language::Serbian => String::from(format!( + "{}{}{}{}{}{}{}", + PromptColor::Red.to_string(), + lang_str.chars().nth(0).unwrap_or(' '), + PromptColor::Blue.to_string(), + lang_str.chars().nth(1).unwrap_or(' '), + PromptColor::White.to_string(), + lang_str.chars().nth(2).unwrap_or(' '), + PromptColor::Reset.to_string() + )), Language::Ukrainian => String::from(format!( "{}{}{}{}{}{}{}", PromptColor::Cyan.to_string(), @@ -85,18 +95,22 @@ mod tests { #[test] fn test_prompt_lang_flag() { - //Belarusian + // Belarusian let expected_str = String::from("\x1b[31mб\x1b[32mе\x1b[37mл\x1b[0m"); println!("{}", language_to_str(Language::Belarusian)); assert_eq!(language_to_str(Language::Belarusian), expected_str); - //Bulgarian + // Bulgarian let expected_str = String::from("\x1b[37mб\x1b[32mл\x1b[31mг\x1b[0m"); println!("{}", language_to_str(Language::Bulgarian)); assert_eq!(language_to_str(Language::Bulgarian), expected_str); - //Russian + // Russian let expected_str = String::from("\x1b[37mр\x1b[34mу\x1b[31mс\x1b[0m"); println!("{}", language_to_str(Language::Russian)); assert_eq!(language_to_str(Language::Russian), expected_str); + // Serbian + let expected_str = String::from("\x1b[31mр\x1b[34mу\x1b[37mс\x1b[0m"); + println!("{}", language_to_str(Language::Serbian)); + assert_eq!(language_to_str(Language::Serbian), expected_str); // Ukrainian let expected_str = String::from("\x1b[36mу\x1b[33mк\x1b[36mр\x1b[0m"); println!("{}", language_to_str(Language::Ukrainian)); diff --git a/src/translator/lang/mod.rs b/src/translator/lang/mod.rs index d33e009..bb19895 100644 --- a/src/translator/lang/mod.rs +++ b/src/translator/lang/mod.rs @@ -32,6 +32,7 @@ pub enum Language { Belarusian, Bulgarian, Russian, + Serbian, Ukrainian, } @@ -43,10 +44,12 @@ pub enum Language { pub(crate) struct Belarusian {} pub(crate) struct Bulgarian {} pub(crate) struct Russian {} +pub(crate) struct Serbian {} pub(crate) struct Ukrainian {} mod belarusian; mod bulgarian; mod russian; +mod serbian; mod ukrainian; impl ToString for Language { @@ -55,6 +58,7 @@ impl ToString for Language { Language::Belarusian => String::from("бел"), Language::Bulgarian => String::from("блг"), Language::Russian => String::from("рус"), + Language::Serbian => String::from("срб"), Language::Ukrainian => String::from("укр") } } @@ -70,6 +74,7 @@ mod tests { assert_eq!(Language::Belarusian.to_string(), String::from("бел")); assert_eq!(Language::Bulgarian.to_string(), String::from("блг")); assert_eq!(Language::Russian.to_string(), String::from("рус")); + assert_eq!(Language::Serbian.to_string(), String::from("срб")); assert_eq!(Language::Ukrainian.to_string(), String::from("укр")); } diff --git a/src/translator/lang/serbian.rs b/src/translator/lang/serbian.rs new file mode 100644 index 0000000..825d536 --- /dev/null +++ b/src/translator/lang/serbian.rs @@ -0,0 +1,512 @@ +//! ### Serbian +//! +//! `serbian` language implementation of Translator trait + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use super::super::Translator; +use super::Serbian; + +impl Translator for Serbian { + /// ### Serbian translator + + /// Converts a string which contains serbian cyrillic characters into a latin string. + /// Characters between '"' (quotes) are escaped, expressions inside escaped blocks are translitarated anyway + /// Transliteration according to GOST 7.79-2000 + fn to_latin(&self, input: &String) -> String { + let mut output = String::new(); + let mut skip_counter: usize = 0; + for (i, c) in input.chars().enumerate() { + if skip_counter > 0 { + //Skip cycles + skip_counter -= 1; //Decrement skip counter + continue; + } + //Push transliterated character + let unchanged_str: String; + output.push_str(match c { + 'А' => "A", + 'а' => "a", + 'Б' => "B", + 'б' => "b", + 'В' => { + //If following character is 'В', then is always W + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'в' | 'В' => { + skip_counter += 1; //Skip character + "W" + } + _ => "V", + } + } + None => "V", + } + } + 'в' => + //If following character is 'В', then is always W + { + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'в' | 'В' => { + skip_counter += 1; //Skip character + "w" + } + _ => "v", + } + } + None => "v", + } + } + 'Г' => "G", + 'г' => "g", + 'Д' => "D", + 'д' => "d", + 'Ђ' => "DJ", + 'ђ' => "dj", + 'Е' => "E", + 'е' => "e", + 'Ж' | 'Ј' => "J", + 'ж' | 'ј' => "j", + 'З' => "Z", + 'з' => "z", + 'И' => + //If following character is 'И', then is always Y + { + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'и' | 'И' => { + skip_counter += 1; //Skip character + "Y" + } + _ => "I", + } + } + None => "I", + } + } + 'и' => + //If following character is 'И', then is always Y + { + match input.chars().nth(i + 1) { + Some(ch) => { + match ch { + 'и' | 'И' => { + skip_counter += 1; //Skip character + "y" + } + _ => "i", + } + } + None => "i", + } + } + 'Ћ' => "C", + 'ћ' => "c", + 'К' => { + match input.chars().nth(i + 1) { + //If following character is 'С', then is always X + Some(ch) => { + match ch { + 'с' | 'С' => { + skip_counter += 1; //Skip character + "X" + } + 'и' | 'И' => { + // If following characters are 'ИУ', then is always Q + match input.chars().nth(i + 2) { + Some(ch) => { + match ch { + 'у' | 'У' => { + skip_counter += 2; // Skip 2 + "Q" + } + _ => "K", + } + } + None => "K", + } + } + _ => "K", + } + } + None => "K", + } + } + 'к' => { + match input.chars().nth(i + 1) { + //If following character is 'С', then is always X + Some(ch) => { + match ch { + 'с' | 'С' => { + skip_counter += 1; //Skip character + "x" + } + 'и' | 'И' => { + // If following characters are 'ИУ', then is always Q + match input.chars().nth(i + 2) { + Some(ch) => { + match ch { + 'у' | 'У' => { + skip_counter += 2; // Skip 2 + "q" + } + _ => "k", + } + } + None => "k", + } + } + _ => "k", + } + } + None => "k", + } + } + 'Л' => "L", + 'л' => "l", + 'Љ' => "LJ", + 'љ' => "lj", + 'М' => "M", + 'м' => "m", + 'Н' => "N", + 'н' => "n", + 'Њ' => "NJ", + 'њ' => "nj", + 'О' => "O", + 'о' => "o", + 'П' => "P", + 'п' => "p", + 'Р' => "R", + 'р' => "r", + 'С' => "S", + 'с' => "s", + 'Т' => "T", + 'т' => "t", + 'Ч' => "CH", + 'ч' => "ch", + 'У' => "U", + 'у' => "u", + 'Ф' => "F", + 'ф' => "f", + 'Х' => "H", + 'х' => "h", + 'Ц' => "TS", + 'ц' => "ts", + 'Џ' => "DZ", + 'џ' => "dz", + 'Ш' => "SH", + 'ш' => "sh", + _ => { + unchanged_str = c.to_string(); + unchanged_str.as_str() + } + }); + } + output + } + + /// Converts a string which contains latin characters into a serbian cyrillic string. + /// Characters between quotes are escapes + fn to_cyrillic(&self, input: &String) -> String { + let mut output: String = String::new(); + let mut skip_cycles: usize = 0; + for (i, c) in input.chars().enumerate() { + if skip_cycles > 0 { + skip_cycles -= 1; + continue; + } + let unchanged_str: String; + output.push_str(match c { + 'A' => "А", + 'a' => "а", + 'B' => "Б", + 'b' => "б", + 'C' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "Ч" + } + _ => "К", + }, + None => "К", + }, + 'c' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "ч" + } + _ => "к", + }, + None => "к", + }, + 'D' => match input.chars().nth(i + 1) { + // If 'J' follows => Ђ; if 'Z' follows => Џ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "Ђ" + } + 'Z' | 'z' => { + skip_cycles += 1; + "Џ" + } + _ => "Д", + }, + None => "Д", + }, + 'd' => match input.chars().nth(i + 1) { + // If 'J' follows => Ђ; if 'Z' follows => Џ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "ђ" + } + 'Z' | 'z' => { + skip_cycles += 1; + "џ" + } + _ => "д", + }, + None => "д", + }, + 'E' => "Е", + 'e' => "е", + 'F' => "Ф", + 'f' => "ф", + 'G' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'y' | 'Y' | 'e' | 'E' | 'i' | 'I' => "ДЖ", + _ => "Г", + }, + None => "Г", + }, + 'g' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'y' | 'Y' | 'e' | 'E' | 'i' | 'I' => "дж", + _ => "г", + }, + None => "г", + }, + 'H' => "Х", + 'h' => "х", + 'I' => "И", + 'i' => "и", + 'J' => "Ј", + 'j' => "ј", + 'K' => "К", + 'k' => "к", + 'L' => match input.chars().nth(i + 1) { + // If 'J' follows => Љ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "Љ" + } + _ => "Л", + }, + None => "Л", + }, + 'l' => match input.chars().nth(i + 1) { + // If 'J' follows => Љ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "љ" + } + _ => "л", + }, + None => "л", + }, + 'M' => "М", + 'm' => "м", + 'N' => match input.chars().nth(i + 1) { + // If 'J' follows => Њ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "Њ" + } + _ => "Н", + }, + None => "Н", + }, + 'n' => match input.chars().nth(i + 1) { + // If 'J' follows => Њ + Some(ch) => match ch { + 'J' | 'j' => { + skip_cycles += 1; + "њ" + } + _ => "н", + }, + None => "н", + }, + 'O' => "О", + 'o' => "о", + 'P' => "П", + 'p' => "п", + 'Q' => "КУ", + 'q' => "ку", + 'R' => "Р", + 'r' => "р", + 'S' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "Ш" + } + _ => "С", + }, + None => "С", + }, + 's' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 'h' | 'H' => { + skip_cycles += 1; + "ш" + } + _ => "с", + }, + None => "с", + }, + 'T' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 's' | 'S' => { + skip_cycles += 1; + "Ц" + } + _ => "Т", + }, + None => "Т", + }, + 't' => match input.chars().nth(i + 1) { + Some(ch) => match ch { + 's' | 'T' => { + skip_cycles += 1; + "ц" + } + _ => "т", + }, + None => "т", + }, + 'U' => "У", + 'u' => "у", + 'V' => "В", + 'v' => "в", + 'W' => "В", + 'w' => "в", + 'X' => "КС", + 'x' => "кс", + 'Y' => "И", + 'y' => "и", + 'Z' => "З", + 'z' => "з", + _ => { + unchanged_str = c.to_string(); + unchanged_str.as_str() + } + }); + } + output + } +} + +//@! Tests + +#[cfg(test)] +mod tests { + + use super::*; + use crate::translator::{new_translator, Language}; + + #[test] + fn test_translator_lang_serbian_to_latin() { + // Serbian translator + let translator: Box = new_translator(Language::Serbian); + // All characters + assert_eq!(translator.to_latin(&String::from("АБВВВГДЂЕЖЈЗИИИЋККСКИУЛЉМНЊОПРСТЧУФХЦЏШ")), String::from("ABWVGDDJEJJZYICKXQLLJMNNJOPRSTCHUFHTSDZSH")); + assert_eq!(translator.to_latin(&String::from("абвввгдђежјзииићккскиулљмнњопрстчуфхцџш")), String::from("abwvgddjejjzyickxqlljmnnjoprstchufhtsdzsh")); + // Simple commands (lower) + assert_eq!(translator.to_latin(&String::from("лс поотис/")), String::from("ls pootis/")); + assert_eq!(translator.to_latin(&String::from("ексећ зш")), String::from("exec zsh")); + assert_eq!(translator.to_latin(&String::from("ћд тесц/")), String::from("cd tests/")); + // Simple commands (upper) + assert_eq!(translator.to_latin(&String::from("ЛС ПООТИС/")), String::from("LS POOTIS/")); + assert_eq!(translator.to_latin(&String::from("ЕКСЕЋ ЗШ")), String::from("EXEC ZSH")); + assert_eq!(translator.to_latin(&String::from("ЋД ТЕСЦ/")), String::from("CD TESTS/")); + // With next char special at the end of word (lower) + assert_eq!(translator.to_latin(&String::from("лс в")), String::from("ls v")); + assert_eq!(translator.to_latin(&String::from("лс сими")), String::from("ls simi")); + assert_eq!(translator.to_latin(&String::from("лс кек")), String::from("ls kek")); + assert_eq!(translator.to_latin(&String::from("лс ки")), String::from("ls ki")); + // With next char special at the end of word (upper) + assert_eq!(translator.to_latin(&String::from("ЛС В")), String::from("LS V")); + assert_eq!(translator.to_latin(&String::from("ЛС СИМИ")), String::from("LS SIMI")); + assert_eq!(translator.to_latin(&String::from("ЛС КЕК")), String::from("LS KEK")); + assert_eq!(translator.to_latin(&String::from("ЛС КИ")), String::from("LS KI")); + } + + #[test] + fn test_translator_lang_serbian_to_cyrillic() { + // Serbian translator + let translator: Box = new_translator(Language::Serbian); + // All characters + assert_eq!(translator.to_cyrillic(&String::from("ABCCHDDJDZEFGGEHIJKLLJMNNJOPQRSSHTTSUVWXYZ")), String::from("АБКЧДЂЏЕФГДЖЕХИЈКЛЉМНЊОПКУРСШТЦУВВКСИЗ")); + assert_eq!(translator.to_cyrillic(&String::from("abcchddjdzefggehijklljmnnjopqrsshttsuvwxyz")), String::from("абкчдђџефгджехијклљмнњопкурсштцуввксиз")); + // Test particular case (sh) + assert_eq!(translator.to_cyrillic(&String::from("shell sis")), "шелл сис"); + assert_eq!(translator.to_cyrillic(&String::from("SHELL SIS")), "ШЕЛЛ СИС"); + // Test particular case (ts) + assert_eq!(translator.to_cyrillic(&String::from("tsunami")), "цунами"); + assert_eq!(translator.to_cyrillic(&String::from("TSUNAMI")), "ЦУНАМИ"); + // Test particular case (g) + assert_eq!(translator.to_cyrillic(&String::from("gin and games")), "джин анд гамес"); + assert_eq!(translator.to_cyrillic(&String::from("GIN AND GAMES")), "ДЖИН АНД ГАМЕС"); + // Test particular case (ch) + assert_eq!(translator.to_cyrillic(&String::from("channel")), "чаннел"); + assert_eq!(translator.to_cyrillic(&String::from("CHANNEL")), "ЧАННЕЛ"); + // Test particular case (ts) + assert_eq!(translator.to_cyrillic(&String::from("tsunami")), "цунами"); + assert_eq!(translator.to_cyrillic(&String::from("TSUNAMI")), "ЦУНАМИ"); + // Test particular case (last character is C) + assert_eq!(translator.to_cyrillic(&String::from("cac")), "как"); + assert_eq!(translator.to_cyrillic(&String::from("CAC")), "КАК"); + // Test particular case (last char is G) + assert_eq!(translator.to_cyrillic(&String::from("gag")), "гаг"); + assert_eq!(translator.to_cyrillic(&String::from("GAG")), "ГАГ"); + // Test particular case (LJ) + assert_eq!(translator.to_cyrillic(&String::from("ljubljana l")), "љубљана л"); + assert_eq!(translator.to_cyrillic(&String::from("LJUBLJANA L")), "ЉУБЉАНА Л"); + // Test particular case (NJ) + assert_eq!(translator.to_cyrillic(&String::from("new jersey is abbreviated with nj non")), "нев јерсеи ис аббревиатед витх њ нон"); + assert_eq!(translator.to_cyrillic(&String::from("NEW JERSEY IS ABBREVIATED WITH NJ NON")), "НЕВ ЈЕРСЕИ ИС АББРЕВИАТЕД ВИТХ Њ НОН"); + // Test particular case (TS) + assert_eq!(translator.to_cyrillic(&String::from("typescript extension is .ts tot")), "типескрипт екстенсион ис .ц тот"); + assert_eq!(translator.to_cyrillic(&String::from("TYPESCRIPT EXTENSION IS .TS TOT")), "ТИПЕСКРИПТ ЕКСТЕНСИОН ИС .Ц ТОТ"); + } +} diff --git a/src/translator/mod.rs b/src/translator/mod.rs index 487fb20..580b97a 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -54,6 +54,7 @@ pub fn new_translator(language: Language) -> Box { Language::Belarusian => Box::new(lang::Belarusian {}), Language::Bulgarian => Box::new(lang::Bulgarian {}), Language::Russian => Box::new(lang::Russian {}), + Language::Serbian => Box::new(lang::Serbian {}), Language::Ukrainian => Box::new(lang::Ukrainian {}), } } @@ -69,6 +70,7 @@ mod tests { let _ = new_translator(Language::Belarusian); let _ = new_translator(Language::Bulgarian); let _ = new_translator(Language::Russian); + let _ = new_translator(Language::Serbian); let _ = new_translator(Language::Ukrainian); } From c5dcf744de94ff9b26b011a41360e5640ee8ae5a Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 16:21:27 +0100 Subject: [PATCH 18/32] Docs --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 76ab401..4317477 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,15 @@ Basically Рус is a shell interface, which means that it reads the user input, ## Features - Different alphabets support -- Possibility to easily implement new translators for other cyrillic alphabets. -- Conversion of both Input and outputs. +- Implementing translators for other alphabets is extremely easy. +- Conversion of both input and output. - Escaping for latin strings. - Interactive, oneshot and file modes. -- Customizations and aliases +- Prompt is fully customizable +- Shell aliases support +- Supports bash, sh, zsh and ~~fish~~ +- ~~Allows you to use your shell aliases and functions~~ +- ~~Text editors support~~ ## Supported alphabets From a2658a53a8f46a87cb48a575dea904924c85a4bf Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 16:28:02 +0100 Subject: [PATCH 19/32] Fixed test --- src/shell/prompt/modules/language.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell/prompt/modules/language.rs b/src/shell/prompt/modules/language.rs index 5daf6fb..5d45d73 100644 --- a/src/shell/prompt/modules/language.rs +++ b/src/shell/prompt/modules/language.rs @@ -108,7 +108,7 @@ mod tests { println!("{}", language_to_str(Language::Russian)); assert_eq!(language_to_str(Language::Russian), expected_str); // Serbian - let expected_str = String::from("\x1b[31mр\x1b[34mу\x1b[37mс\x1b[0m"); + let expected_str = String::from("\x1b[31mс\x1b[34mр\x1b[37mб\x1b[0m"); println!("{}", language_to_str(Language::Serbian)); assert_eq!(language_to_str(Language::Serbian), expected_str); // Ukrainian From 44829438697d4476b28e194faa92974bd9767b0d Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 16:31:51 +0100 Subject: [PATCH 20/32] Hangul is removed from planning --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4317477..7590fae 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ Basically Рус is a shell interface, which means that it reads the user input, ### Planned alphabets -- ![kr](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/South-Korea.png) Hangŭl - According to [Revised Romanization of Korean](https://en.wikipedia.org/wiki/Revised_Romanization_of_Korean) - *Coming soon (0.3.0)* - ![in](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/India.png) Devanagari - *Planned for 2021 (0.4.0)* - ![mk](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Macedonia.png) Macedonian Cyrillic - *Planned for 2021 (0.4.0)* - ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *Planned for 2021 (0.4.0)* From 7bbb838376ab3a763f87b5893fee378829b34e11 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 31 Oct 2020 18:57:55 +0100 Subject: [PATCH 21/32] Nil translator (for tests purpose) --- src/main.rs | 1 + src/shell/prompt/modules/language.rs | 12 +++++ src/translator/lang/mod.rs | 7 ++- src/translator/lang/nil.rs | 66 ++++++++++++++++++++++++++++ src/translator/mod.rs | 2 + 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/translator/lang/nil.rs diff --git a/src/main.rs b/src/main.rs index 4f9d4e5..105e519 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,6 +65,7 @@ fn str_to_language(lang: String) -> Language { "bg" | "бг" | "блг" => Language::Bulgarian, "rs" | "срб" => Language::Serbian, "ua" | "укр" => Language::Ukrainian, + "nil" => Language::Nil, _ => { eprintln!( "{}", diff --git a/src/shell/prompt/modules/language.rs b/src/shell/prompt/modules/language.rs index 5d45d73..e3bbd9d 100644 --- a/src/shell/prompt/modules/language.rs +++ b/src/shell/prompt/modules/language.rs @@ -84,6 +84,14 @@ pub fn language_to_str(language: Language) -> String { PromptColor::Cyan.to_string(), lang_str.chars().nth(2).unwrap_or(' '), PromptColor::Reset.to_string() + )), + Language::Nil => String::from(format!( + "{}{}{}{}{}", + PromptColor::Blink.to_string(), + lang_str.chars().nth(0).unwrap_or(' '), + lang_str.chars().nth(1).unwrap_or(' '), + lang_str.chars().nth(2).unwrap_or(' '), + PromptColor::Reset.to_string() )) } } @@ -115,5 +123,9 @@ mod tests { let expected_str = String::from("\x1b[36mу\x1b[33mк\x1b[36mр\x1b[0m"); println!("{}", language_to_str(Language::Ukrainian)); assert_eq!(language_to_str(Language::Ukrainian), expected_str); + // Nil + let expected_str = String::from("\x1b[5mnil\x1b[0m"); + println!("{}", language_to_str(Language::Nil)); + assert_eq!(language_to_str(Language::Nil), expected_str); } } diff --git a/src/translator/lang/mod.rs b/src/translator/lang/mod.rs index bb19895..518783c 100644 --- a/src/translator/lang/mod.rs +++ b/src/translator/lang/mod.rs @@ -34,6 +34,7 @@ pub enum Language { Russian, Serbian, Ukrainian, + Nil } /// ## Languages @@ -46,11 +47,13 @@ pub(crate) struct Bulgarian {} pub(crate) struct Russian {} pub(crate) struct Serbian {} pub(crate) struct Ukrainian {} +pub(crate) struct Nil {} mod belarusian; mod bulgarian; mod russian; mod serbian; mod ukrainian; +mod nil; impl ToString for Language { fn to_string(&self) -> String { @@ -59,7 +62,8 @@ impl ToString for Language { Language::Bulgarian => String::from("блг"), Language::Russian => String::from("рус"), Language::Serbian => String::from("срб"), - Language::Ukrainian => String::from("укр") + Language::Ukrainian => String::from("укр"), + Language::Nil => String::from("nil") } } } @@ -76,6 +80,7 @@ mod tests { assert_eq!(Language::Russian.to_string(), String::from("рус")); assert_eq!(Language::Serbian.to_string(), String::from("срб")); assert_eq!(Language::Ukrainian.to_string(), String::from("укр")); + assert_eq!(Language::Nil.to_string(), String::from("nil")); } } diff --git a/src/translator/lang/nil.rs b/src/translator/lang/nil.rs new file mode 100644 index 0000000..53424f8 --- /dev/null +++ b/src/translator/lang/nil.rs @@ -0,0 +1,66 @@ +//! ### Nil +//! +//! `nil` language implementation of Translator trait +//! This translator is just for test purposes, and it just doesn't translate anything. +//! Indeed, it returns the same string as the input + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use super::super::Translator; +use super::Nil; + +impl Translator for Nil { + /// ### Nil translator + + /// ### to_latin + /// Just returns input + fn to_latin(&self, input: &String) -> String { + input.clone() + } + /// ### to_cyrillic + /// Converts a string which contains latin characters into a ukrainian cyrillic string. + /// Characters between quotes are escapes + fn to_cyrillic(&self, input: &String) -> String { + input.clone() + } +} + +//@! Tests + +#[cfg(test)] +mod tests { + + use super::*; + use crate::translator::{new_translator, Language}; + + #[test] + fn test_translator_lang_nil_to_latin() { + let translator: Box = new_translator(Language::Nil); + assert_eq!(translator.to_latin(&String::from("HELLO WORLD")), String::from("HELLO WORLD")); + } + + #[test] + fn test_translator_lang_nil_to_cyrillic() { + let translator: Box = new_translator(Language::Nil); + assert_eq!(translator.to_cyrillic(&String::from("HELLO WORLD")), String::from("HELLO WORLD")); + } +} diff --git a/src/translator/mod.rs b/src/translator/mod.rs index 580b97a..c573710 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -56,6 +56,7 @@ pub fn new_translator(language: Language) -> Box { Language::Russian => Box::new(lang::Russian {}), Language::Serbian => Box::new(lang::Serbian {}), Language::Ukrainian => Box::new(lang::Ukrainian {}), + Language::Nil => Box::new(lang::Nil {}) } } @@ -72,6 +73,7 @@ mod tests { let _ = new_translator(Language::Russian); let _ = new_translator(Language::Serbian); let _ = new_translator(Language::Ukrainian); + let _ = new_translator(Language::Nil); } } From c6e014465ae36b62ab620f8d39361a5bf39f5b8b Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 09:45:37 +0100 Subject: [PATCH 22/32] Config Clone trait --- src/config/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/mod.rs b/src/config/mod.rs index 0a1adda..8e40b02 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -35,6 +35,7 @@ use yaml_rust::{Yaml, YamlLoader}; use std::path::PathBuf; //Types +#[derive(Clone)] pub struct Config { pub language: String, pub shell_config: ShellConfig, @@ -43,15 +44,18 @@ pub struct Config { pub prompt_config: PromptConfig, } +#[derive(Clone)] pub struct ShellConfig { pub exec: String, pub args: Vec } +#[derive(Clone)] pub struct OutputConfig { pub translate_output: bool, } +#[derive(Clone)] pub struct PromptConfig { pub prompt_line: String, pub history_size: usize, From ceaf9e0de7255fe4a71633a6e5446a69ccc9d951 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 09:55:47 +0100 Subject: [PATCH 23/32] RuntimeProps refactoring; Added IMIOP (Interactive Mode IO Processors) --- src/main.rs | 9 +- src/runtime/imiop/mod.rs | 48 ++ src/runtime/imiop/shiop.rs | 827 ++++++++++++++++++++++++++++++ src/runtime/imiop/subprociop.rs | 255 ++++++++++ src/runtime/mod.rs | 41 +- src/runtime/props.rs | 873 ++------------------------------ 6 files changed, 1210 insertions(+), 843 deletions(-) create mode 100644 src/runtime/imiop/mod.rs create mode 100644 src/runtime/imiop/shiop.rs create mode 100644 src/runtime/imiop/subprociop.rs diff --git a/src/main.rs b/src/main.rs index 105e519..1cf2b1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,6 @@ mod shell; mod translator; mod utils; -use translator::ioprocessor::IOProcessor; use translator::lang::Language; /// ### print_usage @@ -195,11 +194,9 @@ fn main() { Some(l) => l, None => str_to_language(config.language.clone()) }; - //Set up processor - let processor: IOProcessor = IOProcessor::new(language, translator::new_translator(language)); //Start runtime let rc: u8 = match command { - Some(command) => runtime::run_command(command, processor, config, shell), + Some(command) => runtime::run_command(command, language, config, shell), None => match file { None => { //Get history file @@ -211,9 +208,9 @@ fn main() { Some(pyc_history_file) } }; - runtime::run_interactive(processor, config, shell, history_file) + runtime::run_interactive(language, config, shell, history_file) }, - Some(file) => runtime::run_file(file, processor, config, shell) + Some(file) => runtime::run_file(file, language, config, shell) } }; std::process::exit(rc as i32); diff --git a/src/runtime/imiop/mod.rs b/src/runtime/imiop/mod.rs new file mode 100644 index 0000000..ee43ffa --- /dev/null +++ b/src/runtime/imiop/mod.rs @@ -0,0 +1,48 @@ +//! ## imiop +//! +//! `imiop`, or Interactive Mode I/O Processor, provides the data types and methods for indeed +//! I/O processors in interactive modes. These processors are modules which handled the +//! user input in different shell states (Idle, Running, TextEditor, ...) +//! All processors must implement handle_input_event function, which is called by the Runtime + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use crate::shell::Shell; +use crate::utils::console::InputEvent; + +// List of Imiop +pub(crate) mod shiop; +pub(crate) mod subprociop; + +/// ## Imiop +/// +/// Imiop (interactive mode I/O processor) defines the methods an Imiop has to implement +pub(crate) trait Imiop { + + /// ### handle_input_event + /// + /// Handle input event received from stdin + fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell); + +} + +// TODO: add factory for imiop diff --git a/src/runtime/imiop/shiop.rs b/src/runtime/imiop/shiop.rs new file mode 100644 index 0000000..dee5238 --- /dev/null +++ b/src/runtime/imiop/shiop.rs @@ -0,0 +1,827 @@ +//! ## shiop +//! +//! `shiop`, or Shell I/O Processor, is the implementation of the IMIOP trait to use when in +//! standard Shell Environment (prompt line, nothing special). + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use super::Imiop; +use crate::config::Config; +use crate::runtime::{console_fmt, print_err, print_out, resolve_command}; +use crate::shell::Shell; +use crate::translator::ioprocessor::IOProcessor; +use crate::utils::buffer; +use crate::utils::console::{self, InputEvent}; + +pub(crate) struct ShIop { + input_buffer: Vec, + input_buffer_cursor: usize, + rev_search: Option, // Reverse search match + rev_search_idx: usize, // Reverse search last match index + history_index: usize, + config: Config, + processor: IOProcessor, +} + +impl ShIop { + /// ### new + /// + /// Instantiate a new `ShIop` + pub fn new(config: Config, processor: IOProcessor) -> ShIop { + ShIop { + input_buffer: Vec::with_capacity(2048), + input_buffer_cursor: 0, + rev_search: None, + rev_search_idx: 0, + history_index: 0, + config: config, + processor: processor, + } + } + + /// ### clear_buffer + /// + /// Clear buffer and reset cursor to 0 + fn clear_buffer(&mut self) { + self.input_buffer.clear(); + self.input_buffer_cursor = 0; + } + + /// ### reset_history_index + /// + /// Reset history index to 0 + fn reset_history_index(&mut self) { + //Reset history index too + self.history_index = 0; + } + + /// ### backspace + /// + /// Perform backspace on current console and buffers + fn backspace(&mut self) { + //Remove from buffer and backspace (if possible) + if self.input_buffer_cursor > 0 { + self.input_buffer_cursor -= 1; + if self.input_buffer.len() > self.input_buffer_cursor { + self.input_buffer.remove(self.input_buffer_cursor); + } + console::backspace(); + } + } + + /// ### move_left + /// + /// Move cursor to left + fn move_left(&mut self) { + //If possible, move the cursor right + if self.input_buffer_cursor != 0 { + self.input_buffer_cursor -= 1; + console::move_cursor_left(); + } + } + + /// ### move_right + /// + /// Move cursor to right + fn move_right(&mut self) { + //If possible, move the cursor left + if self.input_buffer_cursor + 1 <= self.input_buffer.len() { + self.input_buffer_cursor += 1; + console::move_cursor_right(); + } + } + + /// ### perform_history_backward + /// + /// Get previous element in history and put it into the buffer + fn perform_history_backward(&mut self, shell: &mut Shell) { + //Match history size + if self.history_index > 1 { + //Decrement history index + self.history_index -= 1; + //Check if history has index + if let Some(cmd) = shell.history.at(self.history_index - 1) { + let prev_len: usize = self.input_buffer.len(); + //Clear buffer + self.clear_buffer(); + //Push command to buffer + for ch in cmd.chars() { + //Push character + self.input_buffer.push(ch); + //Increment buffer pointer + self.input_buffer_cursor += 1; + } + //Rewrite line + console::rewrite(cmd, prev_len); + } + } else if self.history_index == 1 { + let prev_len: usize = self.input_buffer.len(); + //Put history index to 0 + self.history_index = 0; + //Clear buffer + self.clear_buffer(); + console::rewrite(String::from(""), prev_len); + } + } + + /// ### perform_history_forward + /// + /// Get next element in history and put it into the buffer + fn perform_history_forward(&mut self, shell: &mut Shell) { + //Match history size + if self.history_index + 1 <= shell.history.len() { + //Increment history index + self.history_index += 1; + //Check if history has index + if let Some(cmd) = shell.history.at(self.history_index - 1) { + let prev_len: usize = self.input_buffer.len(); + //Clear buffer + self.clear_buffer(); + //Push command to buffer + for ch in cmd.chars() { + //Push character + self.input_buffer.push(ch); + //Increment buffer pointer + self.input_buffer_cursor += 1; + } + //Rewrite line + console::rewrite(cmd, prev_len); + } + } + } + + /// ### indent_history_index + /// + /// Format history index to 4 digts + fn indent_history_index(&self, index: usize) -> String { + if index < 10 { + format!(" {}", index) + } else if index < 100 { + format!(" {}", index) + } else if index < 1000 { + format!(" {}", index) + } else { + format!("{}", index) + } + } + + /// ### search_reverse + /// + /// Perform reverse search + /// Returns matched command in history + fn search_reverse(&mut self, shell: &Shell) -> Option { + let current_match: String = match &self.rev_search { + Some(s) => s.clone(), + None => return None, + }; + // Iterate over history + for i in self.rev_search_idx..shell.history.len() { + // Check if element at index matches (and is different than previous match) + if let Some(check_match) = shell.history.at(i) { + if check_match.contains(current_match.as_str()) { + // Update index + self.rev_search_idx = i + 1; // i + 1, in order to avoid same result at next cycle + // Return match + return Some(check_match.clone()); + } + } + } + // Return None if not found + None + } + + /// ### perform_interactive_enter + /// + /// Perform enter in interactive shell mode + fn perform_interactive_enter(&mut self, shell: &mut Shell) { + //Reset history index + self.reset_history_index(); + // Exit reverse search + self.rev_search = None; + //Newline first + console::println(String::new()); + //Convert input buffer to string + let stdin_input: String = buffer::chars_to_string(&self.input_buffer); + //If input is empty, print prompt (if state is IDLE) + if stdin_input.trim().len() == 0 { + console::print(format!("{} ", shell.get_promptline(&self.processor))); + self.clear_buffer(); + } else { + //Treat input + //If state is Idle, convert expression, otherwise convert text + let input: String = { + //Resolve alias + let mut argv: Vec = + Vec::with_capacity(stdin_input.matches(" ").count() + 1); + for arg in stdin_input.split_whitespace() { + argv.push(String::from(arg)); + } + //Process arg 0 + resolve_command(&mut argv, &self.config); + //Rejoin arguments + let input: String = argv.join(" ") + "\n"; + match &self.processor.expression_to_latin(&input) { + Ok(ex) => ex.clone(), + Err(err) => { + print_err( + String::from(format!("Input error: {:?}", err)), + self.config.output_config.translate_output, + &self.processor, + ); + //Clear input buffer + self.clear_buffer(); + return; + } + } + }; + //Clear input buffer + self.clear_buffer(); + //Process input + self.process_input_interactive(shell, input); + } + } + + /// ### process_input_interactive + /// + /// Process input after enter in interactive mode + fn process_input_interactive(&mut self, shell: &mut Shell, mut input: String) { + //@! Handle events before anything else + if input.starts_with("!") { + //Execute command from history + //Get index + let history_index: &str = &input.as_str()[1..input.len() - 1]; + //Convert index to number + if let Ok(history_index) = history_index.parse::() { + //Check if index is bigger than history lenght + if history_index >= shell.history.len() { + print_err( + format!("!{}: event not found", history_index), + self.config.output_config.translate_output, + &self.processor, + ); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + return; + } + //Reverse index + let history_index: usize = shell.history.len() - history_index - 1; + match shell.history.at(history_index) { + Some(cmd) => { + //Event exists, replace input with command + //Reverse index + input = format!("{}\n", cmd); + } + None => { + //Event doesn't exist + print_err( + format!("!{}: event not found", history_index), + self.config.output_config.translate_output, + &self.processor, + ); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + return; + } + } + } else { + //Event is Not a number + print_err( + format!("!{}: event not found", history_index), + self.config.output_config.translate_output, + &self.processor, + ); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + return; + } + } + //Push input to history + shell.history.push(input.clone()); + // @! Built-in commands + // Check if clear command + if input.starts_with("clear") { + //Clear screen, then write prompt + console::clear(); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + } else if input.starts_with("history") { + //Print history + let history_lines: Vec = shell.history.dump(); + for (idx, line) in history_lines.iter().enumerate() { + print_out( + format!("{} {}", self.indent_history_index(idx), line), + self.config.output_config.translate_output, + &self.processor, + ); + } + console::print(format!("{} ", shell.get_promptline(&self.processor))); + } else if input.starts_with("lev") { + // TODO: start lev + } else { + //@! Write input as usual + if let Err(err) = shell.write(input) { + print_err( + String::from(err.to_string()), + self.config.output_config.translate_output, + &self.processor, + ); + } + } + } +} + +impl Imiop for ShIop { + /// ### handle_input_event + /// + /// Handle input event received from stdin + fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell) { + match ev { + InputEvent::ArrowDown => { + //Get previous element in history + self.perform_history_backward(shell); + } + InputEvent::ArrowUp => { + //Get next element in history + self.perform_history_forward(shell); + } + InputEvent::ArrowLeft => { + self.move_left(); + } + InputEvent::ArrowRight => { + self.move_right(); + } + InputEvent::Backspace => { + self.backspace(); + } + InputEvent::CarriageReturn => { + console::carriage_return(); + } + InputEvent::Ctrl(sig) => { + //Check running state + //if running state is Idle, it will be handled by the console, + match sig { + 1 => { + //CTRL + A + //We must return at the beginning of the string + for _ in 0..self.input_buffer_cursor { + //Move left + console::move_cursor_left(); + } + self.input_buffer_cursor = 0; //Reset cursor + } + 2 => { + //CTRL + B + self.move_left(); + } + 3 => { + //CTRL + C + //Abort input and go to newline + self.clear_buffer(); + //Reset history index + self.reset_history_index(); + // Unset reverse search + self.rev_search = None; + console::println(String::new()); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + } + 4 => { + //CTRL + D + self.backspace(); + } + 5 => { + //CTRL + E + for _ in self.input_buffer_cursor..self.input_buffer.len() { + console::move_cursor_right(); + } + self.input_buffer_cursor = self.input_buffer.len(); + } + 6 => { + //CTRL + F + self.move_right(); + } + 7 => { + //CTRL + G + // exit rev search (and clear buffer) + self.rev_search = None; + self.rev_search_idx = 0; + //Abort input and go to newline + self.clear_buffer(); + console::println(String::new()); + console::print(format!("{} ", shell.get_promptline(&self.processor))); + } + 8 => { + //CTRL + H + self.backspace(); + } + 11 => { + // CTRL + K + //Delete all characters after cursor + while self.input_buffer_cursor < self.input_buffer.len() { + let _ = self.input_buffer.pop(); + } + } + 12 => { + // CTRL + L + //Clear, but doesn't reset input + console::clear(); + console::print(format!( + "{} {}", + shell.get_promptline(&self.processor), + buffer::chars_to_string(&self.input_buffer) + )); + } + 18 => { + // CTRL + R + // If reverse search is empty, set reverse search match + if self.rev_search.is_none() { + // Set reverse search to current input buffer + let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); + self.rev_search = Some(curr_stdin.clone()); + // Set index to first element (0) + self.rev_search_idx = 0; + // Write reverse-i-search prompt + console::rewrite( + format!( + "{}`{}': ", + console_fmt( + String::from("(reverse-i-search)"), + self.config.output_config.translate_output, + &self.processor + ), + curr_stdin + ), + curr_stdin.len(), + ); + } + // Find current input in history starting from bottom + if let Some(matched) = self.search_reverse(shell) { + // Set matched as current input + let prev_length: usize = self.input_buffer.len(); + self.input_buffer.clear(); + self.input_buffer = matched.chars().collect(); + // Set cursor to new length + self.input_buffer_cursor = self.input_buffer.len(); + // Print prompt + console::rewrite(matched, prev_length); + } + } + _ => {} //Unhandled + } + } + InputEvent::Key(k) => { + //Push key + //Push k to input buffer + for ch in k.chars() { + self.input_buffer.insert(self.input_buffer_cursor, ch); + self.input_buffer_cursor += 1; + } + // If rev search, put new input buffer to reverse search + if self.rev_search.is_some() { + // Set reverse search to current input buffer + let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); + self.rev_search = Some(curr_stdin.clone()); + } + //Print key + console::print(k); + } + InputEvent::Enter => { + //@! Send input + //@! Handle enter... + self.perform_interactive_enter(shell); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::config::Config; + use crate::translator::ioprocessor::IOProcessor; + use crate::translator::lang::Language; + use crate::translator::new_translator; + + use std::thread::sleep; + use std::time::Duration; + + #[test] + fn test_runtimeprops_new() { + let shiop = new_shiop(); + assert!(shiop.config.get_alias(&String::from("ll")).is_none()); + assert_eq!(shiop.processor.language, Language::Russian); + assert_eq!(shiop.input_buffer.capacity(), 2048); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.rev_search, None); + assert_eq!(shiop.rev_search_idx, 0); + assert_eq!(shiop.history_index, 0); + } + + #[test] + fn test_runtimeprops_clear_buffer() { + let mut shiop = new_shiop(); + shiop.input_buffer = vec!['a', 'b', 'c']; + shiop.input_buffer_cursor = 3; + shiop.clear_buffer(); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //History index + shiop.history_index = 128; + shiop.reset_history_index(); + assert_eq!(shiop.history_index, 0); + } + + #[test] + fn test_runtimeprops_backspace() { + let mut shiop = new_shiop(); + shiop.input_buffer = vec!['a', 'b', 'c']; + //If cursor is 0, cursor and input buffer won't change + shiop.backspace(); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.input_buffer.len(), 3); + shiop.input_buffer_cursor = 3; + //Backspace from end of buffer + shiop.backspace(); + assert_eq!(shiop.input_buffer_cursor, 2); + assert_eq!(shiop.input_buffer, vec!['a', 'b']); + //Set cursor to 1 and backspace from the middle + shiop.input_buffer_cursor = 1; + shiop.backspace(); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.input_buffer, vec!['b']); + //Try to delete with cursor out of range + shiop.input_buffer = vec!['a', 'b', 'c']; + shiop.input_buffer_cursor = 4; + shiop.backspace(); + assert_eq!(shiop.input_buffer_cursor, 3); + assert_eq!(shiop.input_buffer.len(), 3); + } + + #[test] + fn test_runtimeprops_move_cursor() { + let mut shiop = new_shiop(); + shiop.input_buffer = vec!['a', 'b', 'c', 'd', 'e']; + //Move left + shiop.input_buffer_cursor = 5; + shiop.move_left(); + assert_eq!(shiop.input_buffer_cursor, 4); + //Try to move left when is at 0 + shiop.input_buffer_cursor = 0; + shiop.move_left(); + assert_eq!(shiop.input_buffer_cursor, 0); + //Move right + shiop.move_right(); + assert_eq!(shiop.input_buffer_cursor, 1); + //Move out of bounds + shiop.input_buffer = vec!['a']; + shiop.move_right(); + assert_eq!(shiop.input_buffer_cursor, 1); + } + + #[test] + fn test_runtimeprops_handle_input_event() { + let mut shiop = new_shiop(); + let mut shell: Shell = + Shell::start(String::from("sh"), Vec::new(), &shiop.config.prompt_config).unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + //Prepare history + shell.history.push(String::from("pwd")); + shell.history.push(String::from("ls -l")); + assert_eq!(shiop.history_index, 0); + //Arrow up + shiop.handle_input_event(InputEvent::ArrowUp, &mut shell); + assert_eq!(shiop.history_index, 1); //History index increased + assert_eq!(shiop.input_buffer, vec!['l', 's', ' ', '-', 'l']); //ls -l + assert_eq!(shiop.input_buffer_cursor, 5); + //index 2 + shiop.handle_input_event(InputEvent::ArrowUp, &mut shell); + assert_eq!(shiop.history_index, 2); //History index increased + assert_eq!(shiop.input_buffer, vec!['p', 'w', 'd']); //pwd + assert_eq!(shiop.input_buffer_cursor, 3); + //Nothing bad should happen, input buffer won't change, history index won't be increased + shiop.handle_input_event(InputEvent::ArrowUp, &mut shell); + assert_eq!(shiop.history_index, 2); //History index didn't change + assert_eq!(shiop.input_buffer, vec!['p', 'w', 'd']); //pwd + assert_eq!(shiop.input_buffer_cursor, 3); + //Arrow down + shiop.handle_input_event(InputEvent::ArrowDown, &mut shell); + assert_eq!(shiop.history_index, 1); //History index decreased + assert_eq!(shiop.input_buffer, vec!['l', 's', ' ', '-', 'l']); //ls -l + assert_eq!(shiop.input_buffer_cursor, 5); + shiop.handle_input_event(InputEvent::ArrowDown, &mut shell); + assert_eq!(shiop.history_index, 0); //History index decreased + assert_eq!(shiop.input_buffer.len(), 0); //Empty + //Buffer should now be empty + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Another arrow down should change nothing + shiop.input_buffer = vec!['l', 's']; + shiop.input_buffer_cursor = 2; + shiop.handle_input_event(InputEvent::ArrowDown, &mut shell); + assert_eq!(shiop.history_index, 0); //History index decreased + assert_eq!(shiop.input_buffer.len(), 2); //Empty + assert_eq!(shiop.input_buffer_cursor, 2); + //Arrow left + //Move cursor to left by 1 position + shiop.input_buffer = vec!['l', 's', ' ', '-', 'l']; + shiop.input_buffer_cursor = 5; + shiop.handle_input_event(InputEvent::ArrowLeft, &mut shell); + assert_eq!(shiop.input_buffer_cursor, 4); + //Move cursor to right by 1 position + shiop.handle_input_event(InputEvent::ArrowRight, &mut shell); + assert_eq!(shiop.input_buffer_cursor, 5); + //Backspace + shiop.handle_input_event(InputEvent::Backspace, &mut shell); + assert_eq!(shiop.input_buffer, vec!['l', 's', ' ', '-']); + assert_eq!(shiop.input_buffer_cursor, 4); + //Carriage return + shiop.handle_input_event(InputEvent::CarriageReturn, &mut shell); + //CTRL A + shiop.handle_input_event(InputEvent::Ctrl(1), &mut shell); + assert_eq!(shiop.input_buffer_cursor, 0); + //CTRL B + shiop.input_buffer_cursor = 2; + shiop.handle_input_event(InputEvent::Ctrl(2), &mut shell); + assert_eq!(shiop.input_buffer_cursor, 1); + //CTRL C + shiop.history_index = 255; + shiop.handle_input_event(InputEvent::Ctrl(3), &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.history_index, 0); //Reset history index + //CTRL R ( reverse search; set input buffer to ifc) + shiop.input_buffer = vec!['i', 'f', 'c']; + shiop.input_buffer_cursor = 3; + shell.history.push(String::from("ifconfig eth0")); + shiop.handle_input_event(InputEvent::Ctrl(18), &mut shell); + // Input buffer should now be 'ifconfig eth' + assert_eq!( + shiop.input_buffer, + vec!['i', 'f', 'c', 'o', 'n', 'f', 'i', 'g', ' ', 'e', 't', 'h', '0'] + ); + assert_eq!(shiop.rev_search, Some(String::from("ifc"))); + assert_eq!(shiop.rev_search_idx, 1); // 0 + 1 + //CTRL G ( exit rev-search ) + shiop.handle_input_event(InputEvent::Ctrl(7), &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.rev_search, None); + assert_eq!(shiop.rev_search_idx, 0); // 0 + //CTRL D + shiop.input_buffer = vec!['l', 's', ' ', '-', 'l']; + shiop.input_buffer_cursor = 5; + shiop.handle_input_event(InputEvent::Ctrl(4), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l', 's', ' ', '-']); + assert_eq!(shiop.input_buffer_cursor, 4); + //CTRL E + shiop.input_buffer_cursor = 1; + shiop.handle_input_event(InputEvent::Ctrl(5), &mut shell); + assert_eq!(shiop.input_buffer_cursor, 4); + //CTRL F + shiop.input_buffer_cursor = 1; + shiop.handle_input_event(InputEvent::Ctrl(6), &mut shell); + assert_eq!(shiop.input_buffer_cursor, 2); + //CTRL H + shiop.handle_input_event(InputEvent::Ctrl(8), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l', ' ', '-']); + assert_eq!(shiop.input_buffer_cursor, 1); + //CTRL K + shiop.handle_input_event(InputEvent::Ctrl(11), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l']); + assert_eq!(shiop.input_buffer_cursor, 1); + //CTRL L + shiop.handle_input_event(InputEvent::Ctrl(12), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l']); + assert_eq!(shiop.input_buffer_cursor, 1); + //Unhandled ctrl key + shiop.handle_input_event(InputEvent::Ctrl(255), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l']); + assert_eq!(shiop.input_buffer_cursor, 1); + //Key + shiop.clear_buffer(); + shiop.handle_input_event(InputEvent::Key(String::from("l")), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l']); + assert_eq!(shiop.input_buffer_cursor, 1); + //Try UTF8 character + shiop.handle_input_event(InputEvent::Key(String::from("л")), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l', 'л']); + assert_eq!(shiop.input_buffer_cursor, 2); + //Add character one position behind + shiop.move_left(); + shiop.handle_input_event(InputEvent::Key(String::from("s")), &mut shell); + assert_eq!(shiop.input_buffer, vec!['l', 's', 'л']); + assert_eq!(shiop.input_buffer_cursor, 2); + shiop.input_buffer = Vec::new(); + shiop.input_buffer_cursor = 0; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.history_index, 0); + //Enter (command) + shiop.history_index = 255; + shiop.input_buffer = vec!['l', 's']; + shiop.input_buffer_cursor = 2; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + assert_eq!(shiop.history_index, 0); //Reset history index + //@! Check if ls is now in history + assert_eq!(shell.history.at(0).unwrap(), String::from("ls")); + //Enter (clear) + shiop.input_buffer = vec!['c', 'l', 'e', 'a', 'r']; + shiop.input_buffer_cursor = 5; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Enter (history) + shiop.input_buffer = vec!['h', 'i', 's', 't', 'o', 'r', 'y']; + shiop.input_buffer_cursor = 7; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Enter (! => Out of range) + shiop.input_buffer = vec!['!', '4', '0']; + shiop.input_buffer_cursor = 3; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Enter (! => Valid) + shiop.input_buffer = vec!['!', '1']; + shiop.input_buffer_cursor = 2; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Enter (! => String) + shiop.input_buffer = vec!['!', 'f', 'o', 'o']; + shiop.input_buffer_cursor = 4; + shiop.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(shiop.input_buffer.len(), 0); + assert_eq!(shiop.input_buffer_cursor, 0); + //Terminate shell + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + let _ = shell.stop(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + } + + #[test] + fn test_runtimeprops_indent_history_index() { + let shiop = new_shiop(); + assert_eq!(shiop.indent_history_index(0), String::from(" 0")); + assert_eq!(shiop.indent_history_index(10), String::from(" 10")); + assert_eq!(shiop.indent_history_index(100), String::from(" 100")); + assert_eq!(shiop.indent_history_index(1000), String::from("1000")); + } + + #[test] + fn test_runtimeprops_reverse_search() { + let mut shiop = new_shiop(); + let mut shell: Shell = + Shell::start(String::from("sh"), Vec::new(), &shiop.config.prompt_config).unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + //Prepare history + shell.history.push(String::from("pwd")); + shell.history.push(String::from("ifconfig")); + shell.history.push(String::from("ls -l")); + shell.history.push(String::from("ls")); + shell.history.push(String::from("ls -la")); + shell.history.push(String::from("lsd")); // Newer ls match + shell.history.push(String::from("if")); // Newer if match + // Perform reverse search + shiop.rev_search = Some(String::from("ls")); + shiop.rev_search_idx = 0; + assert_eq!(shiop.search_reverse(&mut shell), Some(String::from("lsd"))); + assert_eq!( + shiop.search_reverse(&mut shell), + Some(String::from("ls -la")) + ); + assert_eq!(shiop.search_reverse(&mut shell), Some(String::from("ls"))); + assert_eq!( + shiop.search_reverse(&mut shell), + Some(String::from("ls -l")) + ); + assert_eq!(shiop.search_reverse(&mut shell), None); + assert_eq!(shiop.search_reverse(&mut shell), None); // No panic? + } + + fn new_shiop() -> ShIop { + ShIop::new( + Config::default(), + IOProcessor::new(Language::Russian, new_translator(Language::Russian)), + ) + } +} diff --git a/src/runtime/imiop/subprociop.rs b/src/runtime/imiop/subprociop.rs new file mode 100644 index 0000000..c133ef6 --- /dev/null +++ b/src/runtime/imiop/subprociop.rs @@ -0,0 +1,255 @@ +//! ## subprociop +//! +//! `subprociop`, or Sub Process I/O Processor, is the implementation of the IMIOP trait to use +//! when in standard SubProcessRunning state + +/* +* +* Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com +* +* This file is part of "Pyc" +* +* Pyc is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Pyc is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Pyc. If not, see . +* +*/ + +use super::Imiop; +use crate::config::Config; +use crate::runtime::print_err; +use crate::shell::Shell; +use crate::translator::ioprocessor::IOProcessor; +use crate::utils::buffer; +use crate::utils::console::{self, InputEvent}; + +pub(crate) struct SubProcIop { + input_buffer: Vec, + input_buffer_cursor: usize, + config: Config, + processor: IOProcessor, +} + +impl SubProcIop { + /// ### new + /// + /// Instantiate a new `SubProcIop` + pub fn new(config: Config, processor: IOProcessor) -> SubProcIop { + SubProcIop { + input_buffer: Vec::with_capacity(2048), + input_buffer_cursor: 0, + config: config, + processor: processor, + } + } + + /// ### clear_buffer + /// + /// Clear buffer and reset cursor to 0 + fn clear_buffer(&mut self) { + self.input_buffer.clear(); + self.input_buffer_cursor = 0; + } + + /// ### backspace + /// + /// Perform backspace on current console and buffers + fn backspace(&mut self) { + //Remove from buffer and backspace (if possible) + if self.input_buffer_cursor > 0 { + self.input_buffer_cursor -= 1; + if self.input_buffer.len() > self.input_buffer_cursor { + self.input_buffer.remove(self.input_buffer_cursor); + } + console::backspace(); + } + } + + /// ### perform_enter + /// + /// Perform enter in non interactive shell + fn perform_enter(&mut self, shell: &mut Shell) { + //@! Handle enter... + let stdin_input: String = buffer::chars_to_string(&self.input_buffer); + //If input is empty, ignore it + if stdin_input.trim().len() > 0 { + //Treat input + //Convert text + let input: String = self.processor.text_to_latin(&stdin_input); + if let Err(err) = shell.write(input) { + print_err( + String::from(err.to_string()), + self.config.output_config.translate_output, + &self.processor, + ); + } + } + self.clear_buffer(); + } +} + +impl Imiop for SubProcIop { + /// ### handle_input_event + /// + /// Handle input event received from stdin + fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell) { + match ev { + InputEvent::ArrowDown => { + //Pass key + let _ = shell.write(console::input_event_to_string(ev)); + } + InputEvent::ArrowUp => { + //Pass key + let _ = shell.write(console::input_event_to_string(ev)); + } + InputEvent::ArrowLeft => { + //Pass key + let _ = shell.write(console::input_event_to_string(ev)); + } + InputEvent::ArrowRight => { + //Pass key + let _ = shell.write(console::input_event_to_string(ev)); + } + InputEvent::Backspace => { + self.backspace(); + } + InputEvent::CarriageReturn => { + let _ = shell.write(console::input_event_to_string(ev)); + } + InputEvent::Ctrl(_) => { + //Pass to child + //FIXME: doesn't work + let _ = shell.write(console::input_event_to_string(ev)); + //let mut output = String::with_capacity(1); + //output.push(sig as char); + //let _ = shell.write(output); + /* + if let Some(sig) = super::shellsignal_to_signal(sig) { + if let Err(_) = shell.raise(sig) { + print_err(String::from("Could not send signal to shell"), self.config.output_config.translate_output, &self.processor); + } + }*/ + } + InputEvent::Key(k) => { + //Push key + //Push k to input buffer + for ch in k.chars() { + self.input_buffer.insert(self.input_buffer_cursor, ch); + self.input_buffer_cursor += 1; + } + //Print key + console::print(k); + } + InputEvent::Enter => { + //@! Send input + self.perform_enter(shell); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::config::Config; + use crate::translator::ioprocessor::IOProcessor; + use crate::translator::lang::Language; + use crate::translator::new_translator; + + use std::thread::sleep; + use std::time::Duration; + + #[test] + fn test_runtimeprops_new() { + let processor = new_subprociop(); + assert!(processor.config.get_alias(&String::from("ll")).is_none()); + assert_eq!(processor.processor.language, Language::Russian); + assert_eq!(processor.input_buffer.capacity(), 2048); + assert_eq!(processor.input_buffer_cursor, 0); + } + + #[test] + fn test_runtimeprops_backspace() { + let mut processor = new_subprociop(); + processor.input_buffer = vec!['a', 'b', 'c']; + //If cursor is 0, cursor and input buffer won't change + processor.backspace(); + assert_eq!(processor.input_buffer_cursor, 0); + assert_eq!(processor.input_buffer.len(), 3); + processor.input_buffer_cursor = 3; + //Backspace from end of buffer + processor.backspace(); + assert_eq!(processor.input_buffer_cursor, 2); + assert_eq!(processor.input_buffer, vec!['a', 'b']); + //Set cursor to 1 and backspace from the middle + processor.input_buffer_cursor = 1; + processor.backspace(); + assert_eq!(processor.input_buffer_cursor, 0); + assert_eq!(processor.input_buffer, vec!['b']); + //Try to delete with cursor out of range + processor.input_buffer = vec!['a', 'b', 'c']; + processor.input_buffer_cursor = 4; + processor.backspace(); + assert_eq!(processor.input_buffer_cursor, 3); + assert_eq!(processor.input_buffer.len(), 3); + } + + #[test] + fn test_runtimeprops_handle_input_event_not_interactive() { + //Non interactive shell enter + let mut processor = new_subprociop(); + let mut shell: Shell = Shell::start( + String::from("sh"), + Vec::new(), + &processor.config.prompt_config, + ) + .unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + processor.input_buffer = vec!['l', 's']; + processor.input_buffer_cursor = 2; + processor.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(processor.input_buffer.len(), 0); + assert_eq!(processor.input_buffer_cursor, 0); + //Enter with empty buffer + processor.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(processor.input_buffer.len(), 0); + assert_eq!(processor.input_buffer_cursor, 0); + //Arrows + processor.handle_input_event(InputEvent::ArrowDown, &mut shell); + processor.handle_input_event(InputEvent::ArrowLeft, &mut shell); + processor.handle_input_event(InputEvent::ArrowRight, &mut shell); + processor.handle_input_event(InputEvent::ArrowUp, &mut shell); + //Signal + processor.handle_input_event(InputEvent::Ctrl(3), &mut shell); + //Stop shell + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + let _ = shell.stop(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + //Send signal once has terminated + processor.handle_input_event(InputEvent::Ctrl(2), &mut shell); + //Enter when process has terminated + processor.input_buffer = vec!['l', 's']; + processor.input_buffer_cursor = 2; + processor.handle_input_event(InputEvent::Enter, &mut shell); + assert_eq!(processor.input_buffer.len(), 0); + assert_eq!(processor.input_buffer_cursor, 0); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + } + + fn new_subprociop() -> SubProcIop { + SubProcIop::new( + Config::default(), + IOProcessor::new(Language::Russian, new_translator(Language::Russian)), + ) + } +} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index a639e97..9408931 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -27,7 +27,9 @@ extern crate ansi_term; extern crate nix; +// Runtime modules mod props; +mod imiop; use ansi_term::Colour; use std::path::{Path, PathBuf}; @@ -42,8 +44,10 @@ use props::RuntimeProps; use crate::shell::proc::ShellState; use crate::shell::{Shell}; use crate::shell::unixsignal::UnixSignal; -//Translator +// Translator use crate::translator::ioprocessor::IOProcessor; +use crate::translator::lang::Language; +use crate::translator::new_translator; //Utils use crate::utils::console; use crate::utils::file; @@ -54,9 +58,10 @@ use crate::utils::file; /// /// Run pyc in interactive mode -pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Option, history_file: Option) -> u8 { +pub fn run_interactive(language: Language, config: config::Config, shell: Option, history_file: Option) -> u8 { //Instantiate Runtime Props - let mut props: RuntimeProps = RuntimeProps::new(true, config, processor); + let mut props: RuntimeProps = RuntimeProps::new(true, config, language); + let processor: IOProcessor = IOProcessor::new(language, new_translator(language)); //Determine the shell to use let (shell, args): (String, Vec) = resolve_shell(&props.config, shell); //Intantiate and start a new shell @@ -66,7 +71,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op print_err( String::from(format!("Could not start shell: {}", err)), props.config.output_config.translate_output, - &props.processor, + &processor, ); return 255; } @@ -78,7 +83,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op Err(err) => print_err( String::from(format!("Could not load history from '{}': {}", history_file.display(), err)), props.config.output_config.translate_output, - &props.processor, + &processor, ) } }; @@ -93,7 +98,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op //Force shellenv to refresh info shell.refresh_env(); //Print prompt - console::print(format!("{} ", shell.get_promptline(&props.processor))); + console::print(format!("{} ", shell.get_promptline(&processor))); props.report_state_changed_notified(); //Force state changed to false } else if props.get_state_changed() { props.report_state_changed_notified(); //Check has been done, nothing to do @@ -108,7 +113,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op props.update_state(new_state); } //@! Read Shell stdout - read_from_shell(&mut shell, &props.config, &props.processor); + read_from_shell(&mut shell, &props.config, &processor); //Check if shell has terminated sleep(Duration::from_nanos(100)); //Sleep for 100ns } //@! End of loop @@ -119,7 +124,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op print_err( String::from(format!("Could not write history to '{}': {}", history_file.display(), err)), props.config.output_config.translate_output, - &props.processor, + &processor, ); } }; @@ -127,7 +132,7 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op match shell.stop() { Ok(rc) => rc, Err(err) => { - print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &props.processor); + print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &processor); 255 } } @@ -136,9 +141,10 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op /// ### run_command /// /// Run command in shell and return -pub fn run_command(mut command: String, processor: IOProcessor, config: config::Config, shell: Option) -> u8 { +pub fn run_command(mut command: String, language: Language, config: config::Config, shell: Option) -> u8 { //Instantiate Runtime Props - let mut props: RuntimeProps = RuntimeProps::new(false, config, processor); + let mut props: RuntimeProps = RuntimeProps::new(false, config, language); + let processor: IOProcessor = IOProcessor::new(language, new_translator(language)); //Determine the shell to use let (shell, args): (String, Vec) = resolve_shell(&props.config, shell); //Intantiate and start a new shell @@ -148,7 +154,7 @@ pub fn run_command(mut command: String, processor: IOProcessor, config: config:: print_err( String::from(format!("Could not start shell: {}", err)), props.config.output_config.translate_output, - &props.processor, + &processor, ); return 255; } @@ -167,7 +173,7 @@ pub fn run_command(mut command: String, processor: IOProcessor, config: config:: print_err( String::from(format!("Could not start shell: {}", err)), props.config.output_config.translate_output, - &props.processor, + &processor, ); return 255; } @@ -179,7 +185,7 @@ pub fn run_command(mut command: String, processor: IOProcessor, config: config:: props.handle_input_event(ev, &mut shell); }; //@! Read Shell stdout - read_from_shell(&mut shell, &props.config, &props.processor); + read_from_shell(&mut shell, &props.config, &processor); //Check if shell has terminated if shell.get_state() == ShellState::Terminated { break; @@ -190,7 +196,7 @@ pub fn run_command(mut command: String, processor: IOProcessor, config: config:: match shell.stop() { Ok(rc) => rc, Err(err) => { - print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &props.processor); + print_err(format!("Could not stop shell: {}", err), props.config.output_config.translate_output, &processor); 255 } } @@ -199,8 +205,9 @@ pub fn run_command(mut command: String, processor: IOProcessor, config: config:: /// ### run_file /// /// Run shell reading commands from file -pub fn run_file(file: String, processor: IOProcessor, config: config::Config, shell: Option) -> u8 { +pub fn run_file(file: String, language: Language, config: config::Config, shell: Option) -> u8 { let file_path: &Path = Path::new(file.as_str()); + let processor: IOProcessor = IOProcessor::new(language, new_translator(language)); let lines: Vec = match file::read_lines(file_path) { Ok(lines) => lines, Err(_) => { @@ -211,7 +218,7 @@ pub fn run_file(file: String, processor: IOProcessor, config: config::Config, sh //Join lines in a single command let command: String = script_lines_to_string(&lines); //Execute command - run_command(command, processor, config, shell) + run_command(command, language, config, shell) } //@! Shell functions diff --git a/src/runtime/props.rs b/src/runtime/props.rs index accb6d5..04c3493 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -23,79 +23,59 @@ * */ -use super::{print_err, print_out, console_fmt, resolve_command}; +use super::imiop::{self, Imiop}; use crate::config::Config; use crate::shell::Shell; use crate::shell::proc::ShellState; +use crate::translator::new_translator; +use crate::translator::lang::Language; use crate::translator::ioprocessor::IOProcessor; -use crate::utils::buffer; -use crate::utils::console::{self, InputEvent}; +use crate::utils::console::InputEvent; + +// TODO: convert interactive boolean to Enum ShellEnvMode /// ## RuntimeProps -/// +/// /// Runtime Props is a wrapper for all the properties used by the Runtime module pub(super) struct RuntimeProps { pub config: Config, - pub processor: IOProcessor, - input_buffer: Vec, - input_buffer_cursor: usize, - interactive: bool, + language: Language, last_state: ShellState, state_changed: bool, - rev_search: Option, // Reverse search match - rev_search_idx: usize, // Reverse search last match index - history_index: usize + imiop: Box } impl RuntimeProps { /// ### new /// /// Instantiates a new RuntimeProps - pub(super) fn new(interactive: bool, config: Config, processor: IOProcessor) -> RuntimeProps { + pub(super) fn new(interactive: bool, config: Config, language: Language) -> RuntimeProps { RuntimeProps { - config: config, - processor: processor, - input_buffer: Vec::with_capacity(2048), - input_buffer_cursor: 0, - interactive: interactive, + config: config.clone(), + language: language, last_state: ShellState::Unknown, state_changed: true, - rev_search: None, - rev_search_idx: 0, - history_index: 0 + imiop: RuntimeProps::init_imiop(interactive, &config, language) } } - /// ### clear_buffer - /// - /// Clear buffer and reset cursor to 0 - pub(super) fn clear_buffer(&mut self) { - self.input_buffer.clear(); - self.input_buffer_cursor = 0; - } - - fn reset_history_index(&mut self) { - //Reset history index too - self.history_index = 0; - } - /// ### get_state - /// + /// /// Get Shell State pub(super) fn get_last_state(&self) -> ShellState { self.last_state } /// ### get_state_changed - /// + /// /// Get state changed value pub(super) fn get_state_changed(&self) -> bool { self.state_changed } /// ### update_state - /// + /// /// Update last state pub(super) fn update_state(&mut self, new_state: ShellState) { self.last_state = new_state; @@ -103,460 +83,51 @@ impl RuntimeProps { } /// ### state_changed_notified - /// + /// /// Report that state changed has been notified correctly. /// Pratically resets state_changed pub(super) fn report_state_changed_notified(&mut self) { self.state_changed = false; } - /// ### backspace - /// - /// Perform backspace on current console and buffers - pub(super) fn backspace(&mut self) { - //Remove from buffer and backspace (if possible) - if self.input_buffer_cursor > 0 { - self.input_buffer_cursor -= 1; - if self.input_buffer.len() > self.input_buffer_cursor { - self.input_buffer.remove(self.input_buffer_cursor); - } - console::backspace(); - } - } - - /// ### move_left - /// - /// Move cursor to left - pub(super) fn move_left(&mut self) { - //If possible, move the cursor right - if self.input_buffer_cursor != 0 { - self.input_buffer_cursor -= 1; - console::move_cursor_left(); - } - } - - /// ### move_right - /// - /// Move cursor to right - pub(super) fn move_right(&mut self) { - //If possible, move the cursor left - if self.input_buffer_cursor + 1 <= self.input_buffer.len() { - self.input_buffer_cursor += 1; - console::move_cursor_right(); - } - } - /// ### handle_input_event - /// + /// /// Handle input event received from stdin pub(super) fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell) { - match ev { - InputEvent::ArrowDown => { - if self.interactive && self.last_state == ShellState::Idle { - //Get previous element in history - self.perform_history_backward(shell); - } else { - //Pass key - let _ = shell.write(console::input_event_to_string(ev)); - } - }, - InputEvent::ArrowUp => { - if self.interactive && self.last_state == ShellState::Idle { - //Get next element in history - self.perform_history_forward(shell); - } else { - //Pass key - let _ = shell.write(console::input_event_to_string(ev)); - } - }, - InputEvent::ArrowLeft => { - if self.interactive && self.last_state == ShellState::Idle { - self.move_left(); - } else { - //Pass key - let _ = shell.write(console::input_event_to_string(ev)); - } - }, - InputEvent::ArrowRight => { - if self.interactive && self.last_state == ShellState::Idle { - self.move_right(); - } else { - //Pass key - let _ = shell.write(console::input_event_to_string(ev)); - } - }, - InputEvent::Backspace => { - self.backspace(); - }, - InputEvent::CarriageReturn => { - if self.interactive && self.last_state == ShellState::Idle { - console::carriage_return(); - } else { - let _ = shell.write(console::input_event_to_string(ev)); - } - }, - InputEvent::Ctrl(sig) => { - //Check running state - //if running state is Idle, it will be handled by the console, - //otherwise by the shell process - if self.last_state == ShellState::Idle && self.interactive { - match sig { - 1 => { //CTRL + A - //We must return at the beginning of the string - for _ in 0..self.input_buffer_cursor { - //Move left - console::move_cursor_left(); - } - self.input_buffer_cursor = 0; //Reset cursor - }, - 2 => { //CTRL + B - self.move_left(); - }, - 3 => { //CTRL + C - //Abort input and go to newline - self.clear_buffer(); - //Reset history index - self.reset_history_index(); - // Unset reverse search - self.rev_search = None; - console::println(String::new()); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - }, - 4 => { //CTRL + D - self.backspace(); - }, - 5 => { //CTRL + E - for _ in self.input_buffer_cursor..self.input_buffer.len() { - console::move_cursor_right(); - } - self.input_buffer_cursor = self.input_buffer.len(); - }, - 6 => { //CTRL + F - self.move_right(); - }, - 7 => { //CTRL + G - // exit rev search (and clear buffer) - self.rev_search = None; - self.rev_search_idx = 0; - //Abort input and go to newline - self.clear_buffer(); - console::println(String::new()); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - }, - 8 => { //CTRL + H - self.backspace(); - }, - 11 => { // CTRL + K - //Delete all characters after cursor - while self.input_buffer_cursor < self.input_buffer.len() { - let _ = self.input_buffer.pop(); - } - }, - 12 => { // CTRL + L - //Clear, but doesn't reset input - console::clear(); - console::print(format!("{} {}", shell.get_promptline(&self.processor), buffer::chars_to_string(&self.input_buffer))); - }, - 18 => { // CTRL + R - // If reverse search is empty, set reverse search match - if self.rev_search.is_none() { - // Set reverse search to current input buffer - let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); - self.rev_search = Some(curr_stdin.clone()); - // Set index to first element (0) - self.rev_search_idx = 0; - // Write reverse-i-search prompt - console::rewrite(format!("{}`{}': ", console_fmt(String::from("(reverse-i-search)"), self.config.output_config.translate_output, &self.processor), curr_stdin), curr_stdin.len()); - } - // Find current input in history starting from bottom - if let Some(matched) = self.search_reverse(shell) { - // Set matched as current input - let prev_length: usize = self.input_buffer.len(); - self.input_buffer.clear(); - self.input_buffer = matched.chars().collect(); - // Set cursor to new length - self.input_buffer_cursor = self.input_buffer.len(); - // Print prompt - console::rewrite(matched, prev_length); - } - }, - _ => {} //Unhandled - } - } else { - //Pass to child - //FIXME: doesn't work - let _ = shell.write(console::input_event_to_string(ev)); - //let mut output = String::with_capacity(1); - //output.push(sig as char); - //let _ = shell.write(output); - /* - if let Some(sig) = super::shellsignal_to_signal(sig) { - if let Err(_) = shell.raise(sig) { - print_err(String::from("Could not send signal to shell"), self.config.output_config.translate_output, &self.processor); - } - }*/ - } - }, - InputEvent::Key(k) => { //Push key - //Push k to input buffer - for ch in k.chars() { - self.input_buffer.insert(self.input_buffer_cursor, ch); - self.input_buffer_cursor += 1; - } - // If rev search, put new input buffer to reverse search - if self.rev_search.is_some() { - // Set reverse search to current input buffer - let curr_stdin: String = buffer::chars_to_string(&self.input_buffer); - self.rev_search = Some(curr_stdin.clone()); - } - //Print key - console::print(k); - }, - InputEvent::Enter => { //@! Send input - //@! Handle enter... - if self.interactive { //@! Interactive shell - self.perform_interactive_enter(shell); - } else { //@! Non interactive shell - self.perform_enter(shell); - } - } - } + // Check if IMIOP has to be changed + self.switch_imiop(); + // Call handle input event for current IMIOP + self.imiop.handle_input_event(ev, shell); } - /// ### perform_interactive_enter + /// ### init_imiop /// - /// Perform enter in interactive shell mode - fn perform_interactive_enter(&mut self, shell: &mut Shell) { - //Reset history index - self.reset_history_index(); - // Exit reverse search - self.rev_search = None; - //Newline first - console::println(String::new()); - //Convert input buffer to string - let stdin_input: String = buffer::chars_to_string(&self.input_buffer); - //If input is empty, print prompt (if state is IDLE) - if stdin_input.trim().len() == 0 { - if self.last_state == ShellState::Idle { - console::print(format!("{} ", shell.get_promptline(&self.processor))); - } - self.clear_buffer(); - } else { - //Treat input - //If state is Idle, convert expression, otherwise convert text - let input: String = match self.last_state { - ShellState::Idle => { - //Resolve alias - let mut argv: Vec = Vec::with_capacity(stdin_input.matches(" ").count() + 1); - for arg in stdin_input.split_whitespace() { - argv.push(String::from(arg)); - } - //Process arg 0 - resolve_command(&mut argv, &self.config); - //Rejoin arguments - let input: String = argv.join(" ") + "\n"; - match self.processor.expression_to_latin(&input) { - Ok(ex) => ex, - Err(err) => { - print_err(String::from(format!("Input error: {:?}", err)), self.config.output_config.translate_output, &self.processor); - //Clear input buffer - self.clear_buffer(); - return; - } - } - }, - ShellState::SubprocessRunning => self.processor.text_to_latin(&buffer::chars_to_string(&self.input_buffer)), - _ => { - self.clear_buffer(); - return; - } - }; - //Clear input buffer - self.clear_buffer(); - //Process input - self.process_input_interactive(shell, input); - } - } + /// Instantiate the first IMIOP at first launch of props - /// ### perform_enter - /// - /// Perform enter in non interactive shell - fn perform_enter(&mut self, shell: &mut Shell) { - //@! Handle enter... - let stdin_input: String = buffer::chars_to_string(&self.input_buffer); - //If input is empty, ignore it - if stdin_input.trim().len() > 0 { - //Treat input - //Convert text - let input: String = self.processor.text_to_latin(&stdin_input); - if let Err(err) = shell.write(input) { - print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor); - } - } - self.clear_buffer(); - } - - /// ### process_input_interactive - /// - /// Process input after enter in interactive mode - fn process_input_interactive(&mut self, shell: &mut Shell, mut input: String) { - if self.last_state == ShellState::Idle { - //@! Handle events before anything else - if input.starts_with("!") { - //Execute command from history - //Get index - let history_index: &str = &input.as_str()[1..input.len() - 1]; - //Convert index to number - if let Ok(history_index) = history_index.parse::() { - //Check if index is bigger than history lenght - if history_index >= shell.history.len() { - print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - return; - } - //Reverse index - let history_index: usize = shell.history.len() - history_index - 1; - match shell.history.at(history_index) { - Some(cmd) => { //Event exists, replace input with command - //Reverse index - input = format!("{}\n", cmd); - }, - None => { //Event doesn't exist - print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - return; - } - } - } else { //Event is Not a number - print_err(format!("!{}: event not found", history_index), self.config.output_config.translate_output, &self.processor); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - return; - } - } - //Push input to history - shell.history.push(input.clone()); - //Check if clear command - if input.starts_with("clear") { - //Clear screen, then write prompt - console::clear(); - console::print(format!("{} ", shell.get_promptline(&self.processor))); - } else if input.starts_with("history") { - //Print history - let history_lines: Vec = shell.history.dump(); - for (idx, line) in history_lines.iter().enumerate() { - print_out(format!("{} {}", self.indent_history_index(idx), line), self.config.output_config.translate_output, &self.processor); - } - console::print(format!("{} ", shell.get_promptline(&self.processor))); - } else { //@! Write input as usual - if let Err(err) = shell.write(input) { - print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor); - } - } - } else { //Write input as usual - if let Err(err) = shell.write(input) { - print_err(String::from(err.to_string()), self.config.output_config.translate_output, &self.processor); - } + fn init_imiop(interactive: bool, config: &Config, language: Language) -> Box { + match interactive { + true => Box::new(imiop::shiop::ShIop::new(config.clone(), IOProcessor::new(language, new_translator(language)))), + false => Box::new(imiop::subprociop::SubProcIop::new(config.clone(), IOProcessor::new(language, new_translator(language)))) } } - /// ### perform_history_backward + /// ### switch_imiop /// - /// Get previous element in history and put it into the buffer - fn perform_history_backward(&mut self, shell: &mut Shell) { - //Match history size - if self.history_index > 1 { - //Decrement history index - self.history_index -= 1; - //Check if history has index - if let Some(cmd) = shell.history.at(self.history_index - 1) { - let prev_len: usize = self.input_buffer.len(); - //Clear buffer - self.clear_buffer(); - //Push command to buffer - for ch in cmd.chars() { - //Push character - self.input_buffer.push(ch); - //Increment buffer pointer - self.input_buffer_cursor += 1; - } - //Rewrite line - console::rewrite(cmd, prev_len); - } - } else if self.history_index == 1 { - let prev_len: usize = self.input_buffer.len(); - //Put history index to 0 - self.history_index = 0; - //Clear buffer - self.clear_buffer(); - console::rewrite(String::from(""), prev_len); - } - } - - /// ### perform_history_forward - /// - /// Get next element in history and put it into the buffer - fn perform_history_forward(&mut self, shell: &mut Shell) { - //Match history size - if self.history_index + 1 <= shell.history.len() { - //Increment history index - self.history_index += 1; - //Check if history has index - if let Some(cmd) = shell.history.at(self.history_index - 1) { - let prev_len: usize = self.input_buffer.len(); - //Clear buffer - self.clear_buffer(); - //Push command to buffer - for ch in cmd.chars() { - //Push character - self.input_buffer.push(ch); - //Increment buffer pointer - self.input_buffer_cursor += 1; - } - //Rewrite line - console::rewrite(cmd, prev_len); - } - } - } - - /// ### indent_history_index - /// - /// Format history index to 4 digts - fn indent_history_index(&self, index: usize) -> String { - if index < 10 { - format!(" {}", index) - } else if index < 100 { - format!(" {}", index) - } else if index < 1000 { - format!(" {}", index) - } else { - format!("{}", index) - } - } - - /// ### search_reverse - /// - /// Perform reverse search - /// Returns matched command in history - - fn search_reverse(&mut self, shell: &Shell) -> Option { - let current_match: String = match &self.rev_search { - Some(s) => s.clone(), - None => return None - }; - // Iterate over history - for i in self.rev_search_idx..shell.history.len() { - // Check if element at index matches (and is different than previous match) - if let Some(check_match) = shell.history.at(i) { - if check_match.contains(current_match.as_str()) { - // Update index - self.rev_search_idx = i + 1; // i + 1, in order to avoid same result at next cycle - // Return match - return Some(check_match.clone()) - } - } + /// Change current imiop based on states + fn switch_imiop(&mut self) { + // Change if last state changed + if self.get_state_changed() { + // TODO: check environmental shell state + // If in standard state... + // Check last_state + self.imiop = match self.get_last_state() { + ShellState::Idle => Box::new(imiop::shiop::ShIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))), + ShellState::SubprocessRunning => Box::new(imiop::subprociop::SubProcIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))), + _ => Box::new(imiop::shiop::ShIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))) + }; + // Reset state changed + self.report_state_changed_notified(); } - // Return None if not found - None } } @@ -565,40 +136,18 @@ mod tests { use super::*; use crate::config::Config; - use crate::translator::ioprocessor::IOProcessor; use crate::translator::lang::Language; - use crate::translator::new_translator; - use std::time::Duration; - use std::thread::sleep; + // use std::thread::sleep; + // use std::time::Duration; #[test] fn test_runtimeprops_new() { let props: RuntimeProps = new_runtime_props(true); assert!(props.config.get_alias(&String::from("ll")).is_none()); - assert_eq!(props.processor.language, Language::Russian); - assert_eq!(props.input_buffer.capacity(), 2048); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.interactive, true); + assert_eq!(props.language, Language::Russian); assert_eq!(props.last_state, ShellState::Unknown); assert_eq!(props.state_changed, true); - assert_eq!(props.rev_search, None); - assert_eq!(props.rev_search_idx, 0); - assert_eq!(props.history_index, 0); - } - - #[test] - fn test_runtimeprops_clear_buffer() { - let mut props: RuntimeProps = new_runtime_props(false); - props.input_buffer = vec!['a', 'b', 'c']; - props.input_buffer_cursor = 3; - props.clear_buffer(); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //History index - props.history_index = 128; - props.reset_history_index(); - assert_eq!(props.history_index, 0); } #[test] @@ -613,338 +162,22 @@ mod tests { assert_eq!(props.get_state_changed(), true); } - #[test] - fn test_runtimeprops_backspace() { - let mut props: RuntimeProps = new_runtime_props(true); - props.input_buffer = vec!['a', 'b', 'c']; - //If cursor is 0, cursor and input buffer won't change - props.backspace(); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.input_buffer.len(), 3); - props.input_buffer_cursor = 3; - //Backspace from end of buffer - props.backspace(); - assert_eq!(props.input_buffer_cursor, 2); - assert_eq!(props.input_buffer, vec!['a', 'b']); - //Set cursor to 1 and backspace from the middle - props.input_buffer_cursor = 1; - props.backspace(); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.input_buffer, vec!['b']); - //Try to delete with cursor out of range - props.input_buffer = vec!['a', 'b', 'c']; - props.input_buffer_cursor = 4; - props.backspace(); - assert_eq!(props.input_buffer_cursor, 3); - assert_eq!(props.input_buffer.len(), 3); - } - - #[test] - fn test_runtimeprops_move_cursor() { - let mut props: RuntimeProps = new_runtime_props(true); - props.input_buffer = vec!['a', 'b', 'c', 'd', 'e']; - //Move left - props.input_buffer_cursor = 5; - props.move_left(); - assert_eq!(props.input_buffer_cursor, 4); - //Try to move left when is at 0 - props.input_buffer_cursor = 0; - props.move_left(); - assert_eq!(props.input_buffer_cursor, 0); - //Move right - props.move_right(); - assert_eq!(props.input_buffer_cursor, 1); - //Move out of bounds - props.input_buffer = vec!['a']; - props.move_right(); - assert_eq!(props.input_buffer_cursor, 1); - } - #[test] fn test_runtimeprops_handle_input_event() { - let mut props: RuntimeProps = new_runtime_props(true); - let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - props.update_state(ShellState::Idle); - //Prepare history - shell.history.push(String::from("pwd")); - shell.history.push(String::from("ls -l")); - assert_eq!(props.history_index, 0); - //Arrow up - props.handle_input_event(InputEvent::ArrowUp, &mut shell); - assert_eq!(props.history_index, 1); //History index increased - assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-', 'l']); //ls -l - assert_eq!(props.input_buffer_cursor, 5); - //index 2 - props.handle_input_event(InputEvent::ArrowUp, &mut shell); - assert_eq!(props.history_index, 2); //History index increased - assert_eq!(props.input_buffer, vec!['p', 'w', 'd']); //pwd - assert_eq!(props.input_buffer_cursor, 3); - //Nothing bad should happen, input buffer won't change, history index won't be increased - props.handle_input_event(InputEvent::ArrowUp, &mut shell); - assert_eq!(props.history_index, 2); //History index didn't change - assert_eq!(props.input_buffer, vec!['p', 'w', 'd']); //pwd - assert_eq!(props.input_buffer_cursor, 3); - //Arrow down - props.handle_input_event(InputEvent::ArrowDown, &mut shell); - assert_eq!(props.history_index, 1); //History index decreased - assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-', 'l']); //ls -l - assert_eq!(props.input_buffer_cursor, 5); - props.handle_input_event(InputEvent::ArrowDown, &mut shell); - assert_eq!(props.history_index, 0); //History index decreased - assert_eq!(props.input_buffer.len(), 0); //Empty - //Buffer should now be empty - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Another arrow down should change nothing - props.input_buffer = vec!['l', 's']; - props.input_buffer_cursor = 2; - props.handle_input_event(InputEvent::ArrowDown, &mut shell); - assert_eq!(props.history_index, 0); //History index decreased - assert_eq!(props.input_buffer.len(), 2); //Empty - assert_eq!(props.input_buffer_cursor, 2); - //Arrow left - //Move cursor to left by 1 position - props.input_buffer = vec!['l', 's', ' ', '-', 'l']; - props.input_buffer_cursor = 5; - props.handle_input_event(InputEvent::ArrowLeft, &mut shell); - assert_eq!(props.input_buffer_cursor, 4); - //Move cursor to right by 1 position - props.handle_input_event(InputEvent::ArrowRight, &mut shell); - assert_eq!(props.input_buffer_cursor, 5); - //Backspace - props.handle_input_event(InputEvent::Backspace, &mut shell); - assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-']); - assert_eq!(props.input_buffer_cursor, 4); - //Carriage return - props.handle_input_event(InputEvent::CarriageReturn, &mut shell); - //Ctrl (interactive mode) - props.last_state = ShellState::Idle; - //CTRL A - props.handle_input_event(InputEvent::Ctrl(1), &mut shell); - assert_eq!(props.input_buffer_cursor, 0); - //CTRL B - props.input_buffer_cursor = 2; - props.handle_input_event(InputEvent::Ctrl(2), &mut shell); - assert_eq!(props.input_buffer_cursor, 1); - //CTRL C - props.history_index = 255; - props.handle_input_event(InputEvent::Ctrl(3), &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.history_index, 0); //Reset history index - //CTRL R ( reverse search; set input buffer to ifc) - props.input_buffer = vec!['i', 'f', 'c']; - props.input_buffer_cursor = 3; - shell.history.push(String::from("ifconfig eth0")); - props.handle_input_event(InputEvent::Ctrl(18), &mut shell); - // Input buffer should now be 'ifconfig eth' - assert_eq!(props.input_buffer, vec!['i', 'f', 'c', 'o', 'n', 'f', 'i', 'g', ' ', 'e', 't', 'h', '0']); - assert_eq!(props.rev_search, Some(String::from("ifc"))); - assert_eq!(props.rev_search_idx, 1); // 0 + 1 - //CTRL G ( exit rev-search ) - props.handle_input_event(InputEvent::Ctrl(7), &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.rev_search, None); - assert_eq!(props.rev_search_idx, 0); // 0 - //CTRL D - props.input_buffer = vec!['l', 's', ' ', '-', 'l']; - props.input_buffer_cursor = 5; - props.handle_input_event(InputEvent::Ctrl(4), &mut shell); - assert_eq!(props.input_buffer, vec!['l', 's', ' ', '-']); - assert_eq!(props.input_buffer_cursor, 4); - //CTRL E - props.input_buffer_cursor = 1; - props.handle_input_event(InputEvent::Ctrl(5), &mut shell); - assert_eq!(props.input_buffer_cursor, 4); - //CTRL F - props.input_buffer_cursor = 1; - props.handle_input_event(InputEvent::Ctrl(6), &mut shell); - assert_eq!(props.input_buffer_cursor, 2); - //CTRL H - props.handle_input_event(InputEvent::Ctrl(8), &mut shell); - assert_eq!(props.input_buffer, vec!['l', ' ', '-']); - assert_eq!(props.input_buffer_cursor, 1); - //CTRL K - props.handle_input_event(InputEvent::Ctrl(11), &mut shell); - assert_eq!(props.input_buffer, vec!['l']); - assert_eq!(props.input_buffer_cursor, 1); - //CTRL L - props.handle_input_event(InputEvent::Ctrl(12), &mut shell); - assert_eq!(props.input_buffer, vec!['l']); - assert_eq!(props.input_buffer_cursor, 1); - //Unhandled ctrl key - props.handle_input_event(InputEvent::Ctrl(255), &mut shell); - assert_eq!(props.input_buffer, vec!['l']); - assert_eq!(props.input_buffer_cursor, 1); - //Key - props.clear_buffer(); - props.handle_input_event(InputEvent::Key(String::from("l")), &mut shell); - assert_eq!(props.input_buffer, vec!['l']); - assert_eq!(props.input_buffer_cursor, 1); - //Try UTF8 character - props.handle_input_event(InputEvent::Key(String::from("л")), &mut shell); - assert_eq!(props.input_buffer, vec!['l', 'л']); - assert_eq!(props.input_buffer_cursor, 2); - //Add character one position behind - props.move_left(); - props.handle_input_event(InputEvent::Key(String::from("s")), &mut shell); - assert_eq!(props.input_buffer, vec!['l', 's', 'л']); - assert_eq!(props.input_buffer_cursor, 2); - //Enter (empty buffer) - props.last_state = ShellState::Idle; - props.input_buffer = Vec::new(); - props.input_buffer_cursor = 0; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.history_index, 0); - //Enter (command) - props.history_index = 255; - props.last_state = ShellState::Idle; - props.input_buffer = vec!['l', 's']; - props.input_buffer_cursor = 2; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - assert_eq!(props.history_index, 0); //Reset history index - //@! Check if ls is now in history - assert_eq!(shell.history.at(0).unwrap(), String::from("ls")); - //Enter (clear) - props.last_state = ShellState::Idle; - props.input_buffer = vec!['c', 'l', 'e', 'a', 'r']; - props.input_buffer_cursor = 5; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter (history) - props.last_state = ShellState::Idle; - props.input_buffer = vec!['h', 'i', 's', 't', 'o', 'r', 'y']; - props.input_buffer_cursor = 7; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter (! => Out of range) - props.last_state = ShellState::Idle; - props.input_buffer = vec!['!', '4', '0']; - props.input_buffer_cursor = 3; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter (! => Valid) - props.input_buffer = vec!['!', '1']; - props.input_buffer_cursor = 2; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter (! => String) - props.input_buffer = vec!['!', 'f', 'o', 'o']; - props.input_buffer_cursor = 4; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter once has terminated - props.last_state = ShellState::Terminated; - props.input_buffer = vec!['l', 's']; - props.input_buffer_cursor = 2; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Write as text - props.last_state = ShellState::SubprocessRunning; - props.input_buffer = vec!['h', 'e', 'l', 'l', 'o']; - props.input_buffer_cursor = 5; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //CTRL key non interactive (or shell state not Idle) - //SIGINT - props.handle_input_event(InputEvent::Ctrl(2), &mut shell); - //Unhandled signal - props.handle_input_event(InputEvent::Ctrl(1), &mut shell); - //Terminate shell - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - let _ = shell.stop(); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + // TODO: REDO } #[test] fn test_runtimeprops_handle_input_event_not_interactive() { - //Non interactive shell enter + // TODO: redo let mut props: RuntimeProps = new_runtime_props(false); - let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - props.input_buffer = vec!['l', 's']; - props.input_buffer_cursor = 2; - props.last_state = ShellState::SubprocessRunning; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Enter with empty buffer - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - //Arrows - props.handle_input_event(InputEvent::ArrowDown, &mut shell); - props.handle_input_event(InputEvent::ArrowLeft, &mut shell); - props.handle_input_event(InputEvent::ArrowRight, &mut shell); - props.handle_input_event(InputEvent::ArrowUp, &mut shell); - //Signal - props.handle_input_event(InputEvent::Ctrl(3), &mut shell); - //Stop shell - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - let _ = shell.stop(); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - assert_eq!(shell.get_state(), ShellState::Terminated); - //Send signal once has terminated - props.last_state = ShellState::SubprocessRunning; - props.handle_input_event(InputEvent::Ctrl(2), &mut shell); - //Enter when process has terminated - props.input_buffer = vec!['l', 's']; - props.input_buffer_cursor = 2; - props.last_state = ShellState::SubprocessRunning; - props.handle_input_event(InputEvent::Enter, &mut shell); - assert_eq!(props.input_buffer.len(), 0); - assert_eq!(props.input_buffer_cursor, 0); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - } - - #[test] - fn test_runtimeprops_indent_history_index() { - let props: RuntimeProps = new_runtime_props(true); - assert_eq!(props.indent_history_index(0), String::from(" 0")); - assert_eq!(props.indent_history_index(10), String::from(" 10")); - assert_eq!(props.indent_history_index(100), String::from(" 100")); - assert_eq!(props.indent_history_index(1000), String::from("1000")); - } - - #[test] - fn test_runtimeprops_reverse_search() { - let mut props: RuntimeProps = new_runtime_props(true); - let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); - sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - props.update_state(ShellState::Idle); - //Prepare history - shell.history.push(String::from("pwd")); - shell.history.push(String::from("ifconfig")); - shell.history.push(String::from("ls -l")); - shell.history.push(String::from("ls")); - shell.history.push(String::from("ls -la")); - shell.history.push(String::from("lsd")); // Newer ls match - shell.history.push(String::from("if")); // Newer if match - // Perform reverse search - props.rev_search = Some(String::from("ls")); - props.rev_search_idx = 0; - assert_eq!(props.search_reverse(&mut shell), Some(String::from("lsd"))); - assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls -la"))); - assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls"))); - assert_eq!(props.search_reverse(&mut shell), Some(String::from("ls -l"))); - assert_eq!(props.search_reverse(&mut shell), None); - assert_eq!(props.search_reverse(&mut shell), None); // No panic? } fn new_runtime_props(interactive: bool) -> RuntimeProps { - RuntimeProps::new(interactive, Config::default(), IOProcessor::new(Language::Russian, new_translator(Language::Russian))) + RuntimeProps::new( + interactive, + Config::default(), + Language::Russian, + ) } } From 5cd1356f950e1c69864ccba6e0e48f6f3329347f Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 10:07:22 +0100 Subject: [PATCH 24/32] RuntimeProps tests --- src/runtime/imiop/mod.rs | 6 +-- src/runtime/props.rs | 103 +++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/runtime/imiop/mod.rs b/src/runtime/imiop/mod.rs index ee43ffa..2a5bc12 100644 --- a/src/runtime/imiop/mod.rs +++ b/src/runtime/imiop/mod.rs @@ -34,15 +34,13 @@ pub(crate) mod shiop; pub(crate) mod subprociop; /// ## Imiop -/// +/// /// Imiop (interactive mode I/O processor) defines the methods an Imiop has to implement pub(crate) trait Imiop { - /// ### handle_input_event - /// + /// /// Handle input event received from stdin fn handle_input_event(&mut self, ev: InputEvent, shell: &mut Shell); - } // TODO: add factory for imiop diff --git a/src/runtime/props.rs b/src/runtime/props.rs index 04c3493..4b8c2b1 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -26,11 +26,11 @@ use super::imiop::{self, Imiop}; use crate::config::Config; -use crate::shell::Shell; use crate::shell::proc::ShellState; -use crate::translator::new_translator; -use crate::translator::lang::Language; +use crate::shell::Shell; use crate::translator::ioprocessor::IOProcessor; +use crate::translator::lang::Language; +use crate::translator::new_translator; use crate::utils::console::InputEvent; // TODO: convert interactive boolean to Enum ShellEnvMode @@ -43,7 +43,7 @@ pub(super) struct RuntimeProps { language: Language, last_state: ShellState, state_changed: bool, - imiop: Box + imiop: Box, } impl RuntimeProps { @@ -56,7 +56,7 @@ impl RuntimeProps { language: language, last_state: ShellState::Unknown, state_changed: true, - imiop: RuntimeProps::init_imiop(interactive, &config, language) + imiop: RuntimeProps::init_imiop(interactive, &config, language), } } @@ -101,18 +101,24 @@ impl RuntimeProps { } /// ### init_imiop - /// + /// /// Instantiate the first IMIOP at first launch of props fn init_imiop(interactive: bool, config: &Config, language: Language) -> Box { match interactive { - true => Box::new(imiop::shiop::ShIop::new(config.clone(), IOProcessor::new(language, new_translator(language)))), - false => Box::new(imiop::subprociop::SubProcIop::new(config.clone(), IOProcessor::new(language, new_translator(language)))) + true => Box::new(imiop::shiop::ShIop::new( + config.clone(), + IOProcessor::new(language, new_translator(language)), + )), + false => Box::new(imiop::subprociop::SubProcIop::new( + config.clone(), + IOProcessor::new(language, new_translator(language)), + )), } } /// ### switch_imiop - /// + /// /// Change current imiop based on states fn switch_imiop(&mut self) { // Change if last state changed @@ -121,9 +127,18 @@ impl RuntimeProps { // If in standard state... // Check last_state self.imiop = match self.get_last_state() { - ShellState::Idle => Box::new(imiop::shiop::ShIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))), - ShellState::SubprocessRunning => Box::new(imiop::subprociop::SubProcIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))), - _ => Box::new(imiop::shiop::ShIop::new(self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)))) + ShellState::Idle => Box::new(imiop::shiop::ShIop::new( + self.config.clone(), + IOProcessor::new(self.language, new_translator(self.language)), + )), + ShellState::SubprocessRunning => Box::new(imiop::subprociop::SubProcIop::new( + self.config.clone(), + IOProcessor::new(self.language, new_translator(self.language)), + )), + _ => Box::new(imiop::shiop::ShIop::new( + self.config.clone(), + IOProcessor::new(self.language, new_translator(self.language)), + )), }; // Reset state changed self.report_state_changed_notified(); @@ -138,8 +153,8 @@ mod tests { use crate::config::Config; use crate::translator::lang::Language; - // use std::thread::sleep; - // use std::time::Duration; + use std::thread::sleep; + use std::time::Duration; #[test] fn test_runtimeprops_new() { @@ -162,22 +177,68 @@ mod tests { assert_eq!(props.get_state_changed(), true); } + #[test] + fn test_runtimeprops_switch_imiop() { + let mut props: RuntimeProps = new_runtime_props(true); + // State hasn't changed + props.state_changed = false; + props.last_state = ShellState::Idle; + props.switch_imiop(); + // Change state + props.state_changed = true; + props.last_state = ShellState::SubprocessRunning; + props.switch_imiop(); + // Change back to Idle + props.state_changed = true; + props.last_state = ShellState::Idle; + props.switch_imiop(); + // Change to unhandled state + props.state_changed = true; + props.last_state = ShellState::Unknown; + props.switch_imiop(); + } + #[test] fn test_runtimeprops_handle_input_event() { - // TODO: REDO + let mut props: RuntimeProps = new_runtime_props(true); + let config: Config = Config::default(); + let mut shell: Shell = Shell::start( + String::from("sh"), + Vec::new(), + &config.prompt_config, + ) + .unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + props.handle_input_event(InputEvent::Enter, &mut shell); + //Signal + props.handle_input_event(InputEvent::Ctrl(3), &mut shell); + //Stop shell + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + let _ = shell.stop(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP } #[test] fn test_runtimeprops_handle_input_event_not_interactive() { - // TODO: redo let mut props: RuntimeProps = new_runtime_props(false); + let config: Config = Config::default(); + let mut shell: Shell = Shell::start( + String::from("sh"), + Vec::new(), + &config.prompt_config, + ) + .unwrap(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + props.handle_input_event(InputEvent::Enter, &mut shell); + //Signal + props.handle_input_event(InputEvent::Ctrl(3), &mut shell); + //Stop shell + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP + let _ = shell.stop(); + sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP } fn new_runtime_props(interactive: bool) -> RuntimeProps { - RuntimeProps::new( - interactive, - Config::default(), - Language::Russian, - ) + RuntimeProps::new(interactive, Config::default(), Language::Russian) } } From 12c831d2cd52762814646b0ccd5b1245d0299316 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 10:18:11 +0100 Subject: [PATCH 25/32] ShellState separated from ShellProcState --- src/runtime/mod.rs | 5 ++-- src/runtime/props.rs | 16 +++++----- src/shell/mod.rs | 61 +++++++++++++++++++++++++++++---------- src/shell/proc/mod.rs | 8 ++--- src/shell/proc/process.rs | 60 +++++++++++++++++++------------------- 5 files changed, 89 insertions(+), 61 deletions(-) diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 9408931..70a223e 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -41,8 +41,7 @@ use crate::config; //Props use props::RuntimeProps; //Shell -use crate::shell::proc::ShellState; -use crate::shell::{Shell}; +use crate::shell::{Shell, ShellState}; use crate::shell::unixsignal::UnixSignal; // Translator use crate::translator::ioprocessor::IOProcessor; @@ -94,7 +93,7 @@ pub fn run_interactive(language: Language, config: config::Config, shell: Option if current_state != props.get_last_state() { props.update_state(current_state); } - if props.get_state_changed() && current_state == ShellState::Idle { + if props.get_state_changed() && current_state == ShellState::Shell { //Force shellenv to refresh info shell.refresh_env(); //Print prompt diff --git a/src/runtime/props.rs b/src/runtime/props.rs index 4b8c2b1..982870f 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -26,8 +26,7 @@ use super::imiop::{self, Imiop}; use crate::config::Config; -use crate::shell::proc::ShellState; -use crate::shell::Shell; +use crate::shell::{Shell, ShellState}; use crate::translator::ioprocessor::IOProcessor; use crate::translator::lang::Language; use crate::translator::new_translator; @@ -123,11 +122,10 @@ impl RuntimeProps { fn switch_imiop(&mut self) { // Change if last state changed if self.get_state_changed() { - // TODO: check environmental shell state - // If in standard state... + // TODO: text editor // Check last_state self.imiop = match self.get_last_state() { - ShellState::Idle => Box::new(imiop::shiop::ShIop::new( + ShellState::Shell => Box::new(imiop::shiop::ShIop::new( self.config.clone(), IOProcessor::new(self.language, new_translator(self.language)), )), @@ -172,8 +170,8 @@ mod tests { assert_eq!(props.get_state_changed(), true); props.report_state_changed_notified(); assert_eq!(props.get_state_changed(), false); - props.update_state(ShellState::Idle); - assert_eq!(props.get_last_state(), ShellState::Idle); + props.update_state(ShellState::Shell); + assert_eq!(props.get_last_state(), ShellState::Shell); assert_eq!(props.get_state_changed(), true); } @@ -182,7 +180,7 @@ mod tests { let mut props: RuntimeProps = new_runtime_props(true); // State hasn't changed props.state_changed = false; - props.last_state = ShellState::Idle; + props.last_state = ShellState::Shell; props.switch_imiop(); // Change state props.state_changed = true; @@ -190,7 +188,7 @@ mod tests { props.switch_imiop(); // Change back to Idle props.state_changed = true; - props.last_state = ShellState::Idle; + props.last_state = ShellState::Shell; props.switch_imiop(); // Change to unhandled state props.state_changed = true; diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 36da38b..39c50ef 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -32,7 +32,7 @@ extern crate nix; extern crate whoami; use history::ShellHistory; -use proc::{ShellError, ShellProc, ShellState}; +use proc::{ShellError, ShellProc, ShellProcState}; use prompt::ShellPrompt; use crate::config::PromptConfig; @@ -41,6 +41,19 @@ use crate::translator::ioprocessor::IOProcessor; use std::path::PathBuf; use std::time::{Duration}; +/// ### ShellState +/// +/// ShellState represents the shell environment state, which basically is a super state of +/// the shell state. This is used to switch between built-in modes (std shell, text editor, ...) +#[derive(Copy, Clone, PartialEq, std::fmt::Debug)] +pub enum ShellState { + Shell, + SubprocessRunning, + TextEditor, + Terminated, + Unknown +} + /// ### Shell /// /// Shell represents the current user shell configuration @@ -48,7 +61,8 @@ pub struct Shell { pub history: ShellHistory, process: ShellProc, prompt: ShellPrompt, - props: ShellProps + props: ShellProps, + state: ShellState } /// ### ShellProps @@ -87,7 +101,8 @@ impl Shell { process: shell_process, prompt: shell_prompt, props: ShellProps::new(hostname, user, wrkdir), - history: ShellHistory::new() + history: ShellHistory::new(), + state: ShellState::Shell }) } @@ -106,6 +121,7 @@ impl Shell { /// /// Mirrors ShellProc read pub fn read(&mut self) -> Result<(Option, Option), ShellError> { + // TODO: env state self.process.read() } @@ -113,6 +129,7 @@ impl Shell { /// /// Mirrors ShellProc write pub fn write(&mut self, input: String) -> Result<(), ShellError> { + // TODO: env state self.process.write(input) } @@ -121,6 +138,7 @@ impl Shell { /// Send a signal to shell process #[allow(dead_code)] pub fn raise(&mut self, sig: unixsignal::UnixSignal) -> Result<(), ShellError> { + // TODO: env state self.process.raise(sig.to_nix_signal()) } @@ -128,7 +146,18 @@ impl Shell { /// /// Returns the current Shell state pub fn get_state(&mut self) -> ShellState { - self.process.update_state() + let proc_state: ShellProcState = self.process.update_state(); + match self.state { + ShellState::TextEditor => ShellState::TextEditor, + _ => { + self.state = match proc_state { + ShellProcState::Idle => ShellState::Shell, + ShellProcState::SubprocessRunning => ShellState::SubprocessRunning, + _ => ShellState::Terminated + }; + self.state + } + } } /// ### refresh_env @@ -207,9 +236,11 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellState::Idle); + assert_eq!(shell_env.get_state(), ShellProcState::Idle); //Verify history capacity assert_eq!(shell_env.history.len(), 0); + // Verify env state + assert_eq!(shell_env.state, ShellState::Shell); //Get username etc println!("Username: {}", shell_env.props.username); println!("Hostname: {}", shell_env.props.hostname); @@ -222,7 +253,7 @@ mod tests { //Terminate shell assert_eq!(shell_env.stop().unwrap(), 9); sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - assert_eq!(shell_env.get_state(), ShellState::Terminated); + assert_eq!(shell_env.get_state(), ShellProcState::Terminated); } #[test] @@ -233,7 +264,7 @@ mod tests { let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).unwrap(); sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP //Shell should have terminated - assert_eq!(shell_env.get_state(), ShellState::Terminated); + assert_eq!(shell_env.get_state(), ShellProcState::Terminated); } #[test] @@ -246,13 +277,13 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellState::Idle); + assert_eq!(shell_env.get_state(), ShellProcState::Idle); //Try to start a blocking process (e.g. cat) let command: String = String::from("head -n 2\n"); assert!(shell_env.write(command).is_ok()); sleep(Duration::from_millis(500)); //Check if status is SubprocessRunning - assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning); + assert_eq!(shell_env.get_state(), ShellProcState::SubprocessRunning); let stdin: String = String::from("foobar\n"); assert!(shell_env.write(stdin.clone()).is_ok()); //Wait 100ms @@ -274,21 +305,21 @@ mod tests { } } //Verify shell status again - assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning); + assert_eq!(shell_env.get_state(), ShellProcState::SubprocessRunning); if ! test_must_pass { //NOTE: this is an issue related to tests. THIS PROBLEM DOESN'T HAPPEN IN PRODUCTION ENVIRONMENT let stdin: String = String::from("foobar\n"); assert!(shell_env.write(stdin.clone()).is_ok()); sleep(Duration::from_millis(50)); assert!(shell_env.read().is_ok()); sleep(Duration::from_millis(50)); - assert_eq!(shell_env.get_state(), ShellState::Idle); + assert_eq!(shell_env.get_state(), ShellProcState::Idle); } //Now should be IDLE //Okay, send SIGINT now assert!(shell_env.process.kill().is_ok()); //Shell should have terminated sleep(Duration::from_millis(500)); - assert_eq!(shell_env.get_state(), ShellState::Terminated); + assert_eq!(shell_env.get_state(), ShellProcState::Terminated); assert_eq!(shell_env.stop().unwrap(), 9); } @@ -302,7 +333,7 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellState::Idle); + assert_eq!(shell_env.get_state(), ShellProcState::Idle); //Terminate the shell gracefully sleep(Duration::from_millis(500)); let command: String = String::from("exit 5\n"); @@ -310,7 +341,7 @@ mod tests { //Wait shell to terminate sleep(Duration::from_millis(1000)); //Verify shell has terminated - assert_eq!(shell_env.get_state(), ShellState::Terminated); + assert_eq!(shell_env.get_state(), ShellProcState::Terminated); //Verify exitcode to be 0 assert_eq!(shell_env.stop().unwrap(), 5); } @@ -326,7 +357,7 @@ mod tests { //Wait shell to terminate sleep(Duration::from_millis(500)); //Verify shell has terminated - assert_eq!(shell_env.get_state(), ShellState::Terminated); + assert_eq!(shell_env.get_state(), ShellProcState::Terminated); //Verify exitcode to be 0 assert_eq!(shell_env.stop().unwrap(), 2); } diff --git a/src/shell/proc/mod.rs b/src/shell/proc/mod.rs index b9d62d0..c39264e 100644 --- a/src/shell/proc/mod.rs +++ b/src/shell/proc/mod.rs @@ -35,11 +35,11 @@ use pipe::Pipe; //Proc has a thread which runs the subprocess of the shell and 3 pipes (stdout, stdin, stderr). It must provides the function to write and to read -/// ### ShellState +/// ### ShellProcState /// -/// ShellState represents the current shell state +/// ShellProcState represents the current shell state #[derive(Copy, Clone, PartialEq, std::fmt::Debug)] -pub enum ShellState { +pub enum ShellProcState { Idle, SubprocessRunning, Terminated, @@ -65,7 +65,7 @@ pub enum ShellError { /// Shell Proc represents an instance of the shell process wrapper #[derive(std::fmt::Debug)] pub struct ShellProc { - pub state: ShellState, //Shell process state + pub state: ShellProcState, //Shell process state pub exit_status: u8, //Exit status of the subprocess (child of shell) pub pid: i32, //Shell pid pub wrkdir: PathBuf, //Working directory diff --git a/src/shell/proc/process.rs b/src/shell/proc/process.rs index 26b32d8..c9843a4 100644 --- a/src/shell/proc/process.rs +++ b/src/shell/proc/process.rs @@ -27,7 +27,7 @@ extern crate nix; extern crate tempfile; extern crate uuid; -use super::{ShellError, ShellProc, ShellState}; +use super::{ShellError, ShellProc, ShellProcState}; use super::pipe::Pipe; use std::ffi::{CStr, CString}; @@ -73,7 +73,7 @@ impl ShellProc { }; //Return Shell Proc Ok(ShellProc { - state: ShellState::Idle, + state: ShellProcState::Idle, uuid: uuid, exit_status: 0, exec_time: Duration::from_millis(0), @@ -101,7 +101,7 @@ impl ShellProc { /// /// cleanup shell once exited. Returns the shell exit code pub fn cleanup(&mut self) -> Result { - if self.update_state() != ShellState::Terminated { + if self.update_state() != ShellProcState::Terminated { return Err(ShellError::ShellRunning) } //Close pipes @@ -133,7 +133,7 @@ impl ShellProc { /// Read from child pipes pub fn read(&mut self) -> Result<(Option, Option), ShellError> { /* NOTE: doesn't make sense; read must be possible even if shell has terminated - if self.update_state() == ShellState::Terminated { + if self.update_state() == ShellProcState::Terminated { return Err(ShellError::ShellTerminated) }*/ let stdout: Option = match self.stdout_pipe.read(50, false) { @@ -154,11 +154,11 @@ impl ShellProc { /// /// Write to child process stdin pub fn write(&mut self, mut data: String) -> Result<(), ShellError> { - if self.update_state() == ShellState::Terminated { + if self.update_state() == ShellProcState::Terminated { return Err(ShellError::ShellTerminated) } //Add echo command to data if shell state is Idle - if self.state == ShellState::Idle { + if self.state == ShellProcState::Idle { //Replace data newline with ';' while data.ends_with('\n') { data.pop(); @@ -208,17 +208,17 @@ impl ShellProc { /// ### update_state /// /// Update shell running state checking if the other thread has terminated - pub fn update_state(&mut self) -> ShellState { + pub fn update_state(&mut self) -> ShellProcState { //Wait pid (NO HANG) match nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(self.pid), Some(nix::sys::wait::WaitPidFlag::WNOHANG)) { Err(_) => {}, //Could not get information Ok(status) => match status { nix::sys::wait::WaitStatus::Exited(_, rc) => { - self.state = ShellState::Terminated; + self.state = ShellProcState::Terminated; self.rc = rc as u8; }, nix::sys::wait::WaitStatus::Signaled(_, signal, _) => { - self.state = ShellState::Terminated; + self.state = ShellProcState::Terminated; self.rc = signal as u8; }, _ => {}, //Still running @@ -296,7 +296,7 @@ impl ShellProc { } } self.exec_time = self.start_time.elapsed(); - self.state = ShellState::Idle; + self.state = ShellProcState::Idle; } /// ### set_state_running @@ -304,7 +304,7 @@ impl ShellProc { /// Set state to running fn set_state_running(&mut self) { self.start_time = Instant::now(); - self.state = ShellState::SubprocessRunning; + self.state = ShellProcState::SubprocessRunning; } } @@ -333,7 +333,7 @@ mod tests { let mut shell_proc: ShellProc = ShellProc::start(vec![String::from("sh")]).unwrap(); println!("A new shell started with PID {}", shell_proc.pid); //Check shell parameters - assert_eq!(shell_proc.state, ShellState::Idle); + assert_eq!(shell_proc.state, ShellProcState::Idle); assert_eq!(shell_proc.exit_status, 0); assert_ne!(shell_proc.pid, 0); assert_ne!(shell_proc.wrkdir.len(), 0); @@ -344,13 +344,13 @@ mod tests { assert_eq!(shell_proc.echo_command, format!("echo \"\x02$?;`pwd`;{}\x03\"\n", shell_proc.uuid)); //Verify shell is still running sleep(Duration::from_millis(500)); - assert_eq!(shell_proc.update_state(), ShellState::Idle); + assert_eq!(shell_proc.update_state(), ShellProcState::Idle); //Stop process assert!(shell_proc.kill().is_ok()); sleep(Duration::from_millis(500)); - assert_eq!(shell_proc.update_state(), ShellState::Terminated); + assert_eq!(shell_proc.update_state(), ShellProcState::Terminated); //Rc should be set to 9 - assert_eq!(shell_proc.state, ShellState::Terminated); + assert_eq!(shell_proc.state, ShellProcState::Terminated); assert_eq!(shell_proc.rc, 9); //Cleanup assert!(shell_proc.cleanup().is_ok()); @@ -362,7 +362,7 @@ mod tests { println!("A new shell started with PID {}", shell_proc.pid); //Shell should have died sleep(Duration::from_millis(1000)); - assert_eq!(shell_proc.update_state(), ShellState::Terminated); + assert_eq!(shell_proc.update_state(), ShellProcState::Terminated); assert_eq!(shell_proc.rc, 255); } @@ -372,11 +372,11 @@ mod tests { println!("A new shell started with PID {}", shell_proc.pid); //Verify shell is still running sleep(Duration::from_millis(500)); - assert_eq!(shell_proc.update_state(), ShellState::Idle); + assert_eq!(shell_proc.update_state(), ShellProcState::Idle); //Send SIGINT assert!(shell_proc.raise(nix::sys::signal::Signal::SIGINT).is_ok()); sleep(Duration::from_millis(500)); - assert_eq!(shell_proc.update_state(), ShellState::Terminated); + assert_eq!(shell_proc.update_state(), ShellProcState::Terminated); assert_eq!(shell_proc.rc, 2); } @@ -387,12 +387,12 @@ mod tests { sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP //Parse metadata let metadata: String = String::from("128;/home;ee9ec814-a751-4329-850f-6d54d12c8a5c"); - shell_proc.state = ShellState::SubprocessRunning; + shell_proc.state = ShellProcState::SubprocessRunning; shell_proc.set_state_idle(metadata); //Verify metadata have been parsed successfully assert_eq!(shell_proc.exit_status, 128); assert_eq!(shell_proc.wrkdir, PathBuf::from("/home")); - assert_eq!(shell_proc.state, ShellState::Idle); + assert_eq!(shell_proc.state, ShellProcState::Idle); //Kill assert!(shell_proc.kill().is_ok()); } @@ -405,22 +405,22 @@ mod tests { //Parse stdout when empty assert!(shell_proc.parse_stdout(None).is_none()); //Parse stdout with metadata only (and parse theme) - shell_proc.state = ShellState::SubprocessRunning; + shell_proc.state = ShellProcState::SubprocessRunning; assert!(shell_proc.parse_stdout(Some(format!("\x02128;/home;{}\x03\n", shell_proc.uuid))).is_none()); assert_eq!(shell_proc.exit_status, 128); assert_eq!(shell_proc.wrkdir, PathBuf::from("/home")); - assert_eq!(shell_proc.state, ShellState::Idle); + assert_eq!(shell_proc.state, ShellProcState::Idle); //Parse stdout with output only - shell_proc.state = ShellState::SubprocessRunning; + shell_proc.state = ShellProcState::SubprocessRunning; assert_eq!(shell_proc.parse_stdout(Some(String::from("HELLO\n"))).unwrap(), String::from("HELLO\n")); - assert_eq!(shell_proc.state, ShellState::SubprocessRunning); //State unchanged + assert_eq!(shell_proc.state, ShellProcState::SubprocessRunning); //State unchanged assert_eq!(*shell_proc.stdout_cache.as_ref().unwrap(), String::from("HELLO\n")); //Parse stdout with everything - shell_proc.state = ShellState::SubprocessRunning; + shell_proc.state = ShellProcState::SubprocessRunning; assert_eq!(shell_proc.parse_stdout(Some(format!("HELLO\n\x022;/tmp;{}\x03\n", shell_proc.uuid))).unwrap(), String::from("HELLO\n")); assert_eq!(shell_proc.exit_status, 2); assert_eq!(shell_proc.wrkdir, PathBuf::from("/tmp")); - assert_eq!(shell_proc.state, ShellState::Idle); + assert_eq!(shell_proc.state, ShellProcState::Idle); assert!(shell_proc.stdout_cache.is_none()); //Kill assert!(shell_proc.kill().is_ok()); @@ -433,7 +433,7 @@ mod tests { //Send a cd command assert!(shell_proc.write(String::from("cd /tmp\n")).is_ok()); //State should have changed to subprocess - assert_eq!(shell_proc.state, ShellState::SubprocessRunning); + assert_eq!(shell_proc.state, ShellProcState::SubprocessRunning); //Then read response sleep(Duration::from_millis(50)); let (stdout, stderr) = shell_proc.read().unwrap(); @@ -442,7 +442,7 @@ mod tests { assert!(stderr.is_none()); //Verify shell is still running sleep(Duration::from_millis(100)); - assert_eq!(shell_proc.update_state(), ShellState::Idle); + assert_eq!(shell_proc.update_state(), ShellProcState::Idle); //Verify wrkdir is now /tmp/ assert_eq!(shell_proc.wrkdir, PathBuf::from("/tmp")); //Verify exit status @@ -452,9 +452,9 @@ mod tests { //Stop process assert!(shell_proc.kill().is_ok()); sleep(Duration::from_millis(500)); - assert_eq!(shell_proc.update_state(), ShellState::Terminated); + assert_eq!(shell_proc.update_state(), ShellProcState::Terminated); //Rc should be set to 9 - assert_eq!(shell_proc.state, ShellState::Terminated); + assert_eq!(shell_proc.state, ShellProcState::Terminated); assert_eq!(shell_proc.rc, 9); //Cleanup assert!(shell_proc.cleanup().is_ok()); From b133e4a6ab1ddf9c2e31dbd0d1f9a4d1749bcc88 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 10:19:00 +0100 Subject: [PATCH 26/32] Removed unused 'Unknown' state for ShellProcState --- src/shell/proc/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shell/proc/mod.rs b/src/shell/proc/mod.rs index c39264e..c21e4ae 100644 --- a/src/shell/proc/mod.rs +++ b/src/shell/proc/mod.rs @@ -42,8 +42,7 @@ use pipe::Pipe; pub enum ShellProcState { Idle, SubprocessRunning, - Terminated, - Unknown + Terminated } /// ### ShellError From 6a0a636a890062c031b4339ea6112e317b31530d Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 10:31:47 +0100 Subject: [PATCH 27/32] I should really buy a new laptop... --- src/runtime/props.rs | 2 -- src/shell/mod.rs | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/runtime/props.rs b/src/runtime/props.rs index 982870f..ccbccad 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -32,8 +32,6 @@ use crate::translator::lang::Language; use crate::translator::new_translator; use crate::utils::console::InputEvent; -// TODO: convert interactive boolean to Enum ShellEnvMode - /// ## RuntimeProps /// /// Runtime Props is a wrapper for all the properties used by the Runtime module diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 39c50ef..1b2ced6 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -236,7 +236,7 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellProcState::Idle); + assert_eq!(shell_env.get_state(), ShellState::Shell); //Verify history capacity assert_eq!(shell_env.history.len(), 0); // Verify env state @@ -253,7 +253,7 @@ mod tests { //Terminate shell assert_eq!(shell_env.stop().unwrap(), 9); sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP - assert_eq!(shell_env.get_state(), ShellProcState::Terminated); + assert_eq!(shell_env.get_state(), ShellState::Terminated); } #[test] @@ -264,7 +264,7 @@ mod tests { let mut shell_env: Shell = Shell::start(shell, vec![], &PromptConfig::default()).unwrap(); sleep(Duration::from_millis(500)); //DON'T REMOVE THIS SLEEP //Shell should have terminated - assert_eq!(shell_env.get_state(), ShellProcState::Terminated); + assert_eq!(shell_env.get_state(), ShellState::Terminated); } #[test] @@ -277,13 +277,13 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellProcState::Idle); + assert_eq!(shell_env.get_state(), ShellState::Shell); //Try to start a blocking process (e.g. cat) let command: String = String::from("head -n 2\n"); assert!(shell_env.write(command).is_ok()); sleep(Duration::from_millis(500)); //Check if status is SubprocessRunning - assert_eq!(shell_env.get_state(), ShellProcState::SubprocessRunning); + assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning); let stdin: String = String::from("foobar\n"); assert!(shell_env.write(stdin.clone()).is_ok()); //Wait 100ms @@ -305,21 +305,21 @@ mod tests { } } //Verify shell status again - assert_eq!(shell_env.get_state(), ShellProcState::SubprocessRunning); + assert_eq!(shell_env.get_state(), ShellState::SubprocessRunning); if ! test_must_pass { //NOTE: this is an issue related to tests. THIS PROBLEM DOESN'T HAPPEN IN PRODUCTION ENVIRONMENT let stdin: String = String::from("foobar\n"); assert!(shell_env.write(stdin.clone()).is_ok()); sleep(Duration::from_millis(50)); assert!(shell_env.read().is_ok()); sleep(Duration::from_millis(50)); - assert_eq!(shell_env.get_state(), ShellProcState::Idle); + assert_eq!(shell_env.get_state(), ShellState::Shell); } //Now should be IDLE //Okay, send SIGINT now assert!(shell_env.process.kill().is_ok()); //Shell should have terminated sleep(Duration::from_millis(500)); - assert_eq!(shell_env.get_state(), ShellProcState::Terminated); + assert_eq!(shell_env.get_state(), ShellState::Terminated); assert_eq!(shell_env.stop().unwrap(), 9); } @@ -333,7 +333,7 @@ mod tests { //Verify PID assert_ne!(shell_env.process.pid, 0); //Verify shell status - assert_eq!(shell_env.get_state(), ShellProcState::Idle); + assert_eq!(shell_env.get_state(), ShellState::Shell); //Terminate the shell gracefully sleep(Duration::from_millis(500)); let command: String = String::from("exit 5\n"); @@ -341,7 +341,7 @@ mod tests { //Wait shell to terminate sleep(Duration::from_millis(1000)); //Verify shell has terminated - assert_eq!(shell_env.get_state(), ShellProcState::Terminated); + assert_eq!(shell_env.get_state(), ShellState::Terminated); //Verify exitcode to be 0 assert_eq!(shell_env.stop().unwrap(), 5); } @@ -357,7 +357,7 @@ mod tests { //Wait shell to terminate sleep(Duration::from_millis(500)); //Verify shell has terminated - assert_eq!(shell_env.get_state(), ShellProcState::Terminated); + assert_eq!(shell_env.get_state(), ShellState::Terminated); //Verify exitcode to be 0 assert_eq!(shell_env.stop().unwrap(), 2); } From 6ab25e3506729c1622404c923b5f0def1c424e3d Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sun, 1 Nov 2020 15:16:25 +0100 Subject: [PATCH 28/32] Changed 'e' in russian translator --- CHANGELOG.md | 8 ++++- docs/translators/ru.md | 4 +-- pyc.yml | 1 + src/translator/lang/russian.rs | 55 ++++++++++++++-------------------- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1877cd6..35953ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,13 @@ Released on ?? - New translators: - 🇷🇸 Serbian - 🇺🇦 Ukrainian -- Prompt configuration: +- Translators changes: + - 🇷🇺 Russian: + - Latin to cyrillic: + - E => E + - Cyrillic to latin: + - E => E +- **Prompt** configuration: - new ```commit_prepend``` and ```commit_append``` keys - New colors keys: - KBOLD diff --git a/docs/translators/ru.md b/docs/translators/ru.md index 1e19e48..658a191 100644 --- a/docs/translators/ru.md +++ b/docs/translators/ru.md @@ -44,7 +44,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | КС | X | | | Ы | Y | | | Я | YA | | -| Е | YE | | +| Е | E | | | Ю | YU | | | З | Z | | | ₽ | $ | | @@ -62,7 +62,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | CH | Ч | | | Ч | CH | | | D | Д | | -| E | Э | | +| E | E | | | F | Ф | | | G | Г | | | G | ДЖ | If g is followed by Y, E, I | diff --git a/pyc.yml b/pyc.yml index 422ee76..c77623a 100644 --- a/pyc.yml +++ b/pyc.yml @@ -2,6 +2,7 @@ language: ru shell: exec: "bash" alias: + - рус: pyc - чд: cd - пвд: pwd - уич: which diff --git a/src/translator/lang/russian.rs b/src/translator/lang/russian.rs index cddce96..9c4f199 100644 --- a/src/translator/lang/russian.rs +++ b/src/translator/lang/russian.rs @@ -83,10 +83,8 @@ impl Translator for Russian { 'г' => "g", 'Д' => "D", 'д' => "d", - 'Е' => "YE", - 'е' => "ye", - 'Э' => "E", - 'э' => "e", + 'Е' | 'Э' => "E", + 'е' | 'э' => "e", 'Ё' => "YO", 'ё' => "yo", 'Ж' => "J", @@ -305,8 +303,8 @@ impl Translator for Russian { }, 'D' => "Д", 'd' => "д", - 'E' => "Э", - 'e' => "э", + 'E' => "Е", + 'e' => "е", 'F' => "Ф", 'f' => "ф", 'G' => match input.chars().nth(i + 1) { @@ -477,32 +475,32 @@ mod tests { println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "ls -l"); //Echo hello - let input: String = String::from("экхо хэлло"); + let input: String = String::from("екхо хелло"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "echo hello"); //K vs C - let input: String = String::from("ифконфиг этх0 аддрэсс 192.168.1.30 нэтмаскъ 255.255.255.0"); //Use твёрдый знак to force k in netmask + let input: String = String::from("ифконфиг етх0 аддресс 192.168.1.30 нетмаскъ 255.255.255.0"); //Use твёрдый знак to force k in netmask let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, "ifconfig eth0 address 192.168.1.30 netmask 255.255.255.0" ); - let input: String = String::from("кат РЭАДМЭ.мд"); + let input: String = String::from("кат РЕАДМЭ.мд"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "cat README.md"); //Test all letters (Lowercase) - let input: String = String::from("абкьдэфгхижйкълмнопкюрстуввьксызшщёюячц"); + let input: String = String::from("абкьдэефгхижйкълмнопкюрстуввьксызшщёюячц"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "abcdefghijjklmnopqrstuvwxyzshshhyoyuyachz"); + assert_eq!(output, "abcdeefghijjklmnopqrstuvwxyzshshhyoyuyachz"); //Test all letters (Uppercase) - let input: String = String::from("АБКЬДЭФГХИЖЙКЪЛМНОПКЮРСТУВВЬКСЫЗШЩЁЮЯЧЦ"); + let input: String = String::from("АБКЬДЭЕФГХИЖЙКЪЛМНОПКЮРСТУВВЬКСЫЗШЩЁЮЯЧЦ"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ABCDEFGHIJJKLMNOPQRSTUVWXYZSHSHHYOYUYACHZ"); + assert_eq!(output, "ABCDEEFGHIJJKLMNOPQRSTUVWXYZSHSHHYOYUYACHZ"); //Special cases 'Q' let input: String = String::from("москюуитто_пуб"); let output = translator.to_latin(&input); @@ -521,15 +519,6 @@ mod tests { let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "SRV"); - //Special case: Ye - let input: String = String::from("елл"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "yell"); - let input: String = String::from("ЕЛЛ"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "YELL"); //Special case: ck let input: String = String::from("чэкк чэкк"); let output = translator.to_latin(&input); @@ -613,24 +602,24 @@ mod tests { println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "а б к д э ф г х и ж к л м н о п кю р с т у в у кс ы з" + "а б к д е ф г х и ж к л м н о п кю р с т у в у кс ы з" ); let input: String = String::from("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "А Б К Д Э Ф Г Х И Ж К Л М Н О П КЮ Р С Т У В У КС Ы З" + "А Б К Д Е Ф Г Х И Ж К Л М Н О П КЮ Р С Т У В У КС Ы З" ); //Test particular case (sh) let input: String = String::from("shell"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "шэлл"); + assert_eq!(output, "шелл"); let input: String = String::from("SHELL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ШЭЛЛ"); + assert_eq!(output, "ШЕЛЛ"); //Test particular case (jo) let input: String = String::from("Option"); let output = translator.to_cyrillic(&input); @@ -653,11 +642,11 @@ mod tests { let input: String = String::from("gin and games"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "джин анд гамэс"); + assert_eq!(output, "джин анд гамес"); let input: String = String::from("GIN AND GAMES"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ДЖИН АНД ГАМЭС"); + assert_eq!(output, "ДЖИН АНД ГАМЕС"); //Test particular case (iu) let input: String = String::from("iuta"); let output = translator.to_cyrillic(&input); @@ -689,16 +678,16 @@ mod tests { let input: String = String::from("channel"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "чаннэл"); + assert_eq!(output, "чаннел"); let input: String = String::from("CHANNEL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЧАННЭЛ"); + assert_eq!(output, "ЧАННЕЛ"); //Test some words let input: String = String::from("Usage: cat [OPTION]... [FILE]..."); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "Усаджэ: кат [ОПТЁН]... [ФИЛЭ]..."); + assert_eq!(output, "Усадже: кат [ОПТЁН]... [ФИЛЕ]..."); //Special cases: last character is 'c' let input: String = String::from("chic"); let output = translator.to_cyrillic(&input); @@ -730,11 +719,11 @@ mod tests { let input: String = String::from("less"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "лэсс"); + assert_eq!(output, "лесс"); let input: String = String::from("LESS"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЛЭСС"); + assert_eq!(output, "ЛЕСС"); //Special cases: last character is 't' let input: String = String::from("cat"); let output = translator.to_cyrillic(&input); From d9b0c1694b07d013a29c67a1ef0d4515b13a8324 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 14 Nov 2020 09:20:34 +0100 Subject: [PATCH 29/32] Removed 0.3.1 stuff --- src/shell/mod.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 1b2ced6..0db748d 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -49,7 +49,6 @@ use std::time::{Duration}; pub enum ShellState { Shell, SubprocessRunning, - TextEditor, Terminated, Unknown } @@ -121,7 +120,6 @@ impl Shell { /// /// Mirrors ShellProc read pub fn read(&mut self) -> Result<(Option, Option), ShellError> { - // TODO: env state self.process.read() } @@ -129,7 +127,6 @@ impl Shell { /// /// Mirrors ShellProc write pub fn write(&mut self, input: String) -> Result<(), ShellError> { - // TODO: env state self.process.write(input) } @@ -138,7 +135,6 @@ impl Shell { /// Send a signal to shell process #[allow(dead_code)] pub fn raise(&mut self, sig: unixsignal::UnixSignal) -> Result<(), ShellError> { - // TODO: env state self.process.raise(sig.to_nix_signal()) } @@ -148,7 +144,6 @@ impl Shell { pub fn get_state(&mut self) -> ShellState { let proc_state: ShellProcState = self.process.update_state(); match self.state { - ShellState::TextEditor => ShellState::TextEditor, _ => { self.state = match proc_state { ShellProcState::Idle => ShellState::Shell, From 4004965b3062a8714b4b17a7a2abbcef777eb87e Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 14 Nov 2020 09:30:02 +0100 Subject: [PATCH 30/32] Fixed tests --- src/runtime/mod.rs | 2 +- src/translator/ioprocessor.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 70a223e..eb35cbc 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -441,7 +441,7 @@ mod tests { fn test_runtime_console_fmt() { let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian)); //Out - assert_eq!(console_fmt(String::from("Hello"), true, &iop), String::from("Хэлло")); + assert_eq!(console_fmt(String::from("Hello"), true, &iop), String::from("Хелло")); assert_eq!(console_fmt(String::from("Hello"), false, &iop), String::from("Hello")); } diff --git a/src/translator/ioprocessor.rs b/src/translator/ioprocessor.rs index c432dd7..f888787 100644 --- a/src/translator/ioprocessor.rs +++ b/src/translator/ioprocessor.rs @@ -307,7 +307,7 @@ mod tests { let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian)); assert_eq!(iop.language, Language::Russian); let input: String = String::from("Привет Мир!"); - assert_eq!(iop.text_to_latin(&input), String::from("Privyet Mir!")); + assert_eq!(iop.text_to_latin(&input), String::from("Privet Mir!")); } #[test] @@ -336,7 +336,7 @@ mod tests { let input: String = String::from("экхо \\\"привет\\\""); assert_eq!( iop.expression_to_latin(&input).unwrap(), - String::from("echo \\\"privyet\\\"") + String::from("echo \\\"privet\\\"") ); //With expressions let input: String = String::from("экхо ₽(хостнамэ)"); @@ -403,7 +403,7 @@ mod tests { let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian)); assert_eq!(iop.language, Language::Russian); let input: String = String::from("Hello World!"); - assert_eq!(iop.text_to_cyrillic(&input), String::from("Хэлло Уорлд!")); + assert_eq!(iop.text_to_cyrillic(&input), String::from("Хелло Уорлд!")); } #[test] @@ -415,43 +415,43 @@ mod tests { let input: String = String::from("echo foobar"); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо фообар") + String::from("ечо фообар") ); //With escape let input: String = String::from("echo \"hello world\""); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо \"hello world\"") + String::from("ечо \"hello world\"") ); //With escape + backslash - let input: String = String::from("echo \\\"privyet\\\""); + let input: String = String::from("echo \\\"Privet\\\""); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо \\\"привет\\\"") + String::from("ечо \\\"Привет\\\"") ); //With expressions let input: String = String::from("echo $(hostname)"); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо $(хостнамэ)") + String::from("ечо $(хостнаме)") ); //With expressions + escapes let input: String = String::from("echo $(cat \"/tmp/README.txt\")"); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо $(кат \"/tmp/README.txt\")") + String::from("ечо $(кат \"/tmp/README.txt\")") ); //With expressions + escapes + backslash let input: String = String::from("echo $(cat \"/tmp/john\\(that_guy\\).txt\")"); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо $(кат \"/tmp/john\\(that_guy\\).txt\")") + String::from("ечо $(кат \"/tmp/john\\(that_guy\\).txt\")") ); //Nested expressions let input: String = String::from("echo $(hostname) $(echo $(home)/$(whoami))"); assert_eq!( iop.expression_to_cyrillic(&input).unwrap(), - String::from("эчо $(хостнамэ) $(эчо $(хомэ)/$(ухоами))") + String::from("ечо $(хостнаме) $(ечо $(хоме)/$(ухоами))") ); } @@ -494,6 +494,6 @@ mod tests { //Instantiate IOProcessor let iop: IOProcessor = IOProcessor::new(Language::Russian, new_translator(Language::Russian)); assert_eq!(iop.language, Language::Russian); - assert_eq!(iop.text_to_cyrillic(&latin_text), String::from("\x1b[31mРЭД\x1b[0m")); + assert_eq!(iop.text_to_cyrillic(&latin_text), String::from("\x1b[31mРЕД\x1b[0m")); } } From 1863f02efeed4414cd335d64ecb86360a6ae3108 Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 14 Nov 2020 09:46:15 +0100 Subject: [PATCH 31/32] Applied 'e' rule also to belarusian and bulgarian --- docs/translators/bg.md | 4 +- docs/translators/by.md | 4 +- src/translator/lang/belarusian.rs | 61 +++++++++++++------------------ src/translator/lang/bulgarian.rs | 61 +++++++++++++------------------ 4 files changed, 56 insertions(+), 74 deletions(-) diff --git a/docs/translators/bg.md b/docs/translators/bg.md index 65a9fef..8a441ac 100644 --- a/docs/translators/bg.md +++ b/docs/translators/bg.md @@ -44,7 +44,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | КС | X | | | Ы | Y | | | Я | YA | | -| Е | YE | | +| Е | E | | | Ю | YU | | | З | Z | | | € | $ | | @@ -62,7 +62,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | CH | Ч | | | Ч | CH | | | D | Д | | -| E | Э | | +| E | Е | | | F | Ф | | | G | Г | | | G | ДЖ | If g is followed by Y, E, I | diff --git a/docs/translators/by.md b/docs/translators/by.md index ad3b044..8d53676 100644 --- a/docs/translators/by.md +++ b/docs/translators/by.md @@ -44,7 +44,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | КС | X | | | Ы | Y | | | Я | YA | | -| Е | YE | | +| Е | E | | | Ю | YU | | | З | Z | | | ₽ | $ | | @@ -62,7 +62,7 @@ The conversion from cyrillic to latin follows the [GOST 7.79-2000](https://en.wi | CH | Ч | | | Ч | CH | | | D | Д | | -| E | Э | | +| E | Е | | | F | Ф | | | G | Г | | | G | ДЖ | If g is followed by Y, E, I | diff --git a/src/translator/lang/belarusian.rs b/src/translator/lang/belarusian.rs index c661fe2..7bb18e3 100644 --- a/src/translator/lang/belarusian.rs +++ b/src/translator/lang/belarusian.rs @@ -83,8 +83,8 @@ impl Translator for Belarusian { 'г' => "g", 'Д' => "D", 'д' => "d", - 'Е' => "YE", - 'е' => "ye", + 'Е' => "E", + 'е' => "e", 'Э' => "E", 'э' => "e", 'Ё' => "YO", @@ -304,8 +304,8 @@ impl Translator for Belarusian { }, 'D' => "Д", 'd' => "д", - 'E' => "Э", - 'e' => "э", + 'E' => "Е", + 'e' => "е", 'F' => "Ф", 'f' => "ф", 'G' => match input.chars().nth(i + 1) { @@ -476,32 +476,32 @@ impl Translator for Belarusian { println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "ls -l"); //Echo hello - let input: String = String::from("экхо хэлло"); + let input: String = String::from("екхо хелло"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "echo hello"); //K vs C - let input: String = String::from("іфконфіг этх0 аддрэсс 192.168.1.30 нэтмаскʼ 255.255.255.0"); //Use твёрдый знак to force k in netmask + let input: String = String::from("іфконфіг етх0 аддресс 192.168.1.30 нетмаскʼ 255.255.255.0"); //Use твёрдый знак to force k in netmask let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, "ifconfig eth0 address 192.168.1.30 netmask 255.255.255.0" ); - let input: String = String::from("кат РЭАДМЭ.мд"); + let input: String = String::from("кат РЕАДМЕ.мд"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "cat README.md"); //Test all letters (Lowercase) - let input: String = String::from("абкьдэфгхіжйкʼлмнопкюрстуввьксызшёюячцў"); + let input: String = String::from("абкьдэефгхіжйкʼлмнопкюрстуввьксызшёюячцў"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "abcdefghijjklmnopqrstuvwxyzshyoyuyachzu"); + assert_eq!(output, "abcdeefghijjklmnopqrstuvwxyzshyoyuyachzu"); //Test all letters (Uppercase) - let input: String = String::from("АБКЬДЭФГХІЖЙКʼЛМНОПКЮРСТУВВЬКСЫЗШЁЮЯЧЦЎ"); + let input: String = String::from("АБКЬДЕЭФГХІЖЙКʼЛМНОПКЮРСТУВВЬКСЫЗШЁЮЯЧЦЎ"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ABCDEFGHIJJKLMNOPQRSTUVWXYZSHYOYUYACHZU"); + assert_eq!(output, "ABCDEEFGHIJJKLMNOPQRSTUVWXYZSHYOYUYACHZU"); //Special cases 'Q' let input: String = String::from("москюуітто_пуб"); let output = translator.to_latin(&input); @@ -520,30 +520,21 @@ impl Translator for Belarusian { let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "SRV"); - //Special case: Ye - let input: String = String::from("елл"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "yell"); - let input: String = String::from("ЕЛЛ"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "YELL"); //Special case: ck - let input: String = String::from("чэкк чэкк"); + let input: String = String::from("чекк чэкк"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "check check"); - let input: String = String::from("ЧЭКК ЧЭКК"); + let input: String = String::from("ЧЕКК ЧЭКК"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "CHECK CHECK"); //Special case: k as last character which becomes 'c' - let input: String = String::from("рэк к к"); + let input: String = String::from("рек к к"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "rec k k"); - let input: String = String::from("РЭК К К"); + let input: String = String::from("РЕК К К"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "REC K K"); @@ -612,24 +603,24 @@ impl Translator for Belarusian { println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "а б к д э ф г х і ж к л м н о п кю р с т у в ў кс ы з" + "а б к д е ф г х і ж к л м н о п кю р с т у в ў кс ы з" ); let input: String = String::from("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "А Б К Д Э Ф Г Х І Ж К Л М Н О П КЮ Р С Т У В Ў КС Ы З" + "А Б К Д Е Ф Г Х І Ж К Л М Н О П КЮ Р С Т У В Ў КС Ы З" ); //Test particular case (sh) let input: String = String::from("shell"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "шэлл"); + assert_eq!(output, "шелл"); let input: String = String::from("SHELL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ШЭЛЛ"); + assert_eq!(output, "ШЕЛЛ"); //Test particular case (jo) let input: String = String::from("Option"); let output = translator.to_cyrillic(&input); @@ -652,11 +643,11 @@ impl Translator for Belarusian { let input: String = String::from("gin and games"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "джін анд гамэс"); + assert_eq!(output, "джін анд гамес"); let input: String = String::from("GIN AND GAMES"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ДЖІН АНД ГАМЭС"); + assert_eq!(output, "ДЖІН АНД ГАМЕС"); //Test particular case (iu) let input: String = String::from("iuta"); let output = translator.to_cyrillic(&input); @@ -688,16 +679,16 @@ impl Translator for Belarusian { let input: String = String::from("channel"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "чаннэл"); + assert_eq!(output, "чаннел"); let input: String = String::from("CHANNEL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЧАННЭЛ"); + assert_eq!(output, "ЧАННЕЛ"); //Test some words let input: String = String::from("Usage: cat [OPTION]... [FILE]..."); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "Усаджэ: кат [ОПТЁН]... [ФІЛЭ]..."); + assert_eq!(output, "Усадже: кат [ОПТЁН]... [ФІЛЕ]..."); //Special cases: last character is 'c' let input: String = String::from("chic"); let output = translator.to_cyrillic(&input); @@ -729,11 +720,11 @@ impl Translator for Belarusian { let input: String = String::from("less"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "лэсс"); + assert_eq!(output, "лесс"); let input: String = String::from("LESS"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЛЭСС"); + assert_eq!(output, "ЛЕСС"); //Special cases: last character is 't' let input: String = String::from("cat"); let output = translator.to_cyrillic(&input); diff --git a/src/translator/lang/bulgarian.rs b/src/translator/lang/bulgarian.rs index 1ba3821..de1b740 100644 --- a/src/translator/lang/bulgarian.rs +++ b/src/translator/lang/bulgarian.rs @@ -84,8 +84,8 @@ impl Translator for Bulgarian { 'г' => "g", 'Д' => "D", 'д' => "d", - 'Е' => "YE", - 'е' => "ye", + 'Е' => "E", + 'е' => "e", 'Э' => "E", 'э' => "e", 'Ё' => "YO", @@ -306,8 +306,8 @@ impl Translator for Bulgarian { }, 'D' => "Д", 'd' => "д", - 'E' => "Э", - 'e' => "э", + 'E' => "Е", + 'e' => "е", 'F' => "Ф", 'f' => "ф", 'G' => match input.chars().nth(i + 1) { @@ -478,32 +478,32 @@ mod tests { println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "ls -l"); //Echo hello - let input: String = String::from("экхо хэлло"); + let input: String = String::from("екхо хелло"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "echo hello"); //K vs C - let input: String = String::from("ифконфиг этх0 аддрэсс 192.168.1.30 нэтмаскъ 255.255.255.0"); //Use твёрдый знак to force k in netmask + let input: String = String::from("ифконфиг етх0 аддресс 192.168.1.30 нетмаскъ 255.255.255.0"); //Use твёрдый знак to force k in netmask let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, "ifconfig eth0 address 192.168.1.30 netmask 255.255.255.0" ); - let input: String = String::from("кат РЭАДМЭ.мд"); + let input: String = String::from("кат РЕАДМЕ.мд"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "cat README.md"); //Test all letters (Lowercase) - let input: String = String::from("абкьдэфгхижйкълмнопкюрстуввьксызшщёюячц"); + let input: String = String::from("абкьдеэфгхижйкълмнопкюрстуввьксызшщёюячц"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "abcdefghijjklmnopqrstuvwxyzshshtyoyuyachz"); + assert_eq!(output, "abcdeefghijjklmnopqrstuvwxyzshshtyoyuyachz"); //Test all letters (Uppercase) - let input: String = String::from("АБКЬДЭФГХИЖЙКЪЛМНОПКЮРСТУВВЬКСЫЗШЩЁЮЯЧЦ"); + let input: String = String::from("АБКЬДЕЭФГХИЖЙКЪЛМНОПКЮРСТУВВЬКСЫЗШЩЁЮЯЧЦ"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ABCDEFGHIJJKLMNOPQRSTUVWXYZSHSHTYOYUYACHZ"); + assert_eq!(output, "ABCDEEFGHIJJKLMNOPQRSTUVWXYZSHSHTYOYUYACHZ"); //Special cases 'Q' let input: String = String::from("москюуитто_пуб"); let output = translator.to_latin(&input); @@ -522,30 +522,21 @@ mod tests { let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "SRV"); - //Special case: Ye - let input: String = String::from("елл"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "yell"); - let input: String = String::from("ЕЛЛ"); - let output = translator.to_latin(&input); - println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "YELL"); //Special case: ck - let input: String = String::from("чэкк чэкк"); + let input: String = String::from("чекк чэкк"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "check check"); - let input: String = String::from("ЧЭКК ЧЭКК"); + let input: String = String::from("ЧЕКК ЧЭКК"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "CHECK CHECK"); //Special case: k as last character which becomes 'c' - let input: String = String::from("рэк к к"); + let input: String = String::from("рек к к"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "rec k k"); - let input: String = String::from("РЭК К К"); + let input: String = String::from("РЕК К К"); let output = translator.to_latin(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!(output, "REC K K"); @@ -614,24 +605,24 @@ mod tests { println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "а б к д э ф г х и ж к л м н о п кю р с т у в у кс ы з" + "а б к д е ф г х и ж к л м н о п кю р с т у в у кс ы з" ); let input: String = String::from("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); assert_eq!( output, - "А Б К Д Э Ф Г Х И Ж К Л М Н О П КЮ Р С Т У В У КС Ы З" + "А Б К Д Е Ф Г Х И Ж К Л М Н О П КЮ Р С Т У В У КС Ы З" ); //Test particular case (sh) let input: String = String::from("shell"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "шэлл"); + assert_eq!(output, "шелл"); let input: String = String::from("SHELL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ШЭЛЛ"); + assert_eq!(output, "ШЕЛЛ"); //Test particular case (jo) let input: String = String::from("Option"); let output = translator.to_cyrillic(&input); @@ -654,11 +645,11 @@ mod tests { let input: String = String::from("gin and games"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "джин анд гамэс"); + assert_eq!(output, "джин анд гамес"); let input: String = String::from("GIN AND GAMES"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ДЖИН АНД ГАМЭС"); + assert_eq!(output, "ДЖИН АНД ГАМЕС"); //Test particular case (iu) let input: String = String::from("iuta"); let output = translator.to_cyrillic(&input); @@ -690,16 +681,16 @@ mod tests { let input: String = String::from("channel"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "чаннэл"); + assert_eq!(output, "чаннел"); let input: String = String::from("CHANNEL"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЧАННЭЛ"); + assert_eq!(output, "ЧАННЕЛ"); //Test some words let input: String = String::from("Usage: cat [OPTION]... [FILE]..."); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "Усаджэ: кат [ОПТЁН]... [ФИЛЭ]..."); + assert_eq!(output, "Усадже: кат [ОПТЁН]... [ФИЛЕ]..."); //Special cases: last character is 'c' let input: String = String::from("chic"); let output = translator.to_cyrillic(&input); @@ -731,11 +722,11 @@ mod tests { let input: String = String::from("less"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "лэсс"); + assert_eq!(output, "лесс"); let input: String = String::from("LESS"); let output = translator.to_cyrillic(&input); println!("\"{}\" => \"{}\"", input, output); - assert_eq!(output, "ЛЭСС"); + assert_eq!(output, "ЛЕСС"); //Special cases: last character is 't' let input: String = String::from("cat"); let output = translator.to_cyrillic(&input); From a42337b3adc544b8ca3cb5fe0e1bf10ecbd4846e Mon Sep 17 00:00:00 2001 From: ChristianVisintin Date: Sat, 14 Nov 2020 09:50:45 +0100 Subject: [PATCH 32/32] 0.3.0 --- CHANGELOG.md | 12 +++++++++++- README.md | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35953ef..3e37407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ## Pyc 0.3.0 -Released on ?? +Released on 14/11/2020 - New translators: - 🇷🇸 Serbian @@ -17,6 +17,16 @@ Released on ?? - E => E - Cyrillic to latin: - E => E + - 🇧🇾 Belarusian: + - Latin to cyrillic: + - E => E + - Cyrillic to latin: + - E => E + - 🇧🇬 Bulgarian: + - Latin to cyrillic: + - E => E + - Cyrillic to latin: + - E => E - **Prompt** configuration: - new ```commit_prepend``` and ```commit_append``` keys - New colors keys: diff --git a/README.md b/README.md index 7590fae..4e8cba4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ~ Use your alphabet with your favourite shell ~ Developed by Christian Visintin -Current version: [0.3.0 (??/??/2020)](./CHANGELOG.md#pyc-030) +Current version: [0.3.0 (14/11/2020)](./CHANGELOG.md#pyc-030) --- @@ -304,7 +304,7 @@ I will fix this soon ### Text editors dont' work -I will try to fix this issue +An integrated text editor will be available in 0.4.0 ---