Merge branch 'master' of ../../tmp/nomwut into config_rework

This commit is contained in:
elkowar 2021-07-21 19:25:32 +02:00
commit bcf3f18a24
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
52 changed files with 3158 additions and 0 deletions

848
crates/yuck/Cargo.lock generated Normal file
View file

@ -0,0 +1,848 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
[[package]]
name = "ascii-canvas"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
dependencies = [
"term",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "beef"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6736e2428df2ca2848d846c43e88745121a6654696e349ce0054a420815a7409"
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "console"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"terminal_size",
"winapi",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "ctor"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "derive_more"
version = "0.99.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "diff"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "ena"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "eww_config"
version = "0.1.0"
dependencies = [
"anyhow",
"codespan-reporting",
"derive_more",
"insta",
"itertools",
"lalrpop",
"lalrpop-util",
"lazy_static",
"maplit",
"pretty_assertions",
"regex",
"serde",
"serde_json",
"simplexpr",
"smart-default",
"strum",
"thiserror",
]
[[package]]
name = "fixedbitset"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "insta"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244"
dependencies = [
"console",
"lazy_static",
"ron",
"serde",
"serde_json",
"serde_yaml",
"similar",
"uuid",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "lalrpop"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988"
dependencies = [
"ascii-canvas",
"atty",
"bit-set",
"diff",
"ena",
"itertools",
"lalrpop-util",
"petgraph",
"pico-args",
"regex",
"regex-syntax",
"string_cache",
"term",
"tiny-keccak",
"unicode-xid",
]
[[package]]
name = "lalrpop-util"
version = "0.19.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278"
dependencies = [
"regex",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "logos"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
dependencies = [
"beef",
"fnv",
"proc-macro2",
"quote",
"regex-syntax",
"syn",
"utf8-ranges",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "output_vt100"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
dependencies = [
"winapi",
]
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "petgraph"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "pretty_assertions"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b"
dependencies = [
"ansi_term",
"ctor",
"diff",
"output_vt100",
]
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ron"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f"
dependencies = [
"base64",
"bitflags",
"serde",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
]
[[package]]
name = "rustversion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"dtoa",
"linked-hash-map",
"serde",
"yaml-rust",
]
[[package]]
name = "similar"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
[[package]]
name = "simplexpr"
version = "0.1.0"
dependencies = [
"itertools",
"lalrpop",
"lalrpop-util",
"logos",
"maplit",
"regex",
"serde",
"serde_json",
"strum",
"thiserror",
]
[[package]]
name = "siphasher"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbce6d4507c7e4a3962091436e56e95290cb71fa302d0d270e32130b75fbff27"
[[package]]
name = "smart-default"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "string_cache"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"precomputed-hash",
]
[[package]]
name = "strum"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "utf8-ranges"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba"
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

36
crates/yuck/Cargo.toml Normal file
View file

@ -0,0 +1,36 @@
[package]
name = "eww_config"
version = "0.1.0"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
edition = "2018"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lalrpop-util = "0.19.5"
regex = "1"
itertools = "0.10"
thiserror = "1.0"
maplit = "1.0"
codespan-reporting = "0.11"
derive_more = "0.99"
smart-default = "0.6"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
lazy_static = "1.4"
pretty_assertions = "0.7"
strum = { version = "0.21", features = ["derive"] }
anyhow = "1"
simplexpr = { path = "../../projects/simplexpr" }
[build-dependencies]
lalrpop = "0.19.5"
[dev-dependencies]
insta = { version = "1.7", features = ["ron"]}

4
crates/yuck/build.rs Normal file
View file

@ -0,0 +1,4 @@
extern crate lalrpop;
fn main() {
lalrpop::process_root().unwrap();
}

View file

@ -0,0 +1,30 @@
// use eww_config::{
// format_diagnostic::ToDiagnostic,
// parser::{ast::*, from_ast::FromAst},
//};
fn main() {
// let mut files = codespan_reporting::files::SimpleFiles::new();
// let input = r#"
//(heyho ; :foo { "foo \" } bar " }
//; :baz {(foo == bar ? 12.2 : 12)}
//(foo)
//(defwidget foo [something bla] "foo")
//(baz))"#;
// let file_id = files.add("foo.eww", input);
// let ast = eww_config::parser::parse_string(file_id, input);
// match ast.and_then(eww_config::parser::from_ast::Element::<Ast, Ast>::from_ast) {
// Ok(ast) => {
// println!("{:?}", ast);
//}
// Err(err) => {
// dbg!(&err);
// let diag = err.to_diagnostic();
// use codespan_reporting::term;
// let config = term::Config::default();
// let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always);
// term::emit(&mut writer, &config, &files, &diag).unwrap();
//}
}

View file

@ -0,0 +1,40 @@
use eww_config::{
config::{widget_definition::WidgetDefinition, widget_use::WidgetUse, *},
error::AstError,
format_diagnostic::ToDiagnostic,
parser::from_ast::FromAst,
};
fn main() {
let mut files = codespan_reporting::files::SimpleFiles::new();
let input_use = r#"
(foo :something 12
:bla "bruh"
"some text")
"#;
let input_def = r#"
(defwidget foo [something bla] "foo")
"#;
let file_id_use = files.add("use.eww", input_use);
let file_id_def = files.add("def.eww", input_def);
let parsed_use = WidgetUse::from_ast(eww_config::parser::parse_string(file_id_use, input_use).unwrap()).unwrap();
let parsed_def = WidgetDefinition::from_ast(eww_config::parser::parse_string(file_id_def, input_def).unwrap()).unwrap();
let defs = maplit::hashmap! {
"foo".to_string() => parsed_def,
};
match validate::validate(&defs, &parsed_use) {
Ok(ast) => {
println!("{:?}", ast);
}
Err(err) => {
let err = AstError::ValidationError(err);
let diag = err.to_diagnostic();
use codespan_reporting::term;
let config = term::Config::default();
let mut writer = term::termcolor::StandardStream::stderr(term::termcolor::ColorChoice::Always);
term::emit(&mut writer, &config, &files, &diag).unwrap();
}
}
}

View file

@ -0,0 +1 @@
nightly

14
crates/yuck/rustfmt.toml Normal file
View file

@ -0,0 +1,14 @@
unstable_features = true
fn_single_line = false
max_width = 130
reorder_impl_items = true
merge_imports = true
normalize_comments = true
use_field_init_shorthand = true
#wrap_comments = true
combine_control_expr = false
condense_wildcard_suffixes = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
use_small_heuristics = "Max"

View file

@ -0,0 +1,125 @@
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
};
use simplexpr::{
dynval::{DynVal, FromDynVal},
eval::EvalError,
SimplExpr,
};
use crate::{
error::AstError,
parser::{
ast::{Ast, Span},
from_ast::FromAst,
},
value::AttrName,
};
#[derive(Debug, thiserror::Error)]
pub enum AttrError {
#[error("Missing required attribute {0}")]
MissingRequiredAttr(Span, AttrName),
#[error("{1}")]
EvaluationError(Span, EvalError),
#[error("{1}")]
Other(Span, Box<dyn std::error::Error>),
}
impl AttrError {
pub fn span(&self) -> Span {
match self {
AttrError::MissingRequiredAttr(span, _) => *span,
AttrError::EvaluationError(span, _) => *span,
AttrError::Other(span, _) => *span,
}
}
}
#[derive(Debug)]
pub struct UnusedAttrs {
definition_span: Span,
attrs: Vec<(Span, AttrName)>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct AttrEntry {
pub key_span: Span,
pub value: Ast,
}
impl AttrEntry {
pub fn new(key_span: Span, value: Ast) -> AttrEntry {
AttrEntry { key_span, value }
}
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)]
pub struct Attributes {
pub span: Span,
pub attrs: HashMap<AttrName, AttrEntry>,
}
impl Attributes {
pub fn new(span: Span, attrs: HashMap<AttrName, AttrEntry>) -> Self {
Attributes { span, attrs }
}
pub fn ast_required<T: FromAst>(&mut self, key: &str) -> Result<T, AstError> {
let key = AttrName(key.to_string());
match self.attrs.remove(&key) {
Some(AttrEntry { key_span, value }) => T::from_ast(value),
None => Err(AttrError::MissingRequiredAttr(self.span, key.clone()).into()),
}
}
pub fn ast_optional<T: FromAst>(&mut self, key: &str) -> Result<Option<T>, AstError> {
match self.attrs.remove(&AttrName(key.to_string())) {
Some(AttrEntry { key_span, value }) => T::from_ast(value).map(Some),
None => Ok(None),
}
}
pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError>
where
E: std::error::Error + 'static,
T: FromDynVal<Err = E>,
{
let ast: SimplExpr = self.ast_required(&key)?;
Ok(ast
.eval_no_vars()
.map_err(|err| AttrError::EvaluationError(ast.span().into(), err))?
.read_as()
.map_err(|e| AttrError::Other(ast.span().into(), Box::new(e)))?)
}
pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, AstError>
where
E: std::error::Error + 'static,
T: FromDynVal<Err = E>,
{
let ast: SimplExpr = match self.ast_optional(key)? {
Some(ast) => ast,
None => return Ok(None),
};
Ok(Some(
ast.eval_no_vars()
.map_err(|err| AttrError::EvaluationError(ast.span().into(), err))?
.read_as()
.map_err(|e| AttrError::Other(ast.span().into(), Box::new(e)))?,
))
}
/// Consumes the attributes to return a list of unused attributes which may be used to emit a warning.
/// TODO actually use this and implement warnings,... lol
pub fn get_unused(self, definition_span: Span) -> UnusedAttrs {
UnusedAttrs {
definition_span,
attrs: self.attrs.into_iter().map(|(k, v)| (v.key_span.to(v.value.span().into()), k)).collect(),
}
}
}

View file

@ -0,0 +1,124 @@
use std::str::FromStr;
use anyhow::*;
use crate::{
enum_parse,
error::AstResult,
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::FromAstElementContent,
},
value::NumWithUnit,
};
use super::{attributes::Attributes, window_definition::EnumParseError};
pub type BackendWindowOptions = X11WindowOptions;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct X11WindowOptions {
pub wm_ignore: bool,
pub sticky: bool,
pub window_type: EwwWindowType,
pub struts: StrutDefinition,
}
impl X11WindowOptions {
pub fn from_attrs(attrs: &mut Attributes) -> AstResult<Self> {
let struts = attrs.ast_optional("reserve")?;
let window_type = attrs.primitive_optional("windowtype")?;
Ok(X11WindowOptions {
wm_ignore: attrs.primitive_optional("wm-ignore")?.unwrap_or(window_type.is_none() && struts.is_none()),
window_type: window_type.unwrap_or_default(),
sticky: attrs.primitive_optional("sticky")?.unwrap_or(true),
struts: struts.unwrap_or_default(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)]
pub enum EwwWindowType {
#[default]
Dock,
Dialog,
Toolbar,
Normal,
Utility,
}
impl FromStr for EwwWindowType {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
enum_parse! { "window type", s,
"dock" => Self::Dock,
"toolbar" => Self::Toolbar,
"dialog" => Self::Dialog,
"normal" => Self::Normal,
"utility" => Self::Utility,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, serde::Serialize)]
pub enum Side {
#[default]
Top,
Left,
Right,
Bottom,
}
impl std::str::FromStr for Side {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Side, Self::Err> {
enum_parse! { "side", s,
"l" | "left" => Side::Left,
"r" | "right" => Side::Right,
"t" | "top" => Side::Top,
"b" | "bottom" => Side::Bottom,
}
}
}
// Surface definition if the backend for X11 is enable
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize)]
pub struct StrutDefinition {
pub side: Side,
pub dist: NumWithUnit,
}
impl FromAstElementContent for StrutDefinition {
fn get_element_name() -> &'static str {
"struts"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let mut attrs = iter.expect_key_values()?;
Ok(StrutDefinition { side: attrs.primitive_required("side")?, dist: attrs.primitive_required("distance")? })
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct WaylandWindowOptions {
pub exclusive: bool,
pub focusable: bool,
}
impl WaylandWindowOptions {
pub fn from_attrs(attrs: &mut Attributes) -> AstResult<Self> {
Ok(WaylandWindowOptions {
exclusive: attrs.primitive_optional("exclusive")?.unwrap_or(false),
focusable: attrs.primitive_optional("focusable")?.unwrap_or(false),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct NoBackendWindowOptions;
impl NoBackendWindowOptions {
pub fn from_attrs(attrs: &mut Attributes) -> Result<Self> {
Ok(NoBackendWindowOptions)
}
}

View file

@ -0,0 +1,82 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use super::{
script_var_definition::ScriptVarDefinition, var_definition::VarDefinition, widget_definition::WidgetDefinition,
widget_use::WidgetUse, window_definition::WindowDefinition,
};
use crate::{
config::script_var_definition::{PollScriptVar, TailScriptVar},
error::{AstError, AstResult, OptionAstErrorExt},
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
value::{AttrName, VarName},
};
pub enum TopLevel {
VarDefinition(VarDefinition),
ScriptVarDefinition(ScriptVarDefinition),
WidgetDefinition(WidgetDefinition),
WindowDefinition(WindowDefinition),
}
impl FromAst for TopLevel {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
let mut iter = e.try_ast_iter()?;
let (sym_span, element_name) = iter.expect_symbol()?;
Ok(match element_name.as_str() {
x if x == WidgetDefinition::get_element_name() => Self::WidgetDefinition(WidgetDefinition::from_tail(span, iter)?),
x if x == VarDefinition::get_element_name() => Self::VarDefinition(VarDefinition::from_tail(span, iter)?),
x if x == PollScriptVar::get_element_name() => {
Self::ScriptVarDefinition(ScriptVarDefinition::Poll(PollScriptVar::from_tail(span, iter)?))
}
x if x == TailScriptVar::get_element_name() => {
Self::ScriptVarDefinition(ScriptVarDefinition::Tail(TailScriptVar::from_tail(span, iter)?))
}
x if x == WindowDefinition::get_element_name() => Self::WindowDefinition(WindowDefinition::from_tail(span, iter)?),
x => return Err(AstError::UnknownToplevel(sym_span, x.to_string())),
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct Config {
widget_definitions: HashMap<String, WidgetDefinition>,
window_definitions: HashMap<String, WindowDefinition>,
var_definitions: HashMap<VarName, VarDefinition>,
script_vars: HashMap<VarName, ScriptVarDefinition>,
}
impl FromAst for Config {
fn from_ast(e: Ast) -> AstResult<Self> {
let list = e.as_list()?;
let mut config = Self {
widget_definitions: HashMap::new(),
window_definitions: HashMap::new(),
var_definitions: HashMap::new(),
script_vars: HashMap::new(),
};
for element in list {
match TopLevel::from_ast(element)? {
TopLevel::VarDefinition(x) => {
config.var_definitions.insert(x.name.clone(), x);
}
TopLevel::ScriptVarDefinition(x) => {
config.script_vars.insert(x.name().clone(), x);
}
TopLevel::WidgetDefinition(x) => {
config.widget_definitions.insert(x.name.clone(), x);
}
TopLevel::WindowDefinition(x) => {
config.window_definitions.insert(x.name.clone(), x);
}
}
}
Ok(config)
}
}

View file

@ -0,0 +1,2 @@
#[derive(Debug, thiserror::Error)]
pub enum ConfigParseError {}

View file

@ -0,0 +1,13 @@
pub mod attributes;
pub mod backend_window_options;
mod config;
pub mod config_parse_error;
pub mod script_var_definition;
#[cfg(test)]
mod test;
pub mod validate;
pub mod var_definition;
pub mod widget_definition;
pub mod widget_use;
pub mod window_definition;
pub mod window_geometry;

View file

@ -0,0 +1,74 @@
use std::collections::HashMap;
use simplexpr::{dynval::DynVal, SimplExpr};
use crate::{
error::{AstError, AstResult},
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
value::{AttrName, VarName},
};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub enum ScriptVarDefinition {
Poll(PollScriptVar),
Tail(TailScriptVar),
}
impl ScriptVarDefinition {
pub fn name(&self) -> &VarName {
match self {
ScriptVarDefinition::Poll(x) => &x.name,
ScriptVarDefinition::Tail(x) => &x.name,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub enum VarSource {
// TODO allow for other executors? (python, etc)
Shell(String),
#[serde(skip)]
Function(fn() -> Result<DynVal, Box<dyn std::error::Error>>),
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct PollScriptVar {
pub name: VarName,
pub command: VarSource,
pub interval: std::time::Duration,
}
impl FromAstElementContent for PollScriptVar {
fn get_element_name() -> &'static str {
"defpollvar"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let mut attrs = iter.expect_key_values()?;
let interval = attrs.primitive_required::<DynVal, _>("interval")?.as_duration()?;
// let interval = interval.as_duration()?;
let (_, script) = iter.expect_literal()?;
Ok(Self { name: VarName(name), command: VarSource::Shell(script.to_string()), interval })
}
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct TailScriptVar {
pub name: VarName,
pub command: String,
}
impl FromAstElementContent for TailScriptVar {
fn get_element_name() -> &'static str {
"deftailvar"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let (_, script) = iter.expect_literal()?;
Ok(Self { name: VarName(name), command: script.to_string() })
}
}

View file

@ -0,0 +1,112 @@
---
source: src/config/test.rs
expression: config.unwrap()
---
Config(
widget_definitions: {
"bar": WidgetDefinition(
name: "bar",
expected_args: [
AttrName("arg"),
AttrName("arg2"),
],
widget: WidgetUse(
name: "text",
attrs: Attributes(
span: Span(99, 104, 0),
attrs: {
AttrName("text"): AttrEntry(
key_span: Span(99, 104, 0),
value: Literal(Span(99, 104, 0), DynVal("bla", None)),
),
},
),
children: [],
span: Span(99, 104, 0),
),
span: Span(61, 105, 0),
args_span: Span(76, 86, 0),
),
"foo": WidgetDefinition(
name: "foo",
expected_args: [
AttrName("arg"),
],
widget: WidgetUse(
name: "text",
attrs: Attributes(
span: Span(44, 51, 0),
attrs: {
AttrName("text"): AttrEntry(
key_span: Span(44, 51, 0),
value: Literal(Span(44, 51, 0), DynVal("heyho", None)),
),
},
),
children: [],
span: Span(44, 51, 0),
),
span: Span(11, 52, 0),
args_span: Span(26, 31, 0),
),
},
window_definitions: {
"some-window": WindowDefinition(
name: "some-window",
geometry: Some(WindowGeometry(
anchor_point: AnchorPoint(
x: START,
y: START,
),
offset: Coords(
x: Pixels(0),
y: Pixels(0),
),
size: Coords(
x: Percent(12),
y: Pixels(20),
),
)),
stacking: Foreground,
monitor_number: Some(12),
widget: WidgetUse(
name: "foo",
attrs: Attributes(
span: Span(509, 509, 513),
attrs: {
AttrName("arg"): AttrEntry(
key_span: Span(514, 518, 0),
value: Literal(Span(519, 524, 0), DynVal("bla", None)),
),
},
),
children: [],
span: Span(509, 525, 0),
),
resizable: true,
backend_options: X11WindowOptions(
wm_ignore: false,
sticky: true,
window_type: Dock,
struts: StrutDefinition(
side: Left,
dist: Pixels(30),
),
),
),
},
var_definitions: {
VarName("some_var"): VarDefinition(
name: VarName("some_var"),
initial_value: DynVal("bla", None),
span: Span(114, 137, 0),
),
},
script_vars: {
VarName("stuff"): Tail(TailScriptVar(
name: VarName("stuff"),
command: "tail -f stuff",
)),
},
)

View file

@ -0,0 +1,37 @@
use crate::{
config::config::Config,
parser::{
self,
ast::{Ast, Span},
from_ast::FromAst,
lexer::Lexer,
},
};
#[test]
fn test_config() {
let input = r#"
(defwidget foo [arg]
"heyho")
(defwidget bar [arg arg2]
"bla")
(defvar some_var "bla")
(defpollvar stuff :interval "12s" "date")
(deftailvar stuff "tail -f stuff")
(defwindow some-window
:stacking "fg"
:monitor 12
:resizable true
:geometry (geometry :width "12%" :height "20px")
:reserve (struts :side "left" :distance "30px")
(foo :arg "bla"))
"#;
let lexer = Lexer::new(0, input.to_string());
let p = parser::parser::ToplevelParser::new();
let (span, parse_result) = p.parse(0, lexer).unwrap();
let config = Config::from_ast(Ast::List(span, parse_result));
insta::with_settings!({sort_maps => true}, {
insta::assert_ron_snapshot!(config.unwrap());
});
}

View file

@ -0,0 +1,42 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
error::AstResult,
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::FromAst,
},
value::{AttrName, VarName},
};
use super::{widget_definition::WidgetDefinition, widget_use::WidgetUse};
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Unknown widget referenced: {1}")]
UnknownWidget(Span, String),
#[error("Missing attribute `{arg_name}` in use of widget `{widget_name}`")]
MissingAttr { widget_name: String, arg_name: AttrName, arg_list_span: Span, use_span: Span },
}
pub fn validate(defs: &HashMap<String, WidgetDefinition>, content: &WidgetUse) -> Result<(), ValidationError> {
if let Some(def) = defs.get(&content.name) {
for expected in def.expected_args.iter() {
if !content.attrs.attrs.contains_key(expected) {
return Err(ValidationError::MissingAttr {
widget_name: def.name.to_string(),
arg_name: expected.clone(),
arg_list_span: def.args_span,
use_span: content.span,
});
}
}
} else {
return Err(ValidationError::UnknownWidget(content.span, content.name.to_string()));
}
Ok(())
}

View file

@ -0,0 +1,32 @@
use std::collections::HashMap;
use simplexpr::{dynval::DynVal, SimplExpr};
use crate::{
error::AstResult,
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
value::{AttrName, VarName},
};
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct VarDefinition {
pub name: VarName,
pub initial_value: DynVal,
pub span: Span,
}
impl FromAstElementContent for VarDefinition {
fn get_element_name() -> &'static str {
"defvar"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let (_, initial_value) = iter.expect_literal()?;
Ok(Self { name: VarName(name), initial_value, span })
}
}

View file

@ -0,0 +1,39 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
error::AstResult,
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
value::{AttrName, VarName},
};
use super::widget_use::WidgetUse;
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct WidgetDefinition {
pub name: String,
pub expected_args: Vec<AttrName>,
pub widget: WidgetUse,
pub span: Span,
pub args_span: Span,
}
impl FromAstElementContent for WidgetDefinition {
fn get_element_name() -> &'static str {
"defwidget"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let (args_span, expected_args) = iter.expect_array()?;
let expected_args = expected_args.into_iter().map(|x| x.as_symbol().map(AttrName)).collect::<AstResult<_>>()?;
let widget = iter.expect_any().and_then(WidgetUse::from_ast)?;
// TODO verify that this was the last element in the list
// iter.expect_done()?;
Ok(Self { name, expected_args, widget, span, args_span })
}
}

View file

@ -0,0 +1,52 @@
use std::collections::HashMap;
use simplexpr::SimplExpr;
use crate::{
config::attributes::AttrEntry,
error::AstResult,
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::FromAst,
},
value::AttrName,
};
use super::attributes::Attributes;
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct WidgetUse {
pub name: String,
pub attrs: Attributes,
pub children: Vec<WidgetUse>,
pub span: Span,
}
impl FromAst for WidgetUse {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
if let Ok(text) = e.as_literal_ref() {
Ok(Self {
name: "text".to_string(),
attrs: Attributes::new(
span.into(),
maplit::hashmap! {
AttrName("text".to_string()) => AttrEntry::new(
span.into(),
Ast::Literal(span.into(), text.clone())
)
},
),
children: Vec::new(),
span,
})
} else {
let mut iter = e.try_ast_iter()?;
let (_, name) = iter.expect_symbol()?;
let attrs = iter.expect_key_values()?;
let children = iter.map(WidgetUse::from_ast).collect::<AstResult<Vec<_>>>()?;
Ok(Self { name, attrs, children, span })
}
}
}

View file

@ -0,0 +1,100 @@
use std::{collections::HashMap, fmt::Display, str::FromStr};
use simplexpr::{dynval::DynVal, SimplExpr};
use crate::{
error::{AstError, AstResult},
parser::{
ast::{Ast, Span},
ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
},
value::{AttrName, NumWithUnit, VarName},
};
use super::{backend_window_options::BackendWindowOptions, widget_use::WidgetUse, window_geometry::WindowGeometry};
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub struct WindowDefinition {
pub name: String,
pub geometry: Option<WindowGeometry>,
pub stacking: WindowStacking,
pub monitor_number: Option<i32>,
pub widget: WidgetUse,
pub resizable: bool,
pub backend_options: BackendWindowOptions,
}
impl FromAstElementContent for WindowDefinition {
fn get_element_name() -> &'static str {
"defwindow"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let mut attrs = iter.expect_key_values()?;
let monitor_number = attrs.primitive_optional("monitor")?;
let resizable = attrs.primitive_optional("resizable")?.unwrap_or(true);
let stacking = attrs.primitive_optional("stacking")?.unwrap_or(WindowStacking::Foreground);
let geometry = attrs.ast_optional("geometry")?;
let backend_options = BackendWindowOptions::from_attrs(&mut attrs)?;
let widget = iter.expect_any()?;
Ok(Self { name, monitor_number, resizable, widget, stacking, geometry, backend_options })
}
}
#[derive(Debug, thiserror::Error)]
pub struct EnumParseError {
pub input: String,
pub expected: Vec<&'static str>,
}
impl Display for EnumParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed to parse `{}`, must be one of {}", self.input, self.expected.join(", "))
}
}
/// Parse a string with a concrete set of options into some data-structure,
/// and return an [EnumParseError]
/// ```rs
/// let input = "up";
/// enum_parse { "direction", input,
/// "up" => Direction::Up,
/// "down" => Direction::Down,
/// }
/// ```
#[macro_export]
macro_rules! enum_parse {
($name:literal, $input:expr, $($($s:literal)|* => $val:expr),* $(,)?) => {
let input = $input.to_lowercase();
match input.as_str() {
$( $( $s )|* => Ok($val) ),*,
_ => Err(EnumParseError {
input: input,
expected: vec![$($($s),*),*],
})
}
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, smart_default::SmartDefault, serde::Serialize)]
pub enum WindowStacking {
#[default]
Foreground,
Background,
Bottom,
Overlay,
}
impl std::str::FromStr for WindowStacking {
type Err = EnumParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
enum_parse! { "WindowStacking", s,
"foreground" | "fg" => WindowStacking::Foreground,
"background" | "bg" => WindowStacking::Background,
"bottom" | "bt" => WindowStacking::Bottom,
"overlay" | "ov" => WindowStacking::Overlay,
}
}
}

View file

@ -0,0 +1,150 @@
use std::collections::HashMap;
use simplexpr::{dynval::DynVal, SimplExpr};
use crate::{enum_parse, error::{AstError, AstResult}, parser::{
ast::{Ast, Span}, ast_iterator::AstIterator,
from_ast::{FromAst, FromAstElementContent},
}, value::{AttrName, Coords, VarName}};
use super::{widget_use::WidgetUse, window_definition::EnumParseError};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, strum::Display)]
pub enum AnchorAlignment {
#[strum(serialize = "start")]
#[default]
START,
#[strum(serialize = "center")]
CENTER,
#[strum(serialize = "end")]
END,
}
impl AnchorAlignment {
pub fn from_x_alignment(s: &str) -> Result<AnchorAlignment, EnumParseError> {
enum_parse! { "x-alignment", s,
"l" | "left" => AnchorAlignment::START,
"c" | "center" => AnchorAlignment::CENTER,
"r" | "right" => AnchorAlignment::END,
}
}
pub fn from_y_alignment(s: &str) -> Result<AnchorAlignment, EnumParseError> {
enum_parse! { "y-alignment", s,
"t" | "top" => AnchorAlignment::START,
"c" | "center" => AnchorAlignment::CENTER,
"b" | "bottom" => AnchorAlignment::END,
}
}
pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 {
match self {
AnchorAlignment::START => 0,
AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2),
AnchorAlignment::END => size_container - size_inner,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct AnchorPoint {
pub x: AnchorAlignment,
pub y: AnchorAlignment,
}
impl std::fmt::Display for AnchorPoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use AnchorAlignment::*;
match (self.x, self.y) {
(CENTER, CENTER) => write!(f, "center"),
(x, y) => write!(
f,
"{} {}",
match x {
START => "left",
CENTER => "center",
END => "right",
},
match y {
START => "top",
CENTER => "center",
END => "bottom",
}
),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum AnchorPointParseError {
#[error("Could not parse anchor: Must either be \"center\" or be formatted like \"top left\"")]
WrongFormat(String),
#[error(transparent)]
EnumParseError(#[from] EnumParseError),
}
impl std::str::FromStr for AnchorPoint {
type Err = AnchorPointParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "center" {
Ok(AnchorPoint { x: AnchorAlignment::CENTER, y: AnchorAlignment::CENTER })
} else {
let (first, second) = s
.split_once(' ')
.ok_or_else(|| AnchorPointParseError::WrongFormat(s.to_string()))?;
let x_y_result: Result<_, EnumParseError> = try {
AnchorPoint { x: AnchorAlignment::from_x_alignment(first)?, y: AnchorAlignment::from_y_alignment(second)? }
};
x_y_result.or_else(|_| {
Ok(AnchorPoint { x: AnchorAlignment::from_x_alignment(second)?, y: AnchorAlignment::from_y_alignment(first)? })
})
}
}
}
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Serialize)]
pub struct WindowGeometry {
pub anchor_point: AnchorPoint,
pub offset: Coords,
pub size: Coords,
}
impl FromAstElementContent for WindowGeometry {
fn get_element_name() -> &'static str {
"geometry"
}
fn from_tail<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let mut attrs = iter.expect_key_values()?;
Ok(WindowGeometry {
anchor_point: attrs.primitive_optional("anchor")?.unwrap_or_default(),
size: Coords {
x: attrs.primitive_optional("width")?.unwrap_or_default(),
y: attrs.primitive_optional("height")?.unwrap_or_default(),
},
offset: Coords {
x: attrs.primitive_optional("x")?.unwrap_or_default(),
y: attrs.primitive_optional("y")?.unwrap_or_default(),
},
})
}
}
impl WindowGeometry {
pub fn override_if_given(&self, anchor_point: Option<AnchorPoint>, offset: Option<Coords>, size: Option<Coords>) -> Self {
WindowGeometry {
anchor_point: anchor_point.unwrap_or(self.anchor_point),
offset: offset.unwrap_or(self.offset),
size: size.unwrap_or(self.size),
}
}
}
impl std::fmt::Display for WindowGeometry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{} ({})", self.offset, self.size, self.anchor_point)
}
}

104
crates/yuck/src/error.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::{
config::{attributes::AttrError, validate::ValidationError},
parser::{
ast::{Ast, AstType, Span},
lexer, parse_error,
},
};
use codespan_reporting::{diagnostic, files};
use simplexpr::dynval;
use thiserror::Error;
pub type AstResult<T> = Result<T, AstError>;
#[derive(Debug, Error)]
pub enum AstError {
#[error("Unknown toplevel declaration `{1}`")]
UnknownToplevel(Span, String),
#[error("Expected another element, but got nothing")]
MissingNode(Span),
#[error("Wrong type of expression: Expected {1} but got {2}")]
WrongExprType(Span, AstType, AstType),
#[error("Expected to get a value, but got {1}")]
NotAValue(Span, AstType),
#[error("Expected element {1}, but read {2}")]
MismatchedElementName(Span, String, String),
#[error(transparent)]
ConversionError(#[from] dynval::ConversionError),
#[error("{1}")]
Other(Option<Span>, Box<dyn std::error::Error>),
#[error(transparent)]
AttrError(#[from] AttrError),
#[error(transparent)]
ValidationError(#[from] ValidationError),
#[error("Parse error: {source}")]
ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError> },
}
impl AstError {
pub fn get_span(&self) -> Option<Span> {
match self {
AstError::UnknownToplevel(span, _) => Some(*span),
AstError::MissingNode(span) => Some(*span),
AstError::WrongExprType(span, ..) => Some(*span),
AstError::NotAValue(span, ..) => Some(*span),
AstError::MismatchedElementName(span, ..) => Some(*span),
AstError::AttrError(err) => Some(err.span()),
AstError::Other(span, ..) => *span,
AstError::ConversionError(err) => err.value.span().map(|x| x.into()),
AstError::ValidationError(error) => None, // TODO none here is stupid
AstError::ParseError { file_id, source } => file_id.and_then(|id| get_parse_error_span(id, source)),
}
}
pub fn from_parse_error(
file_id: usize,
err: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError>,
) -> AstError {
AstError::ParseError { file_id: Some(file_id), source: err }
}
}
fn get_parse_error_span(
file_id: usize,
err: &lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError>,
) -> Option<Span> {
match err {
lalrpop_util::ParseError::InvalidToken { location } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedEOF { location, expected } => Some(Span(*location, *location, file_id)),
lalrpop_util::ParseError::UnrecognizedToken { token, expected } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::ExtraToken { token } => Some(Span(token.0, token.2, file_id)),
lalrpop_util::ParseError::User { error } => match error {
parse_error::ParseError::SimplExpr(span, error) => *span,
parse_error::ParseError::LexicalError(span) => Some(*span),
},
}
}
// pub fn spanned(span: Span, err: impl Into<AstError>) -> AstError {
// use AstError::*;
// match err.into() {
// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x),
// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))),
// WrongExprType(s, x, y) => WrongExprType(Some(s.unwrap_or(span)), x, y),
// UnknownToplevel(s, x) => UnknownToplevel(Some(s.unwrap_or(span)), x),
// MissingNode(s) => MissingNode(Some(s.unwrap_or(span))),
// NotAValue(s, x) => NotAValue(Some(s.unwrap_or(span)), x),
// MismatchedElementName(s, expected, got) => MismatchedElementName(Some(s.unwrap_or(span)), expected, got),
// Other(s, x) => Other(Some(s.unwrap_or(span)), x),
// x @ ConversionError(_) | x @ AttrError(_) | x @ ValidationError(_) | x @ ParseError { .. } => x,
//}
pub trait OptionAstErrorExt<T> {
fn or_missing(self, span: Span) -> Result<T, AstError>;
}
impl<T> OptionAstErrorExt<T> for Option<T> {
fn or_missing(self, span: Span) -> Result<T, AstError> {
self.ok_or(AstError::MissingNode(span))
}
}

View file

@ -0,0 +1,137 @@
use codespan_reporting::{diagnostic, files};
use simplexpr::dynval;
use diagnostic::*;
use crate::error::AstError;
use super::parser::{ast::Span, parse_error};
macro_rules! gen_diagnostic {
(
$(msg = $msg:expr)?
$(, label = $span:expr $(=> $label:expr)?)?
$(, note = $note:expr)? $(,)?
) => {
Diagnostic::error()
$(.with_message($msg.to_string()))?
$(.with_labels(vec![
Label::primary($span.2, $span.0..$span.1)
$(.with_message($label))?
]))?
$(.with_notes(vec![$note]))?
};
($msg:expr $(, $span:expr $(,)?)?) => {{
Diagnostic::error()
.with_message($msg.to_string())
$(.with_labels(vec![Label::primary($span.2, $span.0..$span.1)]))?
}};
}
pub trait ToDiagnostic {
fn to_diagnostic(&self) -> Diagnostic<usize>;
}
impl ToDiagnostic for AstError {
fn to_diagnostic(&self) -> Diagnostic<usize> {
if let AstError::ValidationError(error) = self {
match error {
crate::config::validate::ValidationError::UnknownWidget(span, name) => gen_diagnostic! {
msg = format!("No widget named {} exists", name),
label = span => "Used here",
},
crate::config::validate::ValidationError::MissingAttr { widget_name, arg_name, arg_list_span, use_span } => {
let diag = gen_diagnostic! {
msg = format!("{}", error),
};
diag.with_labels(vec![
Label::secondary(use_span.2, use_span.0..use_span.1).with_message("Argument missing here"),
Label::secondary(arg_list_span.2, arg_list_span.0..arg_list_span.1).with_message("but is required here"),
])
}
}
} else if let Some(span) = self.get_span() {
match self {
AstError::UnknownToplevel(_, name) => gen_diagnostic!(format!("{}", self), span),
AstError::MissingNode(_) => gen_diagnostic! {
msg = "Expected another element",
label = span => "Expected another element here",
},
AstError::WrongExprType(_, expected, actual) => gen_diagnostic! {
msg = "Wrong type of expression",
label = span => format!("Expected a `{}` here", expected),
note = format!("Expected: {}\nGot: {}", expected, actual),
},
AstError::NotAValue(_, actual) => gen_diagnostic! {
msg = format!("Expected value, but got `{}`", actual),
label = span => "Expected some value here",
note = format!("Got: {}", actual),
},
AstError::ParseError { file_id, source } => lalrpop_error_to_diagnostic(source, span, |error| match error {
parse_error::ParseError::SimplExpr(_, error) => simplexpr_error_to_diagnostic(error, span),
parse_error::ParseError::LexicalError(_) => lexical_error_to_diagnostic(span),
}),
AstError::MismatchedElementName(_, expected, got) => gen_diagnostic! {
msg = format!("Expected element `{}`, but found `{}`", expected, got),
label = span => format!("Expected `{}` here", expected),
note = format!("Expected: {}\nGot: {}", expected, got),
},
AstError::ConversionError(err) => conversion_error_to_diagnostic(err, span),
AstError::Other(_, source) => gen_diagnostic!(source, span),
AstError::AttrError(source) => gen_diagnostic!(source, span),
AstError::ValidationError(_) => todo!(),
}
} else {
Diagnostic::error().with_message(format!("{}", self))
}
}
}
fn lalrpop_error_to_diagnostic<T: std::fmt::Display, E>(
error: &lalrpop_util::ParseError<usize, T, E>,
span: Span,
handle_user_error: impl FnOnce(&E) -> Diagnostic<usize>,
) -> Diagnostic<usize> {
use lalrpop_util::ParseError::*;
match error {
InvalidToken { location } => gen_diagnostic! { msg = "Invalid token", label = span },
UnrecognizedEOF { location, expected } => gen_diagnostic! {
msg = "Input ended unexpectedly. Check if you have any unclosed delimiters",
label = span
},
UnrecognizedToken { token, expected } => gen_diagnostic! {
msg = format!("Unexpected token `{}` encountered", token.1),
label = span => "Token unexpected",
},
ExtraToken { token } => gen_diagnostic!(format!("Extra token encountered: `{}`", token.1)),
User { error } => handle_user_error(error),
}
}
fn simplexpr_error_to_diagnostic(error: &simplexpr::error::Error, span: Span) -> Diagnostic<usize> {
use simplexpr::error::Error::*;
match error {
ParseError { source, .. } => lalrpop_error_to_diagnostic(source, span, move |error| lexical_error_to_diagnostic(span)),
ConversionError(error) => conversion_error_to_diagnostic(error, span),
Eval(error) => gen_diagnostic!(error, span),
Other(error) => gen_diagnostic!(error, span),
Spanned(_, error) => gen_diagnostic!(error, span),
}
}
fn conversion_error_to_diagnostic(error: &dynval::ConversionError, span: Span) -> Diagnostic<usize> {
let diag = gen_diagnostic! {
msg = format!("{}", error),
label = span => format!("{} is not of type `{}`", error.value, error.target_type),
};
diag.with_notes(error.source.as_ref().map(|x| vec![format!("{}", x)]).unwrap_or_default())
}
fn lexical_error_to_diagnostic(span: Span) -> Diagnostic<usize> {
gen_diagnostic! {
msg = "Invalid token",
label = span => "Invalid token"
}
}

10
crates/yuck/src/lib.rs Normal file
View file

@ -0,0 +1,10 @@
#![allow(unused_imports)]
#![allow(unused)]
#![feature(try_blocks)]
pub mod config;
pub mod error;
pub mod format_diagnostic;
pub mod parser;
mod util;
pub mod value;

View file

@ -0,0 +1,187 @@
use itertools::Itertools;
use simplexpr::{ast::SimplExpr, dynval::DynVal};
use std::collections::HashMap;
use std::fmt::Display;
use super::{ast_iterator::AstIterator, from_ast::FromAst};
use crate::{
config::attributes::{AttrEntry, Attributes},
error::{AstError, AstResult, OptionAstErrorExt},
value::AttrName,
};
#[derive(Eq, PartialEq, Clone, Copy, serde::Serialize)]
pub struct Span(pub usize, pub usize, pub usize);
impl Span {
/// Get the span that includes this and the other span completely.
/// Will panic if the spans are from different file_ids.
pub fn to(mut self, other: Span) -> Self {
assert!(other.2 == self.2);
self.1 = other.1;
self
}
pub fn ending_at(mut self, end: usize) -> Self {
self.1 = end;
self
}
pub fn with_length(mut self, end: usize) -> Self {
self.1 = self.0;
self
}
}
impl Into<simplexpr::Span> for Span {
fn into(self) -> simplexpr::Span {
simplexpr::Span(self.0, self.1, self.2)
}
}
impl From<simplexpr::Span> for Span {
fn from(x: simplexpr::Span) -> Span {
Span(x.0, x.1, x.2)
}
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
}
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum AstType {
List,
Array,
Keyword,
Symbol,
Literal,
SimplExpr,
Comment,
/// A value that could be used as a [SimplExpr]
IntoPrimitive,
}
impl Display for AstType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(PartialEq, Eq, Clone, serde::Serialize)]
pub enum Ast {
List(Span, Vec<Ast>),
Array(Span, Vec<Ast>),
Keyword(Span, String),
Symbol(Span, String),
Literal(Span, DynVal),
SimplExpr(Span, SimplExpr),
Comment(Span),
}
macro_rules! as_func {
($exprtype:expr, $name:ident $nameref:ident < $t:ty > = $p:pat => $value:expr) => {
pub fn $name(self) -> Result<$t, AstError> {
match self {
$p => Ok($value),
x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())),
}
}
pub fn $nameref(&self) -> Result<&$t, AstError> {
match self {
$p => Ok($value),
x => Err(AstError::WrongExprType(x.span(), $exprtype, x.expr_type())),
}
}
};
}
impl Ast {
as_func!(AstType::Literal, as_literal as_literal_ref<DynVal> = Ast::Literal(_, x) => x);
as_func!(AstType::Symbol, as_symbol as_symbol_ref<String> = Ast::Symbol(_, x) => x);
as_func!(AstType::Keyword, as_keyword as_keyword_ref<String> = Ast::Keyword(_, x) => x);
as_func!(AstType::List, as_list as_list_ref<Vec<Ast>> = Ast::List(_, x) => x);
pub fn expr_type(&self) -> AstType {
match self {
Ast::List(..) => AstType::List,
Ast::Array(..) => AstType::Array,
Ast::Keyword(..) => AstType::Keyword,
Ast::Symbol(..) => AstType::Symbol,
Ast::Literal(..) => AstType::Literal,
Ast::SimplExpr(..) => AstType::SimplExpr,
Ast::Comment(_) => AstType::Comment,
}
}
pub fn span(&self) -> Span {
match self {
Ast::List(span, _) => *span,
Ast::Array(span, _) => *span,
Ast::Keyword(span, _) => *span,
Ast::Symbol(span, _) => *span,
Ast::Literal(span, _) => *span,
Ast::SimplExpr(span, _) => *span,
Ast::Comment(span) => *span,
}
}
pub fn as_simplexpr(self) -> AstResult<SimplExpr> {
match self {
// do I do this?
// Ast::Array(_, _) => todo!(),
// Ast::Symbol(_, _) => todo!(),
Ast::Literal(span, x) => Ok(SimplExpr::Literal(span.into(), x)),
Ast::SimplExpr(span, x) => Ok(x),
_ => Err(AstError::WrongExprType(self.span(), AstType::IntoPrimitive, self.expr_type())),
}
}
pub fn try_ast_iter(self) -> AstResult<AstIterator<impl Iterator<Item = Ast>>> {
let span = self.span();
let list = self.as_list()?;
Ok(AstIterator::new(span, list.into_iter()))
}
}
impl std::fmt::Display for Ast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Ast::*;
match self {
List(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")),
Array(_, x) => write!(f, "({})", x.iter().map(|e| format!("{}", e)).join(" ")),
Keyword(_, x) => write!(f, ":{}", x),
Symbol(_, x) => write!(f, "{}", x),
Literal(_, x) => write!(f, "\"{}\"", x),
SimplExpr(_, x) => write!(f, "{{{}}}", x),
Comment(_) => write!(f, ""),
}
}
}
impl std::fmt::Debug for Ast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Ast::*;
write!(f, "{}", self)
// match self {
// List(span, x) => f.debug_tuple(&format!("List<{}>", span)).field(x).finish(),
// Array(span, x) => f.debug_tuple(&format!("Array<{}>", span)).field(x).finish(),
// Keyword(span, x) => write!(f, "Number<{}>({})", span, x),
// Symbol(span, x) => write!(f, "Symbol<{}>({})", span, x),
// Value(span, x) => write!(f, "Value<{}>({})", span, x),
// SimplExpr(span, x) => write!(f, "SimplExpr<{}>({})", span, x),
// Comment(span) => write!(f, "Comment<{}>", span),
//}
}
}

View file

@ -0,0 +1,100 @@
use itertools::Itertools;
use simplexpr::{ast::SimplExpr, dynval::DynVal};
use std::collections::HashMap;
use std::fmt::Display;
use super::{
ast::{Ast, AstType, Span},
from_ast::FromAst,
};
use crate::{
config::attributes::{AttrEntry, Attributes},
error::{AstError, AstResult, OptionAstErrorExt},
value::AttrName,
};
pub struct AstIterator<I: Iterator<Item = Ast>> {
remaining_span: Span,
iter: itertools::PutBack<I>,
}
macro_rules! return_or_put_back {
($name:ident, $expr_type:expr, $t:ty = $p:pat => $ret:expr) => {
pub fn $name(&mut self) -> AstResult<$t> {
let expr_type = $expr_type;
match self.expect_any()? {
$p => {
let (span, value) = $ret;
self.remaining_span.1 = span.1;
Ok((span, value))
}
other => {
let span = other.span();
let actual_type = other.expr_type();
self.iter.put_back(other);
Err(AstError::WrongExprType(span, expr_type, actual_type))
}
}
}
};
}
impl<I: Iterator<Item = Ast>> AstIterator<I> {
return_or_put_back!(expect_symbol, AstType::Symbol, (Span, String) = Ast::Symbol(span, x) => (span, x));
return_or_put_back!(expect_literal, AstType::Literal, (Span, DynVal) = Ast::Literal(span, x) => (span, x));
return_or_put_back!(expect_list, AstType::List, (Span, Vec<Ast>) = Ast::List(span, x) => (span, x));
return_or_put_back!(expect_array, AstType::Array, (Span, Vec<Ast>) = Ast::Array(span, x) => (span, x));
pub fn new(span: Span, iter: I) -> Self {
AstIterator { remaining_span: span, iter: itertools::put_back(iter) }
}
pub fn expect_any<T: FromAst>(&mut self) -> AstResult<T> {
self.iter.next().or_missing(self.remaining_span.with_length(0)).and_then(T::from_ast)
}
pub fn expect_key_values(&mut self) -> AstResult<Attributes> {
parse_key_values(self)
}
}
impl<I: Iterator<Item = Ast>> Iterator for AstIterator<I> {
type Item = Ast;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
}
/// Parse consecutive `:keyword value` pairs from an expression iterator into an [Attributes].
fn parse_key_values(iter: &mut AstIterator<impl Iterator<Item = Ast>>) -> AstResult<Attributes> {
let mut data = HashMap::new();
let mut attrs_span = Span(iter.remaining_span.0, iter.remaining_span.0, iter.remaining_span.1);
loop {
match iter.next() {
Some(Ast::Keyword(key_span, kw)) => match iter.next() {
Some(value) => {
attrs_span.1 = iter.remaining_span.0;
let attr_value = AttrEntry { key_span, value };
data.insert(AttrName(kw), attr_value);
}
None => {
iter.iter.put_back(Ast::Keyword(key_span, kw));
attrs_span.1 = iter.remaining_span.0;
return Ok(Attributes::new(attrs_span, data));
}
},
next => {
if let Some(expr) = next {
iter.iter.put_back(expr);
}
attrs_span.1 = iter.remaining_span.0;
return Ok(Attributes::new(attrs_span, data));
}
}
}
}

View file

@ -0,0 +1,58 @@
use super::{
ast::{Ast, AstType, Span},
ast_iterator::AstIterator,
};
use crate::{error::*, parser, util, value::AttrName};
use itertools::Itertools;
use simplexpr::{ast::SimplExpr, dynval::DynVal};
use std::{
collections::{HashMap, LinkedList},
iter::FromIterator,
str::FromStr,
};
pub trait FromAst: Sized {
fn from_ast(e: Ast) -> AstResult<Self>;
}
impl FromAst for Ast {
fn from_ast(e: Ast) -> AstResult<Self> {
Ok(e)
}
}
impl FromAst for String {
fn from_ast(e: Ast) -> AstResult<Self> {
Ok(e.as_literal()?.as_string().unwrap())
}
}
/// A trait that allows creating a type from the tail of a list-node.
/// I.e. to parse (foo [a b] (c d)), [from_tail] would just get [a b] (c d).
pub trait FromAstElementContent: Sized {
fn get_element_name() -> &'static str;
fn from_tail<I: Iterator<Item = Ast>>(span: Span, iter: AstIterator<I>) -> AstResult<Self>;
}
impl<T: FromAstElementContent> FromAst for T {
fn from_ast(e: Ast) -> AstResult<Self> {
let span = e.span();
let mut iter = e.try_ast_iter()?;
let (_, element_name) = iter.expect_symbol()?;
if Self::get_element_name() != element_name {
return Err(AstError::MismatchedElementName(span, Self::get_element_name().to_string(), element_name));
}
Self::from_tail(span, iter)
}
}
impl FromAst for SimplExpr {
fn from_ast(e: Ast) -> AstResult<Self> {
match e {
Ast::Symbol(span, x) => Ok(SimplExpr::VarRef(span.into(), x)),
Ast::Literal(span, x) => Ok(SimplExpr::Literal(span.into(), x)),
Ast::SimplExpr(span, x) => Ok(x),
_ => Err(AstError::NotAValue(e.span(), e.expr_type())),
}
}
}

View file

@ -0,0 +1,151 @@
use regex::{Regex, RegexSet};
use super::{ast::Span, parse_error};
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Token {
LPren,
RPren,
LBrack,
RBrack,
True,
False,
StrLit(String),
NumLit(String),
Symbol(String),
Keyword(String),
SimplExpr(String),
Comment,
Skip,
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::LPren => write!(f, "'('"),
Token::RPren => write!(f, "')'"),
Token::LBrack => write!(f, "'['"),
Token::RBrack => write!(f, "']'"),
Token::True => write!(f, "true"),
Token::False => write!(f, "false"),
Token::StrLit(x) => write!(f, "\"{}\"", x),
Token::NumLit(x) => write!(f, "{}", x),
Token::Symbol(x) => write!(f, "{}", x),
Token::Keyword(x) => write!(f, "{}", x),
Token::SimplExpr(x) => write!(f, "{{{}}}", x),
Token::Comment => write!(f, ""),
Token::Skip => write!(f, ""),
}
}
}
macro_rules! regex_rules {
($(
$regex:literal => $token:expr),*
) => {
lazy_static::lazy_static! {
static ref LEXER_REGEX_SET: RegexSet = RegexSet::new(&[
$(format!("^{}", $regex)),*
]).unwrap();
static ref LEXER_REGEXES: Vec<Regex> = vec![
$(Regex::new(&format!("^{}", $regex)).unwrap()),*
];
static ref LEXER_FNS: Vec<Box<dyn Fn(String) -> Token + Sync>> = vec![
$(Box::new($token)),*
];
}
}
}
regex_rules! {
r"\(" => |_| Token::LPren,
r"\)" => |_| Token::RPren,
r"\[" => |_| Token::LBrack,
r"\]" => |_| Token::RBrack,
r"true" => |_| Token::True,
r"false" => |_| Token::False,
r#""(?:[^"\\]|\\.)*""# => |x| Token::StrLit(x),
r#"[+-]?(?:[0-9]+[.])?[0-9]+"# => |x| Token::NumLit(x),
r#":[^\s\)\]}]+"# => |x| Token::Keyword(x),
r#"[a-zA-Z_!\?<>/\.\*-\+][^\s{}\(\)\[\](){}]*"# => |x| Token::Symbol(x),
r#";.*"# => |_| Token::Comment,
r"[ \t\n\f]+" => |_| Token::Skip
}
pub struct Lexer {
source: String,
file_id: usize,
failed: bool,
pos: usize,
}
impl Lexer {
pub fn new(file_id: usize, source: String) -> Self {
Lexer { source, file_id, failed: false, pos: 0 }
}
}
impl Iterator for Lexer {
type Item = Result<(usize, Token, usize), parse_error::ParseError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.failed || self.pos >= self.source.len() {
return None;
}
let string = &self.source[self.pos..];
if string.starts_with('{') {
self.pos += 1;
let expr_start = self.pos;
let mut in_string = false;
loop {
if self.pos >= self.source.len() {
return None;
}
let string = &self.source[self.pos..];
if string.starts_with('}') && !in_string {
let tok_str = &self.source[expr_start..self.pos];
self.pos += 1;
return Some(Ok((expr_start, Token::SimplExpr(tok_str.to_string()), self.pos - 1)));
} else if string.starts_with('"') {
self.pos += 1;
in_string = !in_string;
} else if string.starts_with("\\\"") {
self.pos += 2;
} else {
self.pos += 1;
}
}
} else {
let match_set = LEXER_REGEX_SET.matches(string);
let matched_token = match_set
.into_iter()
.map(|i: usize| {
let m = LEXER_REGEXES[i].find(string).unwrap();
(m.end(), i)
})
.min_by_key(|(_, x)| *x);
let (len, i) = match matched_token {
Some(x) => x,
None => {
self.failed = true;
return Some(Err(parse_error::ParseError::LexicalError(Span(self.pos, self.pos, self.file_id))));
}
};
let tok_str = &self.source[self.pos..self.pos + len];
let old_pos = self.pos;
self.pos += len;
match LEXER_FNS[i](tok_str.to_string()) {
Token::Skip | Token::Comment => {}
token => {
return Some(Ok((old_pos, token, self.pos)));
}
}
}
}
}
}

View file

@ -0,0 +1,63 @@
use lalrpop_util::lalrpop_mod;
use super::error::{AstError, AstResult};
use ast::Ast;
use std::{fmt::Display, ops::Deref};
use itertools::Itertools;
pub mod ast;
pub mod ast_iterator;
pub mod from_ast;
pub(crate) mod lexer;
pub(crate) mod parse_error;
lalrpop_mod!(
#[allow(clippy::all)]
pub parser,
"/parser/parser.rs"
);
pub fn parse_string(file_id: usize, s: &str) -> AstResult<Ast> {
let lexer = lexer::Lexer::new(file_id, s.to_string());
let parser = parser::AstParser::new();
parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e))
}
macro_rules! test_parser {
($($text:literal),*) => {{
let p = parser::AstParser::new();
use lexer::Lexer;
::insta::with_settings!({sort_maps => true}, {
$(
::insta::assert_debug_snapshot!(p.parse(0, Lexer::new(0, $text.to_string())));
)*
});
}}
}
#[test]
fn test() {
test_parser!(
"1",
"(12)",
"1.2",
"-1.2",
"(1 2)",
"(1 :foo 1)",
"(:foo 1)",
"(:foo->: 1)",
"(foo 1)",
"(lol😄 1)",
r#"(test "hi")"#,
r#"(test "h\"i")"#,
r#"(test " hi ")"#,
"(+ (1 2 (* 2 5)))",
r#"foo ; test"#,
r#"(f arg ; test
arg2)"#,
"\"h\\\"i\""
);
}

View file

@ -0,0 +1,10 @@
use super::ast::Span;
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("{1}")]
SimplExpr(Option<Span>, simplexpr::error::Error),
#[error("Unknown token")]
LexicalError(Span),
}

View file

@ -0,0 +1,75 @@
use std::str::FromStr;
use crate::parser::{lexer::{Token}, ast::{Ast, Span}, parse_error};
use simplexpr::ast::SimplExpr;
use simplexpr;
use lalrpop_util::ParseError;
grammar(file_id: usize);
extern {
type Location = usize;
type Error = parse_error::ParseError;
enum Token {
"(" => Token::LPren,
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"true" => Token::True,
"false" => Token::False,
"string" => Token::StrLit(<String>),
"number" => Token::NumLit(<String>),
"symbol" => Token::Symbol(<String>),
"keyword" => Token::Keyword(<String>),
"simplexpr" => Token::SimplExpr(<String>),
"comment" => Token::Comment,
}
}
pub Toplevel: (Span, Vec<Ast>) = {
<l:@L> <elems:(<Ast>)*> <r:@R> => (Span(l, r, file_id), elems)
}
pub Ast: Ast = {
<l:@L> "(" <elems:(<Ast>)*> ")" <r:@R> => Ast::List(Span(l, r, file_id), elems),
<l:@L> "[" <elems:(<Ast>)*> "]" <r:@R> => Ast::Array(Span(l, r, file_id), elems),
<l:@L> <expr:SimplExpr> <r:@R> => Ast::SimplExpr(Span(l, r, file_id), expr),
<x:Keyword> => x,
<x:Symbol> => x,
<l:@L> <x:Literal> <r:@R> => Ast::Literal(Span(l, r, file_id), x.into()),
<l:@L> "comment" <r:@R> => Ast::Comment(Span(l, r, file_id)),
};
Keyword: Ast = <l:@L> <x:"keyword"> <r:@R> => Ast::Keyword(Span(l, r, file_id), x[1..].to_string());
Symbol: Ast = <l:@L> <x:"symbol"> <r:@R> => Ast::Symbol(Span(l, r, file_id), x.to_string());
Literal: String = {
<StrLit> => <>,
<Num> => <>,
<Bool> => <>,
};
StrLit: String = {
<x:"string"> => {
x[1..x.len() - 1].to_owned()
},
};
SimplExpr: SimplExpr = {
<l:@L> <x:"simplexpr"> =>? {
let expr = x[1..x.len() - 1].to_string();
simplexpr::parse_string(file_id, &expr).map_err(|e| {
let span = e.get_span().map(|simplexpr::Span(simpl_l, simpl_r, file_id)| Span(1 + l + simpl_l, 1 + l + simpl_r, file_id));
ParseError::User { error: parse_error::ParseError::SimplExpr(span, e) }})
}
}
Num: String = <"number"> => <>.to_string();
Bool: String = {
"true" => "true".to_string(),
"false" => "false".to_string(),
}
// vim:shiftwidth=4

View file

@ -0,0 +1,17 @@
---
source: src/parser/element.rs
expression: "Element::<Ast, Ast>::from_ast(parser.parse(0, lexer).unwrap()).unwrap()"
---
Element {
name: "box",
attrs: {
"baz": "hi",
"bar": "12",
},
children: [
foo,
(bar),
],
span: 0..33,
}

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(lol😄 1)\".to_string()))"
---
Ok(
(lol😄 "1"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, r#\"(test \"hi\")\"#.to_string()))"
---
Ok(
(test "hi"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, r#\"(test \"h\\\"i\")\"#.to_string()))"
---
Ok(
(test "h\"i"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, r#\"(test \" hi \")\"#.to_string()))"
---
Ok(
(test " hi "),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(+ (1 2 (* 2 5)))\".to_string()))"
---
Ok(
(+ ("1" "2" (* "2" "5"))),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, r#\"foo ; test\"#.to_string()))"
---
Ok(
foo,
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, r#\"(f arg ; test\n arg2)\"#.to_string()))"
---
Ok(
(f arg arg2),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"\\\"h\\\\\\\"i\\\"\".to_string()))"
---
Ok(
"h\"i",
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(12)\".to_string()))"
---
Ok(
("12"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"1.2\".to_string()))"
---
Ok(
"1.2",
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"-1.2\".to_string()))"
---
Ok(
"-1.2",
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(1 2)\".to_string()))"
---
Ok(
("1" "2"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(1 :foo 1)\".to_string()))"
---
Ok(
("1" :foo "1"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(:foo 1)\".to_string()))"
---
Ok(
(:foo "1"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(:foo->: 1)\".to_string()))"
---
Ok(
(:foo->: "1"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"(foo 1)\".to_string()))"
---
Ok(
(foo "1"),
)

View file

@ -0,0 +1,8 @@
---
source: src/parser/mod.rs
expression: "p.parse(0, Lexer::new(0, \"1\".to_string()))"
---
Ok(
"1",
)

0
crates/yuck/src/util.rs Normal file
View file

View file

@ -0,0 +1,112 @@
use derive_more::*;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::{fmt, str::FromStr};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to parse \"{0}\" as a length value")]
NumParseFailed(String),
#[error("Inalid unit \"{0}\", must be either % or px")]
InvalidUnit(String),
#[error("Invalid format. Coordinates must be formated like 200x100")]
MalformedCoords,
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)]
pub enum NumWithUnit {
#[display(fmt = "{}%", .0)]
#[debug(fmt = "{}%", .0)]
Percent(i32),
#[display(fmt = "{}px", .0)]
#[debug(fmt = "{}px", .0)]
#[default]
Pixels(i32),
}
impl NumWithUnit {
pub fn relative_to(&self, max: i32) -> i32 {
match *self {
NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
}
}
}
impl FromStr for NumWithUnit {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static::lazy_static! {
static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap();
};
let captures = PATTERN.captures(s).ok_or_else(|| Error::NumParseFailed(s.to_string()))?;
let value = captures.get(1).unwrap().as_str().parse::<i32>().map_err(|_| Error::NumParseFailed(s.to_string()))?;
match captures.get(2).unwrap().as_str() {
"px" | "" => Ok(NumWithUnit::Pixels(value)),
"%" => Ok(NumWithUnit::Percent(value)),
unit => Err(Error::InvalidUnit(unit.to_string())),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)]
#[display(fmt = "{}*{}", x, y)]
pub struct Coords {
pub x: NumWithUnit,
pub y: NumWithUnit,
}
impl FromStr for Coords {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (x, y) = s
.split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*')
.ok_or(Error::MalformedCoords)?;
Coords::from_strs(x, y)
}
}
impl fmt::Debug for Coords {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CoordsWithUnits({}, {})", self.x, self.y)
}
}
impl Coords {
pub fn from_pixels(x: i32, y: i32) -> Self {
Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) }
}
/// parse a string for x and a string for y into a [`Coords`] object.
pub fn from_strs(x: &str, y: &str) -> Result<Coords, Error> {
Ok(Coords { x: x.parse()?, y: y.parse()? })
}
/// resolve the possibly relative coordinates relative to a given containers size
pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
(self.x.relative_to(width), self.y.relative_to(height))
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_num_with_unit() {
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap());
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap());
assert_eq!(NumWithUnit::Percent(55), NumWithUnit::from_str("55%").unwrap());
assert!(NumWithUnit::from_str("55pp").is_err());
}
#[test]
fn test_parse_coords() {
assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap());
assert!(Coords::from_str("5060").is_err());
}
}

View file

@ -0,0 +1,41 @@
use derive_more::*;
use serde::{Deserialize, Serialize};
pub mod coords;
pub use coords::*;
/// The name of a variable
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt = "VarName({})", .0)]
pub struct VarName(pub String);
impl std::borrow::Borrow<str> for VarName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for VarName {
fn from(s: &str) -> Self {
VarName(s.to_owned())
}
}
/// The name of an attribute
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt="AttrName({})", .0)]
pub struct AttrName(pub String);
impl std::borrow::Borrow<str> for AttrName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for AttrName {
fn from(s: &str) -> Self {
AttrName(s.to_owned())
}
}