improvement: add parser for format strings (#1021)

This PR implements the parser of format strings described in #624.
This commit is contained in:
Zhenhui Xie 2020-04-07 01:16:18 +08:00 committed by GitHub
parent 3510bfe044
commit 22dc419a3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 517 additions and 4 deletions

132
Cargo.lock generated
View File

@ -107,11 +107,40 @@ dependencies = [
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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"
@ -230,6 +259,14 @@ dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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"
@ -277,6 +314,11 @@ dependencies = [
"termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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"
@ -295,6 +337,14 @@ name = "foreign-types-shared"
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"
@ -455,6 +505,11 @@ dependencies = [
"libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "matches"
version = "0.1.8"
@ -561,6 +616,11 @@ name = "once_cell"
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"
@ -628,6 +688,45 @@ name = "percent-encoding"
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"
@ -876,6 +975,17 @@ dependencies = [
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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"
@ -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)",
@ -1031,6 +1143,11 @@ name = "typenum"
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"
@ -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"

View File

@ -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"

View File

@ -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| {

5
src/formatter/mod.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod model;
mod parser;
pub mod string_formatter;
pub use string_formatter::StringFormatter;

17
src/formatter/model.rs Normal file
View File

@ -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>),
}

76
src/formatter/parser.rs Normal file
View File

@ -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)
}

16
src/formatter/spec.pest Normal file
View File

@ -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)* }

View File

@ -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());
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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 {