diff --git a/CHANGELOG.md b/CHANGELOG.md index b60b550..3e37407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,50 @@ # 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 14/11/2020 + +- New translators: + - 🇷🇸 Serbian + - 🇺🇦 Ukrainian +- Translators changes: + - 🇷🇺 Russian: + - Latin to cyrillic: + - 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: + - KBOLD + - KBLINK + - KSELECT +- 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..f7e4cb0 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,19 +284,19 @@ 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" -version = "0.2.0" +version = "0.3.0" dependencies = [ "ansi_term", "dirs", @@ -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..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" @@ -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/README.md b/README.md index 142aad5..4e8cba4 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 (14/11/2020)](./CHANGELOG.md#pyc-030) --- @@ -36,7 +36,6 @@ Current version: [0.2.0 (27/06/2020)](./CHANGELOG.md#pyc-020-27062020) - [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) @@ -63,32 +62,35 @@ 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 -- ![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)) +- ![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 - *TBD* -- ![me](https://raw.githubusercontent.com/gosquared/flags/master/flags/flags/shiny/24/Montenegro.png) Montenegrin Cyrillic - *TBD* -- ![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)* +- ![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 @@ -155,6 +157,8 @@ prompt: git: branch: "on  " commit_ref_len: 8 + commit_prepend: "(" + commit_append: ")" ``` - shell: Shell configuration @@ -165,6 +169,8 @@ 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 - prompt: Prompt configuration (See [Prompt Configuration](#prompt-line-configuration)) @@ -182,6 +188,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 @@ -204,17 +212,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 @@ -229,9 +240,11 @@ The developer documentation can be found on Rust Docs at } +#[derive(Clone)] pub struct OutputConfig { pub translate_output: bool, } +#[derive(Clone)] pub struct PromptConfig { pub prompt_line: String, pub history_size: usize, @@ -63,6 +67,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 +329,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 +414,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 +437,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 +460,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("✖")); @@ -453,7 +477,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] @@ -614,7 +660,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 +669,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/main.rs b/src/main.rs index 969cd77..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 @@ -63,6 +62,9 @@ fn str_to_language(lang: String) -> Language { "ru" | "рус" => Language::Russian, "by" | "бел" => Language::Belarusian, "bg" | "бг" | "блг" => Language::Bulgarian, + "rs" | "срб" => Language::Serbian, + "ua" | "укр" => Language::Ukrainian, + "nil" => Language::Nil, _ => { eprintln!( "{}", @@ -192,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 @@ -208,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..2a5bc12 --- /dev/null +++ b/src/runtime/imiop/mod.rs @@ -0,0 +1,46 @@ +//! ## 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 eb6e087..eb35cbc 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}; @@ -39,11 +41,12 @@ 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 +// 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 +57,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 +70,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 +82,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, ) } }; @@ -89,11 +93,11 @@ pub fn run_interactive(processor: IOProcessor, config: config::Config, shell: Op 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 - 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 +112,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 +123,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 +131,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 +140,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 +153,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 +172,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 +184,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 +195,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 +204,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 +217,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 @@ -312,6 +318,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 +437,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..ccbccad 100644 --- a/src/runtime/props.rs +++ b/src/runtime/props.rs @@ -23,77 +23,56 @@ * */ -use super::{print_err, print_out, resolve_command}; +use super::imiop::{self, Imiop}; use crate::config::Config; -use crate::shell::Shell; -use crate::shell::proc::ShellState; +use crate::shell::{Shell, ShellState}; use crate::translator::ioprocessor::IOProcessor; -use crate::utils::buffer; -use crate::utils::console::{self, InputEvent}; +use crate::translator::lang::Language; +use crate::translator::new_translator; +use crate::utils::console::InputEvent; /// ## 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: bool, - 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: false, - 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; @@ -101,397 +80,64 @@ 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 + /// ### handle_input_event /// - /// 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(); - } + /// Handle input event received from stdin + pub(super) fn handle_input_event(&mut self, ev: InputEvent, shell: &mut 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); } - /// ### move_left + /// ### init_imiop /// - /// 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(); + /// 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)), + )), } } - /// ### move_right + /// ### switch_imiop /// - /// 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(); - 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 - //TODO: exit rev search - }, - 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 - //TODO: rev search - }, - _ => {} //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; - } - //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); - } - } - } - } - - /// ### 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(); - //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; - } + /// Change current imiop based on states + fn switch_imiop(&mut self) { + // Change if last state changed + if self.get_state_changed() { + // TODO: text editor + // Check last_state + self.imiop = match self.get_last_state() { + ShellState::Shell => 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)), + )), }; - //Clear input buffer - self.clear_buffer(); - //Process input - self.process_input_interactive(shell, input); - } - } - - /// ### 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); - } - } - } - - /// ### 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) + // Reset state changed + self.report_state_changed_notified(); } } } @@ -501,39 +147,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::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, false); - 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] @@ -543,251 +168,47 @@ 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); } #[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() { + fn test_runtimeprops_switch_imiop() { 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); + // State hasn't changed + props.state_changed = false; + props.last_state = ShellState::Shell; + 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::Shell; + 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() { let mut props: RuntimeProps = new_runtime_props(true); - let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); + 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.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 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 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', ' ', '-']); - 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); - //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']); - 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 + //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 @@ -795,55 +216,25 @@ mod tests { #[test] fn test_runtimeprops_handle_input_event_not_interactive() { - //Non interactive shell enter let mut props: RuntimeProps = new_runtime_props(false); - let mut shell: Shell = Shell::start(String::from("sh"), Vec::new(), &props.config.prompt_config).unwrap(); + 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.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")); } 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) } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 5fe4824..0db748d 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,18 @@ 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, + Terminated, + Unknown +} + /// ### Shell /// /// Shell represents the current user shell configuration @@ -48,7 +60,8 @@ pub struct Shell { pub history: ShellHistory, process: ShellProc, prompt: ShellPrompt, - props: ShellProps + props: ShellProps, + state: ShellState } /// ### ShellProps @@ -81,13 +94,14 @@ 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, prompt: shell_prompt, props: ShellProps::new(hostname, user, wrkdir), - history: ShellHistory::new() + history: ShellHistory::new(), + state: ShellState::Shell }) } @@ -128,7 +142,17 @@ 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 { + _ => { + self.state = match proc_state { + ShellProcState::Idle => ShellState::Shell, + ShellProcState::SubprocessRunning => ShellState::SubprocessRunning, + _ => ShellState::Terminated + }; + self.state + } + } } /// ### refresh_env @@ -136,7 +160,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 +172,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 @@ -197,9 +231,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(), ShellState::Shell); //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); @@ -236,7 +272,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(), 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()); @@ -271,7 +307,7 @@ mod tests { 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(), ShellState::Shell); } //Now should be IDLE //Okay, send SIGINT now @@ -292,7 +328,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(), ShellState::Shell); //Terminate the shell gracefully sleep(Duration::from_millis(500)); let command: String = String::from("exit 5\n"); @@ -320,4 +356,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/mod.rs b/src/shell/proc/mod.rs index b9d62d0..c21e4ae 100644 --- a/src/shell/proc/mod.rs +++ b/src/shell/proc/mod.rs @@ -35,15 +35,14 @@ 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, - Unknown + Terminated } /// ### ShellError @@ -65,7 +64,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 d211fbd..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}; @@ -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 @@ -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()); diff --git a/src/shell/prompt/mod.rs b/src/shell/prompt/mod.rs index 8f2ed31..21927d0 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,12 +235,23 @@ 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(""), } } 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 { @@ -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() } } } @@ -362,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(); @@ -374,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(), @@ -384,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); @@ -439,11 +459,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 +503,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(), 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()); } diff --git a/src/shell/prompt/modules/language.rs b/src/shell/prompt/modules/language.rs index 6a912bd..e3bbd9d 100644 --- a/src/shell/prompt/modules/language.rs +++ b/src/shell/prompt/modules/language.rs @@ -64,6 +64,34 @@ pub fn language_to_str(language: Language) -> String { PromptColor::Red.to_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(), + 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() + )), + 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() )) } } @@ -75,17 +103,29 @@ 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)); + 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/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 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")); } } 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); diff --git a/src/translator/lang/mod.rs b/src/translator/lang/mod.rs index 4bf7c69..518783c 100644 --- a/src/translator/lang/mod.rs +++ b/src/translator/lang/mod.rs @@ -32,6 +32,9 @@ pub enum Language { Belarusian, Bulgarian, Russian, + Serbian, + Ukrainian, + Nil } /// ## Languages @@ -42,16 +45,25 @@ pub enum Language { pub(crate) struct Belarusian {} 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 { match self { Language::Belarusian => String::from("бел"), Language::Bulgarian => String::from("блг"), - Language::Russian => String::from("рус") + Language::Russian => String::from("рус"), + Language::Serbian => String::from("срб"), + Language::Ukrainian => String::from("укр"), + Language::Nil => String::from("nil") } } } @@ -66,6 +78,9 @@ 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("укр")); + 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/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); 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/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..c573710 100644 --- a/src/translator/mod.rs +++ b/src/translator/mod.rs @@ -54,6 +54,9 @@ 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 {}), + Language::Nil => Box::new(lang::Nil {}) } } @@ -68,6 +71,9 @@ 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); + let _ = new_translator(Language::Nil); } }