diff options
-rw-r--r-- | Cargo.lock | 132 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/config.rs | 2 | ||||
-rw-r--r-- | src/formatter/mod.rs | 5 | ||||
-rw-r--r-- | src/formatter/model.rs | 17 | ||||
-rw-r--r-- | src/formatter/parser.rs | 76 | ||||
-rw-r--r-- | src/formatter/spec.pest | 16 | ||||
-rw-r--r-- | src/formatter/string_formatter.rs | 252 | ||||
-rw-r--r-- | src/lib.rs | 4 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/module.rs | 5 | ||||
-rw-r--r-- | src/segment.rs | 7 |
12 files changed, 517 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock index 6c3246433..5c9ff58c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,11 +108,40 @@ dependencies = [ ] [[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "byte-unit" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "bytes" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -231,6 +260,14 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "dirs" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -278,6 +315,11 @@ dependencies = [ ] [[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -296,6 +338,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "gethostname" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -456,6 +506,11 @@ dependencies = [ ] [[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -562,6 +617,11 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "open" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -629,6 +689,45 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "pkg-config" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -877,6 +976,17 @@ dependencies = [ ] [[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "smallvec" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -901,6 +1011,8 @@ dependencies = [ "open 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "os_info 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "path-slash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1032,6 +1144,11 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1170,7 +1287,11 @@ dependencies = [ "checksum battery 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "36a698e449024a5d18994a815998bf5e2e4bc1883e35a7d7ba95b6b69ee45907" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byte-unit 3.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6894a79550807490d9f19a138a6da0f8830e70c83e83402dd23f16fd6c479056" +"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" @@ -1185,15 +1306,18 @@ dependencies = [ "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" "checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum gethostname 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum git2 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ef222034f2069cfc5af01ce423574d3d9a3925bd4052912a14e5bcfd7ca9e47a" @@ -1213,6 +1337,7 @@ dependencies = [ "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum mach 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" @@ -1226,6 +1351,7 @@ dependencies = [ "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" "checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum open 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c283bf0114efea9e42f1a60edea9859e8c47528eae09d01df4b29c1e489cc48" "checksum openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)" = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" @@ -1234,6 +1360,10 @@ dependencies = [ "checksum os_info 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ecb53e7b83e5016bf4ac041e15e02b0d240cb27072b19b651b0b4d8cd6bbda9" "checksum path-slash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a0858af4d9136275541f4eac7be1af70add84cf356d901799b065ac1b8ff6e2f" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +"checksum pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +"checksum pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum pretty_env_logger 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" @@ -1264,6 +1394,7 @@ dependencies = [ "checksum serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)" = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" "checksum serde_json 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)" = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" "checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" @@ -1277,6 +1408,7 @@ dependencies = [ "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +"checksum ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" diff --git a/Cargo.toml b/Cargo.toml index 8926c4a6d..9e4d462c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,8 @@ sysinfo = "0.12.0" byte-unit = "3.0.3" starship_module_config_derive = { version = "0.1.0", path = "starship_module_config_derive" } yaml-rust = "0.4" +pest = "^2.1" +pest_derive = "^2.1" nom = "5.1.1" regex = "1.3.6" os_info = "2.0.2" diff --git a/src/config.rs b/src/config.rs index d3f868f16..235351556 100644 --- a/src/config.rs +++ b/src/config.rs @@ -305,7 +305,7 @@ impl Default for SegmentConfig<'static> { - 'italic' - '<color>' (see the parse_color_string doc for valid color strings) */ -fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> { +pub fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> { style_string .split_whitespace() .fold(Some(ansi_term::Style::new()), |maybe_style, token| { diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs new file mode 100644 index 000000000..b0607df0a --- /dev/null +++ b/src/formatter/mod.rs @@ -0,0 +1,5 @@ +pub mod model; +mod parser; +pub mod string_formatter; + +pub use string_formatter::StringFormatter; diff --git a/src/formatter/model.rs b/src/formatter/model.rs new file mode 100644 index 000000000..8e4bbea39 --- /dev/null +++ b/src/formatter/model.rs @@ -0,0 +1,17 @@ +use std::borrow::Cow; + +pub struct TextGroup<'a> { + pub format: Vec<FormatElement<'a>>, + pub style: Vec<StyleElement<'a>>, +} + +pub enum FormatElement<'a> { + Text(Cow<'a, str>), + Variable(Cow<'a, str>), + TextGroup(TextGroup<'a>), +} + +pub enum StyleElement<'a> { + Text(Cow<'a, str>), + Variable(Cow<'a, str>), +} diff --git a/src/formatter/parser.rs b/src/formatter/parser.rs new file mode 100644 index 000000000..95c267a3a --- /dev/null +++ b/src/formatter/parser.rs @@ -0,0 +1,76 @@ +use pest::{error::Error, iterators::Pair, Parser}; + +use super::model::*; + +#[derive(Parser)] +#[grammar = "formatter/spec.pest"] +struct IdentParser; + +fn _parse_textgroup(textgroup: Pair<Rule>) -> TextGroup { + let mut inner_rules = textgroup.into_inner(); + let format = inner_rules.next().unwrap(); + let style = inner_rules.next().unwrap(); + + TextGroup { + format: _parse_format(format), + style: _parse_style(style), + } +} + +fn _parse_variable(variable: Pair<Rule>) -> &str { + variable.into_inner().next().unwrap().as_str() +} + +fn _parse_text(text: Pair<Rule>) -> String { + let mut result = String::new(); + for pair in text.into_inner() { + result.push_str(pair.as_str()); + } + result +} + +fn _parse_format(format: Pair<Rule>) -> Vec<FormatElement> { + let mut result: Vec<FormatElement> = Vec::new(); + + for pair in format.into_inner() { + match pair.as_rule() { + Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())), + Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())), + Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))), + _ => unreachable!(), + } + } + + result +} + +fn _parse_style(style: Pair<Rule>) -> Vec<StyleElement> { + let mut result: Vec<StyleElement> = Vec::new(); + + for pair in style.into_inner() { + match pair.as_rule() { + Rule::text => result.push(StyleElement::Text(_parse_text(pair).into())), + Rule::variable => result.push(StyleElement::Variable(_parse_variable(pair).into())), + _ => unreachable!(), + } + } + + result +} + +pub fn parse(format: &str) -> Result<Vec<FormatElement>, Error<Rule>> { + let pairs = IdentParser::parse(Rule::expression, format)?; + let mut result: Vec<FormatElement> = Vec::new(); + + // Lifetime of Segment is the same as result + for pair in pairs.take_while(|pair| pair.as_rule() != Rule::EOI) { + match pair.as_rule() { + Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())), + Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())), + Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))), + _ => unreachable!(), + } + } + + Ok(result) +} diff --git a/src/formatter/spec.pest b/src/formatter/spec.pest new file mode 100644 index 000000000..36be53be5 --- /dev/null +++ b/src/formatter/spec.pest @@ -0,0 +1,16 @@ +expression = _{ SOI ~ value* ~ EOI } +value = _{ text | variable | textgroup } + +variable = { "$" ~ variable_name } +variable_name = @{ char+ } +char = _{ 'a'..'z' | 'A'..'Z' | '0'..'9' | "_" } + +text = { text_inner+ } +text_inner = _{ text_inner_char | escape } +text_inner_char = { !("[" | "]" | "(" | ")" | "$" | "\\") ~ ANY } +escape = _{ "\\" ~ escaped_char } +escaped_char = { "[" | "]" | "(" | ")" | "\\" | "$" } + +textgroup = { "[" ~ format ~ "]" ~ "(" ~ style ~ ")" } +format = { (variable | text | textgroup)* } +style = { (variable | text)* } diff --git a/src/formatter/string_formatter.rs b/src/formatter/string_formatter.rs new file mode 100644 index 000000000..70938973f --- /dev/null +++ b/src/formatter/string_formatter.rs @@ -0,0 +1,252 @@ +use ansi_term::Style; +use pest::error::Error; +use rayon::prelude::*; +use std::collections::BTreeMap; + +use crate::config::parse_style_string; +use crate::segment::Segment; + +use super::model::*; +use super::parser::{parse, Rule}; + +type VariableMapType = BTreeMap<String, Option<Vec<Segment>>>; + +pub struct StringFormatter<'a> { + format: Vec<FormatElement<'a>>, + variables: VariableMapType, +} + +impl<'a> StringFormatter<'a> { + /// Creates an instance of StringFormatter from a format string + pub fn new(format: &'a str) -> Result<Self, Error<Rule>> { + parse(format) + .map(|format| { + let variables = _get_variables(&format); + (format, variables) + }) + .map(|(format, variables)| Self { format, variables }) + } + + /// Maps variable name to its value + pub fn map(mut self, mapper: impl Fn(&str) -> Option<String> + Sync) -> Self { + self.variables.par_iter_mut().for_each(|(key, value)| { + *value = mapper(key).map(|value| vec![_new_segment(key.to_string(), value, None)]); + }); + self + } + + /// Maps variable name to an array of segments + pub fn map_variables_to_segments( + mut self, + mapper: impl Fn(&str) -> Option<Vec<Segment>> + Sync, + ) -> Self { + self.variables.par_iter_mut().for_each(|(key, value)| { + *value = mapper(key); + }); + self + } + + /// Parse the format string and consume self. + pub fn parse(self, default_style: Option<Style>) -> Vec<Segment> { + fn _parse_textgroup<'a>( + textgroup: TextGroup<'a>, + variables: &'a VariableMapType, + ) -> Vec<Segment> { + let style = _parse_style(textgroup.style); + _parse_format(textgroup.format, style, &variables) + } + + fn _parse_style(style: Vec<StyleElement>) -> Option<Style> { + let style_string = style + .iter() + .flat_map(|style| match style { + StyleElement::Text(text) => text.as_ref().chars(), + StyleElement::Variable(variable) => { + log::warn!( + "Variable `{}` monitored in style string, which is not allowed", + &variable + ); + "".chars() + } + }) + .collect::<String>(); + parse_style_string(&style_string) + } + + fn _parse_format<'a>( + mut format: Vec<FormatElement<'a>>, + style: Option<Style>, + variables: &'a VariableMapType, + ) -> Vec<Segment> { + let mut result: Vec<Segment> = Vec::new(); + + format.reverse(); + while let Some(el) = format.pop() { + let mut segments = match el { + FormatElement::Text(text) => { + vec![_new_segment("_text".into(), text.into_owned(), style)] + } + FormatElement::TextGroup(textgroup) => { + let textgroup = TextGroup { + format: textgroup.format, + style: textgroup.style, + }; + _parse_textgroup(textgroup, &variables) + } + FormatElement::Variable(name) => variables + .get(name.as_ref()) + .map(|segments| segments.clone().unwrap_or_default()) + .unwrap_or_default(), + }; + result.append(&mut segments); + } + + result + } + + _parse_format(self.format, default_style, &self.variables) + } +} + +/// Extract variable names from an array of `FormatElement` into a `BTreeMap` +fn _get_variables<'a>(format: &[FormatElement<'a>]) -> VariableMapType { + let mut variables: VariableMapType = Default::default(); + + fn _push_variables_from_textgroup<'a>( + variables: &mut VariableMapType, + textgroup: &'a TextGroup<'a>, + ) { + for el in &textgroup.format { + match el { + FormatElement::Variable(name) => _push_variable(variables, name.as_ref()), + FormatElement::TextGroup(textgroup) => { + _push_variables_from_textgroup(variables, &textgroup) + } + _ => {} + } + } + for el in &textgroup.style { + if let StyleElement::Variable(name) = el { + _push_variable(variables, name.as_ref()) + } + } + } + + fn _push_variable<'a>(variables: &mut VariableMapType, name: &'a str) { + variables.insert(name.to_owned(), None); + } + + for el in format { + match el { + FormatElement::Variable(name) => _push_variable(&mut variables, name.as_ref()), + FormatElement::TextGroup(textgroup) => { + _push_variables_from_textgroup(&mut variables, &textgroup) + } + _ => {} + } + } + + variables +} + +/// Helper function to create a new segment +fn _new_segment(name: String, value: String, style: Option<Style>) -> Segment { + Segment { + _name: name, + value, + style, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ansi_term::Color; + + // match_next(result: Iter<Segment>, value, style) + macro_rules! match_next { + ($iter:ident, $value:literal, $($style:tt)+) => { + let _next = $iter.next().unwrap(); + assert_eq!(_next.value, $value); + assert_eq!(_next.style, $($style)+); + } + } + + fn empty_mapper(_: &str) -> Option<String> { + None + } + + #[test] + fn test_default_style() { + const FORMAT_STR: &str = "text"; + let style = Some(Color::Red.bold()); + + let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); + let result = formatter.parse(style); + let mut result_iter = result.iter(); + match_next!(result_iter, "text", style); + } + + #[test] + fn test_textgroup_text_only() { + const FORMAT_STR: &str = "[text](red bold)"; + let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); + let result = formatter.parse(None); + let mut result_iter = result.iter(); + match_next!(result_iter, "text", Some(Color::Red.bold())); + } + + #[test] + fn test_variable_only() { + const FORMAT_STR: &str = "$var1"; + + let formatter = StringFormatter::new(FORMAT_STR) + .unwrap() + .map(|variable| match variable { + "var1" => Some("text1".to_owned()), + _ => None, + }); + let result = formatter.parse(None); + let mut result_iter = result.iter(); + match_next!(result_iter, "text1", None); + } + + #[test] + fn test_escaped_chars() { + const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#; + + let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); + let result = formatter.parse(None); + let mut result_iter = result.iter(); + match_next!(result_iter, r#"\[$text](red bold)"#, None); + } + + #[test] + fn test_nested_textgroup() { + const FORMAT_STR: &str = "outer [middle [inner](blue)](red bold)"; + let outer_style = Some(Color::Green.normal()); + let middle_style = Some(Color::Red.bold()); + let inner_style = Some(Color::Blue.normal()); + + let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); + let result = formatter.parse(outer_style); + let mut result_iter = result.iter(); + match_next!(result_iter, "outer ", outer_style); + match_next!(result_iter, "middle ", middle_style); + match_next!(result_iter, "inner", inner_style); + } + + #[test] + fn test_parse_error() { + // brackets without escape + { + const FORMAT_STR: &str = "["; + assert!(StringFormatter::new(FORMAT_STR).is_err()); + } + // Dollar without variable + { + const FORMAT_STR: &str = "$ "; + assert!(StringFormatter::new(FORMAT_STR).is_err()); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 08c91ec3a..7621ecd4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ +#[macro_use] +extern crate pest_derive; + // Lib is present to allow for benchmarking pub mod config; pub mod configs; pub mod context; +pub mod formatter; pub mod module; pub mod modules; pub mod print; diff --git a/src/main.rs b/src/main.rs index 9b917a34b..1ae1eca0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,15 @@ use std::time::SystemTime; #[macro_use] extern crate clap; +#[macro_use] +extern crate pest_derive; mod bug_report; mod config; mod configs; mod configure; mod context; +mod formatter; mod init; mod module; mod modules; diff --git a/src/module.rs b/src/module.rs index 7fc2c72e5..79a4b6407 100644 --- a/src/module.rs +++ b/src/module.rs @@ -99,6 +99,11 @@ impl<'a> Module<'a> { self.segments.last_mut().unwrap() } + /// Set segments in module + pub fn set_segment(&mut self, segments: Vec<Segment>) { + self.segments = segments; + } + /// Get module's name pub fn get_name(&self) -> &String { &self._name diff --git a/src/segment.rs b/src/segment.rs index d01034bb0..73e24b9a8 100644 --- a/src/segment.rs +++ b/src/segment.rs @@ -4,15 +4,16 @@ use std::fmt; /// A segment is a single configurable element in a module. This will usually /// contain a data point to provide context for the prompt's user /// (e.g. The version that software is running). +#[derive(Clone)] pub struct Segment { /// The segment's name, to be used in configuration and logging. - _name: String, + pub _name: String, /// The segment's style. If None, will inherit the style of the module containing it. - style: Option<Style>, + pub style: Option<Style>, /// The string value of the current segment. - value: String, + pub value: String, } impl Segment { |