config: change default config to use toml-based configuration
This commit is contained in:
parent
e24a61bc62
commit
3cebf651c5
58 changed files with 14093 additions and 145 deletions
|
|
@ -6,6 +6,8 @@ tasks:
|
||||||
sudo pacman -Syu --noconfirm
|
sudo pacman -Syu --noconfirm
|
||||||
sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake
|
sudo pacman -S --noconfirm rustup libinput pango mesa libxkbcommon xorg-xwayland adwaita-icon-theme libxcursor cmake
|
||||||
rustup toolchain install stable
|
rustup toolchain install stable
|
||||||
|
cd jay
|
||||||
|
git submodule update --init
|
||||||
- test: |
|
- test: |
|
||||||
cd jay
|
cd jay
|
||||||
cargo test
|
cargo test
|
||||||
|
|
|
||||||
31
.github/workflows/toml-spec.yml
vendored
Normal file
31
.github/workflows/toml-spec.yml
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: toml-spec
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
rustfmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Show env
|
||||||
|
run: |
|
||||||
|
uname -a
|
||||||
|
ldd --version
|
||||||
|
- name: Install
|
||||||
|
run: |
|
||||||
|
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
|
||||||
|
rustup toolchain install nightly --allow-downgrade -c rustfmt
|
||||||
|
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
- name: Check
|
||||||
|
run: |
|
||||||
|
cd toml-spec
|
||||||
|
cargo run
|
||||||
|
git diff --exit-code
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
.*
|
.*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!.gitmodules
|
||||||
!/.cargo
|
!/.cargo
|
||||||
!/.builds
|
!/.builds
|
||||||
!/.github
|
!/.github
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "toml-config/toml-test"]
|
||||||
|
path = toml-config/toml-test
|
||||||
|
url = https://github.com/mahkoh/toml-tests.git
|
||||||
245
Cargo.lock
generated
245
Cargo.lock
generated
|
|
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.7"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
|
@ -77,9 +77,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
|
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
|
|
@ -111,9 +111,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.79"
|
version = "1.0.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
|
|
@ -174,9 +174,9 @@ checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.9.0"
|
version = "1.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -292,13 +292,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "default-config"
|
name = "deranged"
|
||||||
version = "0.1.0"
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"powerfmt",
|
||||||
"jay-config",
|
|
||||||
"log",
|
|
||||||
"rand",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -479,12 +478,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.2"
|
version = "2.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -516,7 +516,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"default-config",
|
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"gpu-alloc",
|
"gpu-alloc",
|
||||||
|
|
@ -525,6 +524,7 @@ dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"isnt",
|
"isnt",
|
||||||
"jay-config",
|
"jay-config",
|
||||||
|
"jay-toml-config",
|
||||||
"libloading 0.8.1",
|
"libloading 0.8.1",
|
||||||
"log",
|
"log",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
|
|
@ -557,6 +557,23 @@ dependencies = [
|
||||||
"uapi",
|
"uapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jay-toml-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bstr",
|
||||||
|
"error_reporter",
|
||||||
|
"indexmap",
|
||||||
|
"jay-config",
|
||||||
|
"log",
|
||||||
|
"phf",
|
||||||
|
"serde_json",
|
||||||
|
"simplelog",
|
||||||
|
"thiserror",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.67"
|
version = "0.3.67"
|
||||||
|
|
@ -627,9 +644,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.20"
|
version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
|
|
@ -646,6 +663,12 @@ dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
@ -666,6 +689,15 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.32.2"
|
version = "0.32.2"
|
||||||
|
|
@ -710,6 +742,48 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||||
|
dependencies = [
|
||||||
|
"phf_macros",
|
||||||
|
"phf_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_generator"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_macros"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.5"
|
version = "1.1.5"
|
||||||
|
|
@ -742,6 +816,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -894,6 +974,15 @@ version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -902,18 +991,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.196"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.196"
|
version = "1.0.197"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -926,11 +1015,25 @@ version = "1.0.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shaderc"
|
name = "shaderc"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
|
|
@ -952,6 +1055,23 @@ dependencies = [
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simplelog"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"termcolor",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
|
@ -995,6 +1115,15 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -1025,6 +1154,39 @@ dependencies = [
|
||||||
"syn 2.0.48",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
@ -1040,6 +1202,18 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml-spec"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"error_reporter",
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uapi"
|
name = "uapi"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
|
|
@ -1072,6 +1246,12 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -1084,6 +1264,16 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
@ -1160,6 +1350,15 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||||
build = "build/build.rs"
|
build = "build/build.rs"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["jay-config", "default-config", "algorithms"]
|
members = ["jay-config", "toml-config", "algorithms", "toml-spec"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
@ -30,7 +30,7 @@ smallvec = { version = "1.11.1", features = ["const_generics", "const_new", "uni
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
jay-config = { path = "jay-config" }
|
jay-config = { path = "jay-config" }
|
||||||
default-config = { path = "default-config" }
|
jay-toml-config = { path = "toml-config" }
|
||||||
algorithms = { path = "algorithms" }
|
algorithms = { path = "algorithms" }
|
||||||
pin-project = "1.1.4"
|
pin-project = "1.1.4"
|
||||||
clap = { version = "4.4.18", features = ["derive", "wrap_help"] }
|
clap = { version = "4.4.18", features = ["derive", "wrap_help"] }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "default-config"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["lib", "cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
jay-config = { path = "../jay-config" }
|
|
||||||
log = "0.4.14"
|
|
||||||
rand = "0.8.5"
|
|
||||||
chrono = "0.4.19"
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
use {
|
|
||||||
chrono::{format::StrftimeItems, Local},
|
|
||||||
jay_config::{
|
|
||||||
config,
|
|
||||||
exec::Command,
|
|
||||||
get_workspace,
|
|
||||||
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
|
||||||
keyboard::{
|
|
||||||
mods::{Modifiers, ALT, CTRL, SHIFT},
|
|
||||||
syms::{
|
|
||||||
SYM_Super_L, SYM_c, SYM_d, SYM_f, SYM_h, SYM_j, SYM_k, SYM_l, SYM_m, SYM_p, SYM_q,
|
|
||||||
SYM_r, SYM_t, SYM_u, SYM_v, SYM_F1, SYM_F10, SYM_F11, SYM_F12, SYM_F2, SYM_F3,
|
|
||||||
SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
quit, reload,
|
|
||||||
status::set_status,
|
|
||||||
switch_to_vt,
|
|
||||||
timer::{duration_until_wall_clock_is_multiple_of, get_timer},
|
|
||||||
video::on_graphics_initialized,
|
|
||||||
Axis::{Horizontal, Vertical},
|
|
||||||
Direction::{Down, Left, Right, Up},
|
|
||||||
},
|
|
||||||
std::time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MOD: Modifiers = ALT;
|
|
||||||
|
|
||||||
fn configure_seat(s: Seat) {
|
|
||||||
s.bind(MOD | SYM_h, move || s.focus(Left));
|
|
||||||
s.bind(MOD | SYM_j, move || s.focus(Down));
|
|
||||||
s.bind(MOD | SYM_k, move || s.focus(Up));
|
|
||||||
s.bind(MOD | SYM_l, move || s.focus(Right));
|
|
||||||
|
|
||||||
s.bind(MOD | SHIFT | SYM_h, move || s.move_(Left));
|
|
||||||
s.bind(MOD | SHIFT | SYM_j, move || s.move_(Down));
|
|
||||||
s.bind(MOD | SHIFT | SYM_k, move || s.move_(Up));
|
|
||||||
s.bind(MOD | SHIFT | SYM_l, move || s.move_(Right));
|
|
||||||
|
|
||||||
s.bind(MOD | SYM_d, move || s.create_split(Horizontal));
|
|
||||||
s.bind(MOD | SYM_v, move || s.create_split(Vertical));
|
|
||||||
|
|
||||||
s.bind(MOD | SYM_t, move || s.toggle_split());
|
|
||||||
s.bind(MOD | SYM_m, move || s.toggle_mono());
|
|
||||||
s.bind(MOD | SYM_u, move || s.toggle_fullscreen());
|
|
||||||
|
|
||||||
s.bind(MOD | SYM_f, move || s.focus_parent());
|
|
||||||
|
|
||||||
s.bind(MOD | SHIFT | SYM_c, move || s.close());
|
|
||||||
|
|
||||||
s.bind(MOD | SHIFT | SYM_f, move || s.toggle_floating());
|
|
||||||
|
|
||||||
s.bind(SYM_Super_L, || Command::new("alacritty").spawn());
|
|
||||||
|
|
||||||
s.bind(MOD | SYM_p, || Command::new("bemenu-run").spawn());
|
|
||||||
|
|
||||||
s.bind(MOD | SYM_q, quit);
|
|
||||||
|
|
||||||
s.bind(MOD | SHIFT | SYM_r, reload);
|
|
||||||
|
|
||||||
let fnkeys = [
|
|
||||||
SYM_F1, SYM_F2, SYM_F3, SYM_F4, SYM_F5, SYM_F6, SYM_F7, SYM_F8, SYM_F9, SYM_F10, SYM_F11,
|
|
||||||
SYM_F12,
|
|
||||||
];
|
|
||||||
for (i, sym) in fnkeys.into_iter().enumerate() {
|
|
||||||
s.bind(CTRL | ALT | sym, move || switch_to_vt(i as u32 + 1));
|
|
||||||
|
|
||||||
let ws = get_workspace(&format!("{}", i + 1));
|
|
||||||
s.bind(MOD | sym, move || s.show_workspace(ws));
|
|
||||||
s.bind(MOD | SHIFT | sym, move || s.set_workspace(ws));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_status() {
|
|
||||||
let time_format: Vec<_> = StrftimeItems::new("%Y-%m-%d %H:%M:%S").collect();
|
|
||||||
let update_status = move || {
|
|
||||||
let status = format!("{}", Local::now().format_with_items(time_format.iter()));
|
|
||||||
set_status(&status);
|
|
||||||
};
|
|
||||||
update_status();
|
|
||||||
let period = Duration::from_secs(5);
|
|
||||||
let timer = get_timer("status_timer");
|
|
||||||
timer.repeated(duration_until_wall_clock_is_multiple_of(period), period);
|
|
||||||
timer.on_tick(update_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure() {
|
|
||||||
// Configure seats and input devices
|
|
||||||
let seat = get_seat("default");
|
|
||||||
configure_seat(seat);
|
|
||||||
let handle_input_device = move |device: InputDevice| {
|
|
||||||
device.set_seat(seat);
|
|
||||||
};
|
|
||||||
input_devices().into_iter().for_each(handle_input_device);
|
|
||||||
on_new_input_device(handle_input_device);
|
|
||||||
|
|
||||||
// Configure the status message
|
|
||||||
setup_status();
|
|
||||||
|
|
||||||
// Start programs
|
|
||||||
on_graphics_initialized(|| {
|
|
||||||
Command::new("mako").spawn();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
config!(configure);
|
|
||||||
|
|
@ -150,7 +150,7 @@ unsafe extern "C" fn default_client_init(
|
||||||
size: usize,
|
size: usize,
|
||||||
) -> *const u8 {
|
) -> *const u8 {
|
||||||
extern "C" fn configure() {
|
extern "C" fn configure() {
|
||||||
default_config::configure();
|
jay_toml_config::configure();
|
||||||
}
|
}
|
||||||
jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure)
|
jay_config::_private::client::init(srv_data, srv_unref, srv_handler, msg, size, configure)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
toml-config/Cargo.toml
Normal file
22
toml-config/Cargo.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "jay-toml-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["lib", "cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
jay-config = { path = "../jay-config" }
|
||||||
|
log = "0.4.14"
|
||||||
|
thiserror = "1.0.57"
|
||||||
|
error_reporter = "1.0.0"
|
||||||
|
phf = { version = "0.11.2", features = ["macros"] }
|
||||||
|
indexmap = "2.2.5"
|
||||||
|
bstr = { version = "1.9.1", default-features = false }
|
||||||
|
ahash = "0.8.11"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
simplelog = { version = "0.12.2", features = ["test"] }
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
walkdir = "2.5.0"
|
||||||
307
toml-config/src/config.rs
Normal file
307
toml-config/src/config.rs
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|
mod context;
|
||||||
|
pub mod error;
|
||||||
|
mod extractor;
|
||||||
|
mod keysyms;
|
||||||
|
mod parser;
|
||||||
|
mod parsers;
|
||||||
|
mod spanned;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
parsers::config::{ConfigParser, ConfigParserError},
|
||||||
|
},
|
||||||
|
toml::{self},
|
||||||
|
},
|
||||||
|
jay_config::{
|
||||||
|
input::acceleration::AccelProfile,
|
||||||
|
keyboard::{Keymap, ModifiedKeySym},
|
||||||
|
logging::LogLevel,
|
||||||
|
status::MessageFormat,
|
||||||
|
theme::Color,
|
||||||
|
video::{GfxApi, Transform},
|
||||||
|
Axis, Direction,
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
time::Duration,
|
||||||
|
},
|
||||||
|
thiserror::Error,
|
||||||
|
toml::toml_parser,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum SimpleCommand {
|
||||||
|
Close,
|
||||||
|
DisablePointerConstraint,
|
||||||
|
Focus(Direction),
|
||||||
|
FocusParent,
|
||||||
|
Move(Direction),
|
||||||
|
None,
|
||||||
|
Quit,
|
||||||
|
ReloadConfigSo,
|
||||||
|
ReloadConfigToml,
|
||||||
|
Split(Axis),
|
||||||
|
ToggleFloating,
|
||||||
|
ToggleFullscreen,
|
||||||
|
ToggleMono,
|
||||||
|
ToggleSplit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Action {
|
||||||
|
ConfigureConnector { con: ConfigConnector },
|
||||||
|
ConfigureDirectScanout { enabled: bool },
|
||||||
|
ConfigureDrmDevice { dev: ConfigDrmDevice },
|
||||||
|
ConfigureIdle { idle: Duration },
|
||||||
|
ConfigureInput { input: Input },
|
||||||
|
ConfigureOutput { out: Output },
|
||||||
|
Exec { exec: Exec },
|
||||||
|
Multi { actions: Vec<Action> },
|
||||||
|
SetEnv { env: Vec<(String, String)> },
|
||||||
|
SetGfxApi { api: GfxApi },
|
||||||
|
SetKeymap { map: ConfigKeymap },
|
||||||
|
SetLogLevel { level: LogLevel },
|
||||||
|
SetRenderDevice { dev: DrmDeviceMatch },
|
||||||
|
SetStatus { status: Option<Status> },
|
||||||
|
SetTheme { theme: Box<Theme> },
|
||||||
|
ShowWorkspace { name: String },
|
||||||
|
SimpleCommand { cmd: SimpleCommand },
|
||||||
|
SwitchToVt { num: u32 },
|
||||||
|
UnsetEnv { env: Vec<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Theme {
|
||||||
|
pub attention_requested_bg_color: Option<Color>,
|
||||||
|
pub bg_color: Option<Color>,
|
||||||
|
pub bar_bg_color: Option<Color>,
|
||||||
|
pub bar_status_text_color: Option<Color>,
|
||||||
|
pub border_color: Option<Color>,
|
||||||
|
pub captured_focused_title_bg_color: Option<Color>,
|
||||||
|
pub captured_unfocused_title_bg_color: Option<Color>,
|
||||||
|
pub focused_inactive_title_bg_color: Option<Color>,
|
||||||
|
pub focused_inactive_title_text_color: Option<Color>,
|
||||||
|
pub focused_title_bg_color: Option<Color>,
|
||||||
|
pub focused_title_text_color: Option<Color>,
|
||||||
|
pub separator_color: Option<Color>,
|
||||||
|
pub unfocused_title_bg_color: Option<Color>,
|
||||||
|
pub unfocused_title_text_color: Option<Color>,
|
||||||
|
pub border_width: Option<i32>,
|
||||||
|
pub title_height: Option<i32>,
|
||||||
|
pub font: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Status {
|
||||||
|
pub format: MessageFormat,
|
||||||
|
pub exec: Exec,
|
||||||
|
pub separator: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum OutputMatch {
|
||||||
|
Any(Vec<OutputMatch>),
|
||||||
|
All {
|
||||||
|
name: Option<String>,
|
||||||
|
connector: Option<String>,
|
||||||
|
serial_number: Option<String>,
|
||||||
|
manufacturer: Option<String>,
|
||||||
|
model: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DrmDeviceMatch {
|
||||||
|
Any(Vec<DrmDeviceMatch>),
|
||||||
|
All {
|
||||||
|
name: Option<String>,
|
||||||
|
syspath: Option<String>,
|
||||||
|
vendor: Option<u32>,
|
||||||
|
vendor_name: Option<String>,
|
||||||
|
model: Option<u32>,
|
||||||
|
model_name: Option<String>,
|
||||||
|
devnode: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Mode {
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
|
pub refresh_rate: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Mode {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} x {}", self.width, self.height)?;
|
||||||
|
if let Some(rr) = self.refresh_rate {
|
||||||
|
write!(f, " @ {}", rr)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Output {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub match_: OutputMatch,
|
||||||
|
pub x: Option<i32>,
|
||||||
|
pub y: Option<i32>,
|
||||||
|
pub scale: Option<f64>,
|
||||||
|
pub transform: Option<Transform>,
|
||||||
|
pub mode: Option<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ConnectorMatch {
|
||||||
|
Any(Vec<ConnectorMatch>),
|
||||||
|
All { connector: Option<String> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InputMatch {
|
||||||
|
Any(Vec<InputMatch>),
|
||||||
|
All {
|
||||||
|
tag: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
syspath: Option<String>,
|
||||||
|
devnode: Option<String>,
|
||||||
|
is_keyboard: Option<bool>,
|
||||||
|
is_pointer: Option<bool>,
|
||||||
|
is_touch: Option<bool>,
|
||||||
|
is_tablet_tool: Option<bool>,
|
||||||
|
is_tablet_pad: Option<bool>,
|
||||||
|
is_gesture: Option<bool>,
|
||||||
|
is_switch: Option<bool>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Input {
|
||||||
|
pub tag: Option<String>,
|
||||||
|
pub match_: InputMatch,
|
||||||
|
pub accel_profile: Option<AccelProfile>,
|
||||||
|
pub accel_speed: Option<f64>,
|
||||||
|
pub tap_enabled: Option<bool>,
|
||||||
|
pub tap_drag_enabled: Option<bool>,
|
||||||
|
pub tap_drag_lock_enabled: Option<bool>,
|
||||||
|
pub left_handed: Option<bool>,
|
||||||
|
pub natural_scrolling: Option<bool>,
|
||||||
|
pub px_per_wheel_scroll: Option<f64>,
|
||||||
|
pub transform_matrix: Option<[[f64; 2]; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Exec {
|
||||||
|
pub prog: String,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub envs: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConfigConnector {
|
||||||
|
pub match_: ConnectorMatch,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConfigDrmDevice {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub match_: DrmDeviceMatch,
|
||||||
|
pub gfx_api: Option<GfxApi>,
|
||||||
|
pub direct_scanout_enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ConfigKeymap {
|
||||||
|
Named(String),
|
||||||
|
Literal(Keymap),
|
||||||
|
Defined { name: String, map: Keymap },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub keymap: Option<ConfigKeymap>,
|
||||||
|
pub shortcuts: Vec<(ModifiedKeySym, Action)>,
|
||||||
|
pub on_graphics_initialized: Option<Action>,
|
||||||
|
pub on_idle: Option<Action>,
|
||||||
|
pub status: Option<Status>,
|
||||||
|
pub connectors: Vec<ConfigConnector>,
|
||||||
|
pub outputs: Vec<Output>,
|
||||||
|
pub workspace_capture: bool,
|
||||||
|
pub env: Vec<(String, String)>,
|
||||||
|
pub on_startup: Option<Action>,
|
||||||
|
pub keymaps: Vec<ConfigKeymap>,
|
||||||
|
pub log_level: Option<LogLevel>,
|
||||||
|
pub theme: Theme,
|
||||||
|
pub gfx_api: Option<GfxApi>,
|
||||||
|
pub direct_scanout_enabled: Option<bool>,
|
||||||
|
pub drm_devices: Vec<ConfigDrmDevice>,
|
||||||
|
pub render_device: Option<DrmDeviceMatch>,
|
||||||
|
pub inputs: Vec<Input>,
|
||||||
|
pub idle: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("Could not parse the toml document")]
|
||||||
|
Toml(#[from] toml_parser::ParserError),
|
||||||
|
#[error("Could not interpret the toml as a config document")]
|
||||||
|
Parser(#[from] ConfigParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_config<F>(input: &[u8], handle_error: F) -> Option<Config>
|
||||||
|
where
|
||||||
|
F: FnOnce(&dyn Error),
|
||||||
|
{
|
||||||
|
let cx = Context {
|
||||||
|
input,
|
||||||
|
used: Default::default(),
|
||||||
|
};
|
||||||
|
macro_rules! fatal {
|
||||||
|
($e:expr) => {{
|
||||||
|
let e = ConfigError::from($e.value);
|
||||||
|
let e = cx.error2($e.span, e);
|
||||||
|
handle_error(&e);
|
||||||
|
return None;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
let toml = match toml_parser::parse(input, &cx) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => fatal!(e),
|
||||||
|
};
|
||||||
|
let config = match toml.parse(&mut ConfigParser(&cx)) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => fatal!(e),
|
||||||
|
};
|
||||||
|
let used = cx.used.take();
|
||||||
|
macro_rules! check_defined {
|
||||||
|
($name:expr, $used:ident, $defined:ident) => {
|
||||||
|
for spanned in &used.$used {
|
||||||
|
if !used.$defined.contains(spanned) {
|
||||||
|
log::warn!(
|
||||||
|
"{} {} used but not defined: {}",
|
||||||
|
$name,
|
||||||
|
spanned.value,
|
||||||
|
cx.error3(spanned.span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
check_defined!("Keymap", keymaps, defined_keymaps);
|
||||||
|
check_defined!("DRM device", drm_devices, defined_drm_devices);
|
||||||
|
check_defined!("Output", outputs, defined_outputs);
|
||||||
|
check_defined!("Input", inputs, defined_inputs);
|
||||||
|
Some(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_config_parses() {
|
||||||
|
let input = include_bytes!("default-config.toml");
|
||||||
|
parse_config(input, |_| ()).unwrap();
|
||||||
|
}
|
||||||
65
toml-config/src/config/context.rs
Normal file
65
toml-config/src/config/context.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::error::SpannedError,
|
||||||
|
toml::{
|
||||||
|
toml_parser::{ErrorHandler, ParserError},
|
||||||
|
toml_span::{Span, Spanned},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ahash::AHashSet,
|
||||||
|
error_reporter::Report,
|
||||||
|
std::{cell::RefCell, convert::Infallible, error::Error},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Context<'a> {
|
||||||
|
pub input: &'a [u8],
|
||||||
|
pub used: RefCell<Used>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Used {
|
||||||
|
pub outputs: Vec<Spanned<String>>,
|
||||||
|
pub inputs: Vec<Spanned<String>>,
|
||||||
|
pub drm_devices: Vec<Spanned<String>>,
|
||||||
|
pub keymaps: Vec<Spanned<String>>,
|
||||||
|
pub defined_outputs: AHashSet<Spanned<String>>,
|
||||||
|
pub defined_inputs: AHashSet<Spanned<String>>,
|
||||||
|
pub defined_drm_devices: AHashSet<Spanned<String>>,
|
||||||
|
pub defined_keymaps: AHashSet<Spanned<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Context<'a> {
|
||||||
|
pub fn error<E: Error>(&self, cause: Spanned<E>) -> SpannedError<'a, E> {
|
||||||
|
self.error2(cause.span, cause.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error2<E: Error>(&self, span: Span, cause: E) -> SpannedError<'a, E> {
|
||||||
|
SpannedError {
|
||||||
|
input: self.input.into(),
|
||||||
|
span,
|
||||||
|
cause: Some(cause),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error3(&self, span: Span) -> SpannedError<'a, Infallible> {
|
||||||
|
SpannedError {
|
||||||
|
input: self.input.into(),
|
||||||
|
span,
|
||||||
|
cause: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ErrorHandler for Context<'a> {
|
||||||
|
fn handle(&self, err: Spanned<ParserError>) {
|
||||||
|
log::warn!("{}", Report::new(self.error(err)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
|
||||||
|
log::warn!("{}", Report::new(self.error(err)));
|
||||||
|
log::info!(
|
||||||
|
"Previous definition here: {}",
|
||||||
|
Report::new(self.error3(prev))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
toml-config/src/config/error.rs
Normal file
89
toml-config/src/config/error.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
use {
|
||||||
|
crate::toml::toml_span::Span,
|
||||||
|
bstr::ByteSlice,
|
||||||
|
error_reporter::Report,
|
||||||
|
std::{
|
||||||
|
borrow::Cow,
|
||||||
|
error::Error,
|
||||||
|
fmt::{Display, Formatter},
|
||||||
|
ops::Deref,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SpannedError<'a, E> {
|
||||||
|
pub input: Cow<'a, [u8]>,
|
||||||
|
pub span: Span,
|
||||||
|
pub cause: Option<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, E: Error> Display for SpannedError<'a, E> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let original = self.input.deref();
|
||||||
|
let span = self.span;
|
||||||
|
|
||||||
|
let (line, column) = translate_position(original, span.lo);
|
||||||
|
let line_num = line + 1;
|
||||||
|
let col_num = column + 1;
|
||||||
|
let gutter = line_num.to_string().len();
|
||||||
|
let content = original
|
||||||
|
.split(|c| *c == b'\n')
|
||||||
|
.nth(line)
|
||||||
|
.expect("valid line number");
|
||||||
|
|
||||||
|
if let Some(cause) = &self.cause {
|
||||||
|
write!(f, "{}: ", Report::new(cause))?;
|
||||||
|
}
|
||||||
|
writeln!(f, "At line {}, column {}:", line_num, col_num)?;
|
||||||
|
for _ in 0..=gutter {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
writeln!(f, "|")?;
|
||||||
|
write!(f, "{} | ", line_num)?;
|
||||||
|
writeln!(f, "{}", content.as_bstr())?;
|
||||||
|
for _ in 0..=gutter {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "|")?;
|
||||||
|
for _ in 0..=column {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "^")?;
|
||||||
|
for _ in (span.lo + 1)..(span.hi.min(span.lo + content.len() - column)) {
|
||||||
|
write!(f, "^")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, E: Error> Error for SpannedError<'a, E> {}
|
||||||
|
|
||||||
|
fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
|
||||||
|
if input.is_empty() {
|
||||||
|
return (0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
let safe_index = index.min(input.len() - 1);
|
||||||
|
let column_offset = index - safe_index;
|
||||||
|
let index = safe_index;
|
||||||
|
|
||||||
|
let nl = input[0..index]
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, b)| **b == b'\n')
|
||||||
|
.map(|(nl, _)| index - nl - 1);
|
||||||
|
let line_start = match nl {
|
||||||
|
Some(nl) => nl + 1,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
|
||||||
|
|
||||||
|
let column = std::str::from_utf8(&input[line_start..=index])
|
||||||
|
.map(|s| s.chars().count() - 1)
|
||||||
|
.unwrap_or_else(|_| index - line_start);
|
||||||
|
let column = column + column_offset;
|
||||||
|
|
||||||
|
(line, column)
|
||||||
|
}
|
||||||
281
toml-config/src/config/extractor.rs
Normal file
281
toml-config/src/config/extractor.rs
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::context::Context,
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ahash::AHashSet,
|
||||||
|
error_reporter::Report,
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Extractor<'v> {
|
||||||
|
cx: &'v Context<'v>,
|
||||||
|
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
used: Vec<&'static str>,
|
||||||
|
log_unused: bool,
|
||||||
|
span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> Extractor<'v> {
|
||||||
|
pub fn new(
|
||||||
|
cx: &'v Context<'v>,
|
||||||
|
span: Span,
|
||||||
|
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
cx,
|
||||||
|
table,
|
||||||
|
used: Default::default(),
|
||||||
|
log_unused: true,
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&mut self, name: &'static str) -> Option<&'v Spanned<Value>> {
|
||||||
|
let v = self.table.get(name);
|
||||||
|
if v.is_some() {
|
||||||
|
self.used.push(name);
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ignore_unused(&mut self) {
|
||||||
|
self.log_unused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract<E: Extractable<'v>, U>(&mut self, e: E) -> Result<E::Output, Spanned<U>>
|
||||||
|
where
|
||||||
|
ExtractorError: Into<U>,
|
||||||
|
{
|
||||||
|
e.extract(self).map_err(|e| e.map(|e| e.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_or_ignore<E: Extractable<'v>, U>(
|
||||||
|
&mut self,
|
||||||
|
e: E,
|
||||||
|
) -> Result<E::Output, Spanned<U>>
|
||||||
|
where
|
||||||
|
ExtractorError: Into<U>,
|
||||||
|
{
|
||||||
|
let res = self.extract(e);
|
||||||
|
if res.is_err() {
|
||||||
|
self.ignore_unused();
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> Drop for Extractor<'v> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.log_unused {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.used.len() == self.table.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let used: AHashSet<_> = self.used.iter().copied().collect();
|
||||||
|
for key in self.table.keys() {
|
||||||
|
if !used.contains(key.value.as_str()) {
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("Ignoring unknown key {0}")]
|
||||||
|
struct Err<'a>(&'a str);
|
||||||
|
let err = self.cx.error2(key.span, Err(&key.value));
|
||||||
|
log::warn!("{}", Report::new(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Eq, PartialEq)]
|
||||||
|
pub enum ExtractorError {
|
||||||
|
#[error("Missing field {0}")]
|
||||||
|
MissingField(&'static str),
|
||||||
|
#[error("Expected {0} but found {1}")]
|
||||||
|
Expected(&'static str, &'static str),
|
||||||
|
#[error("Value must fit in a u32")]
|
||||||
|
U32,
|
||||||
|
#[error("Value must fit in a u64")]
|
||||||
|
U64,
|
||||||
|
#[error("Value must fit in a i32")]
|
||||||
|
I32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Extractable<'v> {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn extract(
|
||||||
|
self,
|
||||||
|
extractor: &mut Extractor<'v>,
|
||||||
|
) -> Result<Self::Output, Spanned<ExtractorError>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v, T, F> Extractable<'v> for F
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Extractor<'v>) -> Result<T, Spanned<ExtractorError>>,
|
||||||
|
{
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn extract(
|
||||||
|
self,
|
||||||
|
extractor: &mut Extractor<'v>,
|
||||||
|
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||||
|
self(extractor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn val(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<&'v Value>, Spanned<ExtractorError>>
|
||||||
|
{
|
||||||
|
move |extractor: &mut Extractor| match extractor.get(name) {
|
||||||
|
None => Err(ExtractorError::MissingField(name).spanned(extractor.span)),
|
||||||
|
Some(v) => Ok(v.as_ref()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ty {
|
||||||
|
($f:ident, $lt:lifetime, $ty:ident, $ret:ty, $v:ident, $map:expr, $name:expr) => {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn $f(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl for<$lt> FnOnce(&mut Extractor<$lt>) -> Result<Spanned<$ret>, Spanned<ExtractorError>> {
|
||||||
|
move |extractor: &mut Extractor| {
|
||||||
|
val(name)(extractor).and_then(|v| match v.value {
|
||||||
|
Value::$ty($v) => Ok($map.spanned(v.span)),
|
||||||
|
_ => Err(ExtractorError::Expected($name, v.value.name()).spanned(v.span)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ty!(str, 'a, String, &'a str, v, v.as_str(), "a string");
|
||||||
|
ty!(int, 'a, Integer, i64, v, *v, "an integer");
|
||||||
|
ty!(flt, 'a, Float, f64, v, *v, "a float");
|
||||||
|
ty!(bol, 'a, Boolean, bool, v, *v, "a boolean");
|
||||||
|
ty!(arr, 'a, Array, &'a [Spanned<Value>], v, &**v, "an array");
|
||||||
|
ty!(tbl, 'a, Table, &'a IndexMap<Spanned<String>, Spanned<Value>>, v, v, "a table");
|
||||||
|
|
||||||
|
pub fn fltorint(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl for<'a> FnOnce(&mut Extractor<'a>) -> Result<Spanned<f64>, Spanned<ExtractorError>> {
|
||||||
|
move |extractor: &mut Extractor| {
|
||||||
|
val(name)(extractor).and_then(|v| match *v.value {
|
||||||
|
Value::Float(f) => Ok(f.spanned(v.span)),
|
||||||
|
Value::Integer(i) => Ok((i as f64).spanned(v.span)),
|
||||||
|
_ => Err(
|
||||||
|
ExtractorError::Expected("a float or an integer", v.value.name()).spanned(v.span),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! int {
|
||||||
|
($f:ident, $ty:ident, $err:ident) => {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn $f(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<$ty>, Spanned<ExtractorError>> {
|
||||||
|
move |extractor: &mut Extractor| {
|
||||||
|
int(name)(extractor).and_then(|v| {
|
||||||
|
v.value
|
||||||
|
.try_into()
|
||||||
|
.map(|n: $ty| n.spanned(v.span))
|
||||||
|
.map_err(|_| ExtractorError::$err.spanned(v.span))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
int!(n32, u32, U32);
|
||||||
|
int!(n64, u64, U64);
|
||||||
|
int!(s32, i32, I32);
|
||||||
|
|
||||||
|
pub fn recover<F>(f: F) -> Recover<F> {
|
||||||
|
Recover(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Recover<E>(E);
|
||||||
|
|
||||||
|
impl<'v, E> Extractable<'v> for Recover<E>
|
||||||
|
where
|
||||||
|
E: Extractable<'v>,
|
||||||
|
<E as Extractable<'v>>::Output: Default,
|
||||||
|
{
|
||||||
|
type Output = <E as Extractable<'v>>::Output;
|
||||||
|
|
||||||
|
fn extract(
|
||||||
|
self,
|
||||||
|
extractor: &mut Extractor<'v>,
|
||||||
|
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||||
|
match self.0.extract(extractor) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("{}", extractor.cx.error(e));
|
||||||
|
Ok(<E as Extractable<'v>>::Output::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opt<F>(f: F) -> Opt<F> {
|
||||||
|
Opt(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Opt<E>(E);
|
||||||
|
|
||||||
|
impl<'v, E> Extractable<'v> for Opt<E>
|
||||||
|
where
|
||||||
|
E: Extractable<'v>,
|
||||||
|
{
|
||||||
|
type Output = Option<<E as Extractable<'v>>::Output>;
|
||||||
|
|
||||||
|
fn extract(
|
||||||
|
self,
|
||||||
|
extractor: &mut Extractor<'v>,
|
||||||
|
) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||||
|
match self.0.extract(extractor) {
|
||||||
|
Ok(v) => Ok(Some(v)),
|
||||||
|
Err(e) if matches!(e.value, ExtractorError::MissingField(_)) => Ok(None),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tuples {
|
||||||
|
($($idx:tt: $name:ident,)*) => {
|
||||||
|
impl<'v, $($name,)*> Extractable<'v> for ($($name,)*)
|
||||||
|
where $($name: Extractable<'v>,)*
|
||||||
|
{
|
||||||
|
type Output = ($($name::Output,)*);
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn extract(self, extractor: &mut Extractor<'v>) -> Result<Self::Output, Spanned<ExtractorError>> {
|
||||||
|
$(
|
||||||
|
let $name = self.$idx.extract(extractor);
|
||||||
|
)*
|
||||||
|
Ok((
|
||||||
|
$(
|
||||||
|
$name?,
|
||||||
|
)*
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tuples!(0:T0,);
|
||||||
|
tuples!(0:T0,1:T1,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,);
|
||||||
|
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9,);
|
||||||
2580
toml-config/src/config/keysyms.rs
Normal file
2580
toml-config/src/config/keysyms.rs
Normal file
File diff suppressed because it is too large
Load diff
118
toml-config/src/config/parser.rs
Normal file
118
toml-config/src/config/parser.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
use {
|
||||||
|
crate::toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
std::{
|
||||||
|
error::Error,
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum DataType {
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
Float,
|
||||||
|
Boolean,
|
||||||
|
Array,
|
||||||
|
Table,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DataType {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
DataType::String => "a string",
|
||||||
|
DataType::Integer => "an integer",
|
||||||
|
DataType::Float => "a float",
|
||||||
|
DataType::Boolean => "a bool",
|
||||||
|
DataType::Array => "an array",
|
||||||
|
DataType::Table => "a table",
|
||||||
|
};
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DataTypes(&'static [DataType]);
|
||||||
|
|
||||||
|
impl Display for DataTypes {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
let d = self.0;
|
||||||
|
match d.len() {
|
||||||
|
0 => Ok(()),
|
||||||
|
1 => d[0].fmt(f),
|
||||||
|
2 => write!(f, "{} or {}", d[0], d[1]),
|
||||||
|
_ => {
|
||||||
|
let mut first = true;
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0..d.len() - 1 {
|
||||||
|
if !first {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
d[i].fmt(f)?;
|
||||||
|
}
|
||||||
|
write!(f, ", or {}", d[d.len() - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UnexpectedDataType(&'static [DataType], DataType);
|
||||||
|
|
||||||
|
impl Display for UnexpectedDataType {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Expected {} but found {}", DataTypes(self.0), self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for UnexpectedDataType {}
|
||||||
|
|
||||||
|
pub type ParseResult<P> = Result<<P as Parser>::Value, Spanned<<P as Parser>::Error>>;
|
||||||
|
|
||||||
|
pub trait Parser {
|
||||||
|
type Value;
|
||||||
|
type Error: From<UnexpectedDataType>;
|
||||||
|
|
||||||
|
const EXPECTED: &'static [DataType];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
let _ = string;
|
||||||
|
expected(self, span, DataType::String)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult<Self> {
|
||||||
|
let _ = integer;
|
||||||
|
expected(self, span, DataType::Integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_float(&mut self, span: Span, float: f64) -> ParseResult<Self> {
|
||||||
|
let _ = float;
|
||||||
|
expected(self, span, DataType::Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult<Self> {
|
||||||
|
let _ = bool;
|
||||||
|
expected(self, span, DataType::Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let _ = array;
|
||||||
|
expected(self, span, DataType::Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let _ = table;
|
||||||
|
expected(self, span, DataType::Table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected<P: Parser + ?Sized>(_p: &P, span: Span, actual: DataType) -> ParseResult<P> {
|
||||||
|
Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span))
|
||||||
|
}
|
||||||
48
toml-config/src/config/parsers.rs
Normal file
48
toml-config/src/config/parsers.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
toml::toml_span::Span,
|
||||||
|
},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod action;
|
||||||
|
mod color;
|
||||||
|
pub mod config;
|
||||||
|
mod connector;
|
||||||
|
mod connector_match;
|
||||||
|
mod drm_device;
|
||||||
|
mod drm_device_match;
|
||||||
|
mod env;
|
||||||
|
pub mod exec;
|
||||||
|
mod gfx_api;
|
||||||
|
mod idle;
|
||||||
|
mod input;
|
||||||
|
mod input_match;
|
||||||
|
pub mod keymap;
|
||||||
|
mod log_level;
|
||||||
|
mod mode;
|
||||||
|
pub mod modified_keysym;
|
||||||
|
mod output;
|
||||||
|
mod output_match;
|
||||||
|
pub mod shortcuts;
|
||||||
|
mod status;
|
||||||
|
mod theme;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum StringParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StringParser;
|
||||||
|
|
||||||
|
impl Parser for StringParser {
|
||||||
|
type Value = String;
|
||||||
|
type Error = StringParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
Ok(string.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
308
toml-config/src/config/parsers/action.rs
Normal file
308
toml-config/src/config/parsers/action.rs
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{arr, bol, n32, opt, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{
|
||||||
|
connector::{ConnectorParser, ConnectorParserError},
|
||||||
|
drm_device::{DrmDeviceParser, DrmDeviceParserError},
|
||||||
|
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
|
||||||
|
env::{EnvParser, EnvParserError},
|
||||||
|
exec::{ExecParser, ExecParserError},
|
||||||
|
gfx_api::{GfxApiParser, GfxApiParserError},
|
||||||
|
idle::{IdleParser, IdleParserError},
|
||||||
|
input::{InputParser, InputParserError},
|
||||||
|
keymap::{KeymapParser, KeymapParserError},
|
||||||
|
log_level::{LogLevelParser, LogLevelParserError},
|
||||||
|
output::{OutputParser, OutputParserError},
|
||||||
|
status::{StatusParser, StatusParserError},
|
||||||
|
theme::{ThemeParser, ThemeParserError},
|
||||||
|
StringParser, StringParserError,
|
||||||
|
},
|
||||||
|
Action,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::Axis::{Horizontal, Vertical},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ActionParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
StringParser(#[from] StringParserError),
|
||||||
|
#[error("Unknown type {0}")]
|
||||||
|
UnknownType(String),
|
||||||
|
#[error("Unknown simple action {0}")]
|
||||||
|
UnknownSimpleAction(String),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error("Could not parse the exec action")]
|
||||||
|
Exec(#[from] ExecParserError),
|
||||||
|
#[error("Could not parse the configure-connector action")]
|
||||||
|
ConfigureConnector(#[from] ConnectorParserError),
|
||||||
|
#[error("Could not parse the configure-input action")]
|
||||||
|
ConfigureInput(#[from] InputParserError),
|
||||||
|
#[error("Could not parse the configure-output action")]
|
||||||
|
ConfigureOutput(#[from] OutputParserError),
|
||||||
|
#[error("Could not parse the environment variables")]
|
||||||
|
Env(#[from] EnvParserError),
|
||||||
|
#[error("Could not parse a set-keymap action")]
|
||||||
|
SetKeymap(#[from] KeymapParserError),
|
||||||
|
#[error("Could not parse a set-status action")]
|
||||||
|
Status(#[from] StatusParserError),
|
||||||
|
#[error("Could not parse a set-theme action")]
|
||||||
|
Theme(#[from] ThemeParserError),
|
||||||
|
#[error("Could not parse a set-log-level action")]
|
||||||
|
SetLogLevel(#[from] LogLevelParserError),
|
||||||
|
#[error("Could not parse a set-gfx-api action")]
|
||||||
|
GfxApi(#[from] GfxApiParserError),
|
||||||
|
#[error("Could not parse a configure-drm-device action")]
|
||||||
|
DrmDevice(#[from] DrmDeviceParserError),
|
||||||
|
#[error("Could not parse a set-render-device action")]
|
||||||
|
SetRenderDevice(#[from] DrmDeviceMatchParserError),
|
||||||
|
#[error("Could not parse a configure-idle action")]
|
||||||
|
ConfigureIdle(#[from] IdleParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActionParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl ActionParser<'_> {
|
||||||
|
fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
use {crate::config::SimpleCommand::*, jay_config::Direction::*};
|
||||||
|
let cmd = match string {
|
||||||
|
"focus-left" => Focus(Left),
|
||||||
|
"focus-down" => Focus(Down),
|
||||||
|
"focus-up" => Focus(Up),
|
||||||
|
"focus-right" => Focus(Right),
|
||||||
|
"move-left" => Move(Left),
|
||||||
|
"move-down" => Move(Down),
|
||||||
|
"move-up" => Move(Up),
|
||||||
|
"move-right" => Move(Right),
|
||||||
|
"split-horizontal" => Split(Horizontal),
|
||||||
|
"split-vertical" => Split(Vertical),
|
||||||
|
"toggle-split" => ToggleSplit,
|
||||||
|
"toggle-mono" => ToggleMono,
|
||||||
|
"toggle-fullscreen" => ToggleFullscreen,
|
||||||
|
"focus-parent" => FocusParent,
|
||||||
|
"close" => Close,
|
||||||
|
"disable-pointer-constraint" => DisablePointerConstraint,
|
||||||
|
"toggle-floating" => ToggleFloating,
|
||||||
|
"quit" => Quit,
|
||||||
|
"reload-config-toml" => ReloadConfigToml,
|
||||||
|
"reload-config-so" => ReloadConfigSo,
|
||||||
|
"none" => None,
|
||||||
|
_ => {
|
||||||
|
return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Action::SimpleCommand { cmd })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_multi(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut actions = vec![];
|
||||||
|
for el in array {
|
||||||
|
actions.push(el.parse(self)?);
|
||||||
|
}
|
||||||
|
Ok(Action::Multi { actions })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let exec = ext
|
||||||
|
.extract(val("exec"))?
|
||||||
|
.parse_map(&mut ExecParser(self.0))?;
|
||||||
|
Ok(Action::Exec { exec })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_switch_to_vt(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let num = ext.extract(n32("num"))?.value;
|
||||||
|
Ok(Action::SwitchToVt { num })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let name = ext.extract(str("name"))?.value.to_string();
|
||||||
|
Ok(Action::ShowWorkspace { name })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let name = ext.extract(str("name"))?.value.to_string();
|
||||||
|
Ok(Action::ShowWorkspace { name })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let con = ext
|
||||||
|
.extract(val("connector"))?
|
||||||
|
.parse_map(&mut ConnectorParser(self.0))?;
|
||||||
|
Ok(Action::ConfigureConnector { con })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let input = ext.extract(val("input"))?.parse_map(&mut InputParser {
|
||||||
|
cx: self.0,
|
||||||
|
tag_ok: false,
|
||||||
|
})?;
|
||||||
|
Ok(Action::ConfigureInput { input })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let idle = ext
|
||||||
|
.extract(val("idle"))?
|
||||||
|
.parse_map(&mut IdleParser(self.0))?;
|
||||||
|
Ok(Action::ConfigureIdle { idle })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let out = ext.extract(val("output"))?.parse_map(&mut OutputParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: false,
|
||||||
|
})?;
|
||||||
|
Ok(Action::ConfigureOutput { out })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?;
|
||||||
|
Ok(Action::SetEnv { env })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
struct P;
|
||||||
|
impl Parser for P {
|
||||||
|
type Value = Vec<String>;
|
||||||
|
type Error = ActionParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
Ok(vec![string.to_string()])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for v in array {
|
||||||
|
res.push(v.parse_map(&mut StringParser)?);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let env = ext.extract(val("env"))?.parse_map(&mut P)?;
|
||||||
|
Ok(Action::UnsetEnv { env })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let map = ext.extract(val("map"))?.parse_map(&mut KeymapParser {
|
||||||
|
cx: self.0,
|
||||||
|
definition: false,
|
||||||
|
})?;
|
||||||
|
Ok(Action::SetKeymap { map })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let status = match ext.extract(opt(val("status")))? {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(v.parse_map(&mut StatusParser(self.0))?),
|
||||||
|
};
|
||||||
|
Ok(Action::SetStatus { status })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let theme = ext
|
||||||
|
.extract(val("theme"))?
|
||||||
|
.parse_map(&mut ThemeParser(self.0))?;
|
||||||
|
Ok(Action::SetTheme {
|
||||||
|
theme: Box::new(theme),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let level = ext.extract(val("level"))?.parse_map(&mut LogLevelParser)?;
|
||||||
|
Ok(Action::SetLogLevel { level })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let api = ext.extract(val("api"))?.parse_map(&mut GfxApiParser)?;
|
||||||
|
Ok(Action::SetGfxApi { api })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let dev = ext
|
||||||
|
.extract(val("dev"))?
|
||||||
|
.parse_map(&mut DrmDeviceMatchParser(self.0))?;
|
||||||
|
Ok(Action::SetRenderDevice { dev })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_direct_scanout(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let enabled = ext.extract(bol("enabled"))?.value;
|
||||||
|
Ok(Action::ConfigureDirectScanout { enabled })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let dev = ext.extract(val("dev"))?.parse_map(&mut DrmDeviceParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: false,
|
||||||
|
})?;
|
||||||
|
Ok(Action::ConfigureDrmDevice { dev })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser for ActionParser<'a> {
|
||||||
|
type Value = Action;
|
||||||
|
type Error = ActionParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
self.parse_simple_cmd(span, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
self.parse_multi(span, array)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let ty = ext.extract_or_ignore(str("type"))?;
|
||||||
|
let res = match ty.value {
|
||||||
|
"simple" => {
|
||||||
|
let cmd = ext.extract(str("cmd"))?;
|
||||||
|
self.parse_simple_cmd(cmd.span, cmd.value)
|
||||||
|
}
|
||||||
|
"multi" => {
|
||||||
|
let actions = ext.extract(arr("actions"))?;
|
||||||
|
self.parse_multi(actions.span, actions.value)
|
||||||
|
}
|
||||||
|
"exec" => self.parse_exec(&mut ext),
|
||||||
|
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
|
||||||
|
"show-workspace" => self.parse_show_workspace(&mut ext),
|
||||||
|
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
|
||||||
|
"configure-connector" => self.parse_configure_connector(&mut ext),
|
||||||
|
"configure-input" => self.parse_configure_input(&mut ext),
|
||||||
|
"configure-output" => self.parse_configure_output(&mut ext),
|
||||||
|
"set-env" => self.parse_set_env(&mut ext),
|
||||||
|
"unset-env" => self.parse_unset_env(&mut ext),
|
||||||
|
"set-keymap" => self.parse_set_keymap(&mut ext),
|
||||||
|
"set-status" => self.parse_set_status(&mut ext),
|
||||||
|
"set-theme" => self.parse_set_theme(&mut ext),
|
||||||
|
"set-log-level" => self.parse_set_log_level(&mut ext),
|
||||||
|
"set-gfx-api" => self.parse_set_gfx_api(&mut ext),
|
||||||
|
"configure-direct-scanout" => self.parse_configure_direct_scanout(&mut ext),
|
||||||
|
"configure-drm-device" => self.parse_configure_drm_device(&mut ext),
|
||||||
|
"set-render-device" => self.parse_set_render_device(&mut ext),
|
||||||
|
"configure-idle" => self.parse_configure_idle(&mut ext),
|
||||||
|
v => {
|
||||||
|
ext.ignore_unused();
|
||||||
|
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(ext);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
58
toml-config/src/config/parsers/color.rs
Normal file
58
toml-config/src/config/parsers/color.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::ExtractorError,
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
},
|
||||||
|
toml::toml_span::{Span, SpannedExt},
|
||||||
|
},
|
||||||
|
jay_config::theme::Color,
|
||||||
|
std::{num::ParseIntError, ops::Range},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ColorParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ColorParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
DataType(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extractor(#[from] ExtractorError),
|
||||||
|
#[error("Color must start with `#`")]
|
||||||
|
Prefix,
|
||||||
|
#[error("String must have length 4, 5, 6, or 9")]
|
||||||
|
Length,
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseIntError(#[from] ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for ColorParser<'_> {
|
||||||
|
type Value = Color;
|
||||||
|
type Error = ColorParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
let hex = match string.strip_prefix("#") {
|
||||||
|
Some(s) => s,
|
||||||
|
_ => return Err(ColorParserError::Prefix.spanned(span)),
|
||||||
|
};
|
||||||
|
let d = |range: Range<usize>| {
|
||||||
|
u8::from_str_radix(&hex[range], 16)
|
||||||
|
.map_err(|e| ColorParserError::ParseIntError(e).spanned(span))
|
||||||
|
};
|
||||||
|
let s = |range: Range<usize>| {
|
||||||
|
let v = d(range)?;
|
||||||
|
Ok((v << 4) | v)
|
||||||
|
};
|
||||||
|
let (r, g, b, a) = match hex.len() {
|
||||||
|
3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX),
|
||||||
|
4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?),
|
||||||
|
6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX),
|
||||||
|
8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?),
|
||||||
|
_ => return Err(ColorParserError::Length.spanned(span)),
|
||||||
|
};
|
||||||
|
Ok(Color::new_straight(r, g, b, a))
|
||||||
|
}
|
||||||
|
}
|
||||||
279
toml-config/src/config/parsers/config.rs
Normal file
279
toml-config/src/config/parsers/config.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{
|
||||||
|
action::ActionParser,
|
||||||
|
connector::ConnectorsParser,
|
||||||
|
drm_device::DrmDevicesParser,
|
||||||
|
drm_device_match::DrmDeviceMatchParser,
|
||||||
|
env::EnvParser,
|
||||||
|
gfx_api::GfxApiParser,
|
||||||
|
idle::IdleParser,
|
||||||
|
input::InputsParser,
|
||||||
|
keymap::KeymapParser,
|
||||||
|
log_level::LogLevelParser,
|
||||||
|
output::OutputsParser,
|
||||||
|
shortcuts::{ShortcutsParser, ShortcutsParserError},
|
||||||
|
status::StatusParser,
|
||||||
|
theme::ThemeParser,
|
||||||
|
},
|
||||||
|
spanned::SpannedErrorExt,
|
||||||
|
Action, Config, Theme,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConfigParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extractor(#[from] ExtractorError),
|
||||||
|
#[error("Could not parse the shortcuts")]
|
||||||
|
ParseShortcuts(#[source] ShortcutsParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConfigParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl ConfigParser<'_> {
|
||||||
|
fn parse_action(&self, name: &str, action: Option<Spanned<&Value>>) -> Option<Action> {
|
||||||
|
match action {
|
||||||
|
None => None,
|
||||||
|
Some(value) => match value.parse(&mut ActionParser(self.0)) {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the {name} action: {}", self.0.error(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for ConfigParser<'_> {
|
||||||
|
type Value = Config;
|
||||||
|
type Error = ConfigParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (
|
||||||
|
(
|
||||||
|
keymap_val,
|
||||||
|
shortcuts_val,
|
||||||
|
on_graphics_init_val,
|
||||||
|
status_val,
|
||||||
|
outputs_val,
|
||||||
|
connectors_val,
|
||||||
|
workspace_capture,
|
||||||
|
env_val,
|
||||||
|
on_startup_val,
|
||||||
|
keymaps_val,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
log_level_val,
|
||||||
|
theme_val,
|
||||||
|
gfx_api_val,
|
||||||
|
drm_devices_val,
|
||||||
|
direct_scanout,
|
||||||
|
render_device_val,
|
||||||
|
inputs_val,
|
||||||
|
on_idle_val,
|
||||||
|
_,
|
||||||
|
idle_val,
|
||||||
|
),
|
||||||
|
) = ext.extract((
|
||||||
|
(
|
||||||
|
opt(val("keymap")),
|
||||||
|
opt(val("shortcuts")),
|
||||||
|
opt(val("on-graphics-initialized")),
|
||||||
|
opt(val("status")),
|
||||||
|
opt(val("outputs")),
|
||||||
|
opt(val("connectors")),
|
||||||
|
recover(opt(bol("workspace-capture"))),
|
||||||
|
opt(val("env")),
|
||||||
|
opt(val("on-startup")),
|
||||||
|
recover(opt(arr("keymaps"))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
opt(val("log-level")),
|
||||||
|
opt(val("theme")),
|
||||||
|
opt(val("gfx-api")),
|
||||||
|
opt(val("drm-devices")),
|
||||||
|
recover(opt(bol("direct-scanout"))),
|
||||||
|
opt(val("render-device")),
|
||||||
|
opt(val("inputs")),
|
||||||
|
opt(val("on-idle")),
|
||||||
|
opt(val("$schema")),
|
||||||
|
opt(val("idle")),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
let mut keymap = None;
|
||||||
|
if let Some(value) = keymap_val {
|
||||||
|
match value.parse(&mut KeymapParser {
|
||||||
|
cx: self.0,
|
||||||
|
definition: false,
|
||||||
|
}) {
|
||||||
|
Ok(m) => keymap = Some(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the keymap: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut shortcuts = vec![];
|
||||||
|
if let Some(value) = shortcuts_val {
|
||||||
|
shortcuts = value
|
||||||
|
.parse(&mut ShortcutsParser(self.0))
|
||||||
|
.map_spanned_err(ConfigParserError::ParseShortcuts)?;
|
||||||
|
}
|
||||||
|
if shortcuts.is_empty() {
|
||||||
|
log::warn!("Config defines no shortcuts");
|
||||||
|
}
|
||||||
|
let on_graphics_initialized =
|
||||||
|
self.parse_action("on-graphics-initialized", on_graphics_init_val);
|
||||||
|
let on_idle = self.parse_action("on-idle", on_idle_val);
|
||||||
|
let on_startup = self.parse_action("on-startup", on_startup_val);
|
||||||
|
let mut status = None;
|
||||||
|
if let Some(value) = status_val {
|
||||||
|
match value.parse(&mut StatusParser(self.0)) {
|
||||||
|
Ok(v) => status = Some(v),
|
||||||
|
Err(e) => log::warn!("Could not parse the status config: {}", self.0.error(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut outputs = vec![];
|
||||||
|
if let Some(value) = outputs_val {
|
||||||
|
match value.parse(&mut OutputsParser(self.0)) {
|
||||||
|
Ok(v) => outputs = v,
|
||||||
|
Err(e) => log::warn!("Could not parse the outputs: {}", self.0.error(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut connectors = vec![];
|
||||||
|
if let Some(value) = connectors_val {
|
||||||
|
match value.parse(&mut ConnectorsParser(self.0)) {
|
||||||
|
Ok(v) => connectors = v,
|
||||||
|
Err(e) => log::warn!("Could not parse the connectors: {}", self.0.error(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut env = vec![];
|
||||||
|
if let Some(value) = env_val {
|
||||||
|
match value.parse(&mut EnvParser) {
|
||||||
|
Ok(v) => env = v,
|
||||||
|
Err(e) => log::warn!(
|
||||||
|
"Could not parse the environment variables: {}",
|
||||||
|
self.0.error(e)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut keymaps = vec![];
|
||||||
|
if let Some(value) = keymaps_val {
|
||||||
|
for value in value.value {
|
||||||
|
match value.parse(&mut KeymapParser {
|
||||||
|
cx: self.0,
|
||||||
|
definition: true,
|
||||||
|
}) {
|
||||||
|
Ok(m) => keymaps.push(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse a keymap: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut log_level = None;
|
||||||
|
if let Some(value) = log_level_val {
|
||||||
|
match value.parse(&mut LogLevelParser) {
|
||||||
|
Ok(v) => log_level = Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the log level: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut theme = Theme::default();
|
||||||
|
if let Some(value) = theme_val {
|
||||||
|
match value.parse(&mut ThemeParser(self.0)) {
|
||||||
|
Ok(v) => theme = v,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the theme: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut gfx_api = None;
|
||||||
|
if let Some(value) = gfx_api_val {
|
||||||
|
match value.parse(&mut GfxApiParser) {
|
||||||
|
Ok(v) => gfx_api = Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the graphics API: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut drm_devices = vec![];
|
||||||
|
if let Some(value) = drm_devices_val {
|
||||||
|
match value.parse(&mut DrmDevicesParser(self.0)) {
|
||||||
|
Ok(v) => drm_devices = v,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the drm devices: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut render_device = None;
|
||||||
|
if let Some(value) = render_device_val {
|
||||||
|
match value.parse(&mut DrmDeviceMatchParser(self.0)) {
|
||||||
|
Ok(v) => render_device = Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the render device: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut inputs = vec![];
|
||||||
|
if let Some(value) = inputs_val {
|
||||||
|
match value.parse(&mut InputsParser(self.0)) {
|
||||||
|
Ok(v) => inputs = v,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the inputs: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut idle = None;
|
||||||
|
if let Some(value) = idle_val {
|
||||||
|
match value.parse(&mut IdleParser(self.0)) {
|
||||||
|
Ok(v) => idle = Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Config {
|
||||||
|
keymap,
|
||||||
|
shortcuts,
|
||||||
|
on_graphics_initialized,
|
||||||
|
on_idle,
|
||||||
|
status,
|
||||||
|
outputs,
|
||||||
|
connectors,
|
||||||
|
workspace_capture: workspace_capture.despan().unwrap_or(true),
|
||||||
|
env,
|
||||||
|
on_startup,
|
||||||
|
keymaps,
|
||||||
|
log_level,
|
||||||
|
theme,
|
||||||
|
gfx_api,
|
||||||
|
drm_devices,
|
||||||
|
direct_scanout_enabled: direct_scanout.despan(),
|
||||||
|
render_device,
|
||||||
|
inputs,
|
||||||
|
idle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
83
toml-config/src/config/parsers/connector.rs
Normal file
83
toml-config/src/config/parsers/connector.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{bol, opt, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError},
|
||||||
|
ConfigConnector,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConnectorParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Match(#[from] ConnectorMatchParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectorParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for ConnectorParser<'a> {
|
||||||
|
type Value = ConfigConnector;
|
||||||
|
type Error = ConnectorParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (match_val, enabled) = ext.extract((val("match"), opt(bol("enabled"))))?;
|
||||||
|
Ok(ConfigConnector {
|
||||||
|
match_: match_val.parse_map(&mut ConnectorMatchParser(self.0))?,
|
||||||
|
enabled: enabled.despan().unwrap_or(true),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectorsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for ConnectorsParser<'a> {
|
||||||
|
type Value = Vec<ConfigConnector>;
|
||||||
|
type Error = ConnectorParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(&mut ConnectorParser(self.0)) {
|
||||||
|
Ok(o) => res.push(o),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse connector: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
log::warn!(
|
||||||
|
"`connectors` value should be an array: {}",
|
||||||
|
self.0.error3(span)
|
||||||
|
);
|
||||||
|
ConnectorParser(self.0)
|
||||||
|
.parse_table(span, table)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
57
toml-config/src/config/parsers/connector_match.rs
Normal file
57
toml-config/src/config/parsers/connector_match.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{opt, str, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
ConnectorMatch,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ConnectorMatchParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectorMatchParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for ConnectorMatchParser<'a> {
|
||||||
|
type Value = ConnectorMatch;
|
||||||
|
type Error = ConnectorMatchParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(self) {
|
||||||
|
Ok(m) => res.push(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ConnectorMatch::Any(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (connector,) = ext.extract((opt(str("name")),))?;
|
||||||
|
Ok(ConnectorMatch::All {
|
||||||
|
connector: connector.map(|v| v.value.to_owned()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
126
toml-config/src/config/parsers/drm_device.rs
Normal file
126
toml-config/src/config/parsers/drm_device.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{bol, opt, recover, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{
|
||||||
|
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
|
||||||
|
gfx_api::GfxApiParser,
|
||||||
|
},
|
||||||
|
ConfigDrmDevice,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DrmDeviceParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Match(#[from] DrmDeviceMatchParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DrmDeviceParser<'a> {
|
||||||
|
pub cx: &'a Context<'a>,
|
||||||
|
pub name_ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser for DrmDeviceParser<'a> {
|
||||||
|
type Value = ConfigDrmDevice;
|
||||||
|
type Error = DrmDeviceParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.cx, span, table);
|
||||||
|
let (name, match_val, direct_scanout_enabled, gfx_api_val) = ext.extract((
|
||||||
|
opt(str("name")),
|
||||||
|
val("match"),
|
||||||
|
recover(opt(bol("direct-scanout"))),
|
||||||
|
opt(val("gfx-api")),
|
||||||
|
))?;
|
||||||
|
let gfx_api = match gfx_api_val {
|
||||||
|
Some(api) => match api.parse(&mut GfxApiParser) {
|
||||||
|
Ok(m) => Some(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse graphics API: {}", self.cx.error(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(name) = name {
|
||||||
|
if self.name_ok {
|
||||||
|
self.cx
|
||||||
|
.used
|
||||||
|
.borrow_mut()
|
||||||
|
.defined_drm_devices
|
||||||
|
.insert(name.into());
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"DRM device names have no effect in this position (did you mean match.name?): {}",
|
||||||
|
self.cx.error3(name.span)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ConfigDrmDevice {
|
||||||
|
name: name.despan().map(|v| v.to_string()),
|
||||||
|
match_: match_val.parse_map(&mut DrmDeviceMatchParser(self.cx))?,
|
||||||
|
direct_scanout_enabled: direct_scanout_enabled.despan(),
|
||||||
|
gfx_api,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DrmDevicesParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for DrmDevicesParser<'a> {
|
||||||
|
type Value = Vec<ConfigDrmDevice>;
|
||||||
|
type Error = DrmDeviceParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(&mut DrmDeviceParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: true,
|
||||||
|
}) {
|
||||||
|
Ok(o) => res.push(o),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse drm device: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
log::warn!(
|
||||||
|
"`drm-devices` value should be an array: {}",
|
||||||
|
self.0.error3(span)
|
||||||
|
);
|
||||||
|
DrmDeviceParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: true,
|
||||||
|
}
|
||||||
|
.parse_table(span, table)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
74
toml-config/src/config/parsers/drm_device_match.rs
Normal file
74
toml-config/src/config/parsers/drm_device_match.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{n32, opt, recover, str, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
DrmDeviceMatch,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DrmDeviceMatchParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DrmDeviceMatchParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for DrmDeviceMatchParser<'_> {
|
||||||
|
type Value = DrmDeviceMatch;
|
||||||
|
type Error = DrmDeviceMatchParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(self) {
|
||||||
|
Ok(m) => res.push(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(DrmDeviceMatch::Any(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (name, syspath, vendor, vendor_name, model, model_name, devnode) = ext.extract((
|
||||||
|
recover(opt(str("name"))),
|
||||||
|
recover(opt(str("syspath"))),
|
||||||
|
recover(opt(n32("pci-vendor"))),
|
||||||
|
recover(opt(str("vendor"))),
|
||||||
|
recover(opt(n32("pci-model"))),
|
||||||
|
recover(opt(str("model"))),
|
||||||
|
recover(opt(str("devnode"))),
|
||||||
|
))?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
self.0.used.borrow_mut().drm_devices.push(name.into());
|
||||||
|
}
|
||||||
|
Ok(DrmDeviceMatch::All {
|
||||||
|
name: name.despan_into(),
|
||||||
|
syspath: syspath.despan_into(),
|
||||||
|
vendor: vendor.despan(),
|
||||||
|
vendor_name: vendor_name.despan_into(),
|
||||||
|
model: model.despan(),
|
||||||
|
model_name: model_name.despan_into(),
|
||||||
|
devnode: devnode.despan_into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
42
toml-config/src/config/parsers/env.rs
Normal file
42
toml-config/src/config/parsers/env.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{StringParser, StringParserError},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum EnvParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
String(#[from] StringParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnvParser;
|
||||||
|
|
||||||
|
impl Parser for EnvParser {
|
||||||
|
type Value = Vec<(String, String)>;
|
||||||
|
type Error = EnvParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
_span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut envs = vec![];
|
||||||
|
for (k, v) in table {
|
||||||
|
envs.push((k.value.to_string(), v.parse_map(&mut StringParser)?));
|
||||||
|
}
|
||||||
|
Ok(envs)
|
||||||
|
}
|
||||||
|
}
|
||||||
91
toml-config/src/config/parsers/exec.rs
Normal file
91
toml-config/src/config/parsers/exec.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{arr, opt, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{
|
||||||
|
env::{EnvParser, EnvParserError},
|
||||||
|
StringParser, StringParserError,
|
||||||
|
},
|
||||||
|
Exec,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ExecParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extractor(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
String(#[from] StringParserError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Env(#[from] EnvParserError),
|
||||||
|
#[error("Array cannot be empty")]
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExecParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for ExecParser<'_> {
|
||||||
|
type Value = Exec;
|
||||||
|
type Error = ExecParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
Ok(Exec {
|
||||||
|
prog: string.to_string(),
|
||||||
|
args: vec![],
|
||||||
|
envs: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
if array.is_empty() {
|
||||||
|
return Err(ExecParserError::Empty.spanned(span));
|
||||||
|
}
|
||||||
|
let prog = array[0].parse_map(&mut StringParser)?;
|
||||||
|
let mut args = vec![];
|
||||||
|
for v in &array[1..] {
|
||||||
|
args.push(v.parse_map(&mut StringParser)?);
|
||||||
|
}
|
||||||
|
Ok(Exec {
|
||||||
|
prog,
|
||||||
|
args,
|
||||||
|
envs: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (prog, args_val, envs_val) =
|
||||||
|
ext.extract((str("prog"), opt(arr("args")), opt(val("env"))))?;
|
||||||
|
let mut args = vec![];
|
||||||
|
if let Some(args_val) = args_val {
|
||||||
|
for arg in args_val.value {
|
||||||
|
args.push(arg.parse_map(&mut StringParser)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let envs = match envs_val {
|
||||||
|
None => vec![],
|
||||||
|
Some(e) => e.parse_map(&mut EnvParser)?,
|
||||||
|
};
|
||||||
|
Ok(Exec {
|
||||||
|
prog: prog.value.to_string(),
|
||||||
|
args,
|
||||||
|
envs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
34
toml-config/src/config/parsers/gfx_api.rs
Normal file
34
toml-config/src/config/parsers/gfx_api.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
toml::toml_span::{Span, SpannedExt},
|
||||||
|
},
|
||||||
|
jay_config::video::GfxApi,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct GfxApiParser;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum GfxApiParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
DataType(#[from] UnexpectedDataType),
|
||||||
|
#[error("Unknown API {0}")]
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for GfxApiParser {
|
||||||
|
type Value = GfxApi;
|
||||||
|
type Error = GfxApiParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
use GfxApi::*;
|
||||||
|
let api = match string.to_ascii_lowercase().as_str() {
|
||||||
|
"opengl" => OpenGl,
|
||||||
|
"vulkan" => Vulkan,
|
||||||
|
_ => return Err(GfxApiParserError::Unknown(string.to_string()).spanned(span)),
|
||||||
|
};
|
||||||
|
Ok(api)
|
||||||
|
}
|
||||||
|
}
|
||||||
45
toml-config/src/config/parsers/idle.rs
Normal file
45
toml-config/src/config/parsers/idle.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{n64, opt, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
std::time::Duration,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum IdleParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IdleParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for IdleParser<'_> {
|
||||||
|
type Value = Duration;
|
||||||
|
type Error = IdleParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?;
|
||||||
|
let idle = Duration::from_secs(
|
||||||
|
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
|
||||||
|
);
|
||||||
|
Ok(idle)
|
||||||
|
}
|
||||||
|
}
|
||||||
205
toml-config/src/config/parsers/input.rs
Normal file
205
toml-config/src/config/parsers/input.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::input_match::{InputMatchParser, InputMatchParserError},
|
||||||
|
Input,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::input::acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InputParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Match(#[from] InputMatchParserError),
|
||||||
|
#[error("Transform matrix must have exactly two rows")]
|
||||||
|
TwoRows,
|
||||||
|
#[error("Transform matrix must have exactly two columns")]
|
||||||
|
TwoColumns,
|
||||||
|
#[error("Transform matrix entries must be floats")]
|
||||||
|
Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputParser<'a> {
|
||||||
|
pub cx: &'a Context<'a>,
|
||||||
|
pub tag_ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser for InputParser<'a> {
|
||||||
|
type Value = Input;
|
||||||
|
type Error = InputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.cx, span, table);
|
||||||
|
let (
|
||||||
|
(
|
||||||
|
tag,
|
||||||
|
match_val,
|
||||||
|
accel_profile,
|
||||||
|
accel_speed,
|
||||||
|
tap_enabled,
|
||||||
|
tap_drag_enabled,
|
||||||
|
tap_drag_lock_enabled,
|
||||||
|
left_handed,
|
||||||
|
natural_scrolling,
|
||||||
|
px_per_wheel_scroll,
|
||||||
|
),
|
||||||
|
(transform_matrix,),
|
||||||
|
) = ext.extract((
|
||||||
|
(
|
||||||
|
opt(str("tag")),
|
||||||
|
val("match"),
|
||||||
|
recover(opt(str("accel-profile"))),
|
||||||
|
recover(opt(fltorint("accel-speed"))),
|
||||||
|
recover(opt(bol("tap-enabled"))),
|
||||||
|
recover(opt(bol("tap-drag-enabled"))),
|
||||||
|
recover(opt(bol("tap-drag-lock-enabled"))),
|
||||||
|
recover(opt(bol("left-handed"))),
|
||||||
|
recover(opt(bol("natural-scrolling"))),
|
||||||
|
recover(opt(fltorint("px-per-wheel-scroll"))),
|
||||||
|
),
|
||||||
|
(recover(opt(val("transform-matrix"))),),
|
||||||
|
))?;
|
||||||
|
let accel_profile = match accel_profile {
|
||||||
|
None => None,
|
||||||
|
Some(p) => match p.value.to_ascii_lowercase().as_str() {
|
||||||
|
"flat" => Some(ACCEL_PROFILE_FLAT),
|
||||||
|
"adaptive" => Some(ACCEL_PROFILE_ADAPTIVE),
|
||||||
|
v => {
|
||||||
|
log::warn!("Unknown accel-profile {v}: {}", self.cx.error3(p.span));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let transform_matrix = match transform_matrix {
|
||||||
|
None => None,
|
||||||
|
Some(matrix) => match matrix.parse(&mut TransformMatrixParser) {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse transform matrix: {}", self.cx.error(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
if self.tag_ok {
|
||||||
|
self.cx.used.borrow_mut().defined_inputs.insert(tag.into());
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Input tags have no effect in this position (did you mean match.tag?): {}",
|
||||||
|
self.cx.error3(tag.span)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Input {
|
||||||
|
tag: tag.despan_into(),
|
||||||
|
match_: match_val.parse_map(&mut InputMatchParser(self.cx))?,
|
||||||
|
accel_profile,
|
||||||
|
accel_speed: accel_speed.despan(),
|
||||||
|
tap_enabled: tap_enabled.despan(),
|
||||||
|
tap_drag_enabled: tap_drag_enabled.despan(),
|
||||||
|
tap_drag_lock_enabled: tap_drag_lock_enabled.despan(),
|
||||||
|
left_handed: left_handed.despan(),
|
||||||
|
natural_scrolling: natural_scrolling.despan(),
|
||||||
|
px_per_wheel_scroll: px_per_wheel_scroll.despan(),
|
||||||
|
transform_matrix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for InputsParser<'a> {
|
||||||
|
type Value = Vec<Input>;
|
||||||
|
type Error = InputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(&mut InputParser {
|
||||||
|
cx: self.0,
|
||||||
|
tag_ok: true,
|
||||||
|
}) {
|
||||||
|
Ok(o) => res.push(o),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse output: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
log::warn!(
|
||||||
|
"`outputs` value should be an array: {}",
|
||||||
|
self.0.error3(span)
|
||||||
|
);
|
||||||
|
InputParser {
|
||||||
|
cx: self.0,
|
||||||
|
tag_ok: true,
|
||||||
|
}
|
||||||
|
.parse_table(span, table)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransformMatrixParser;
|
||||||
|
|
||||||
|
impl Parser for TransformMatrixParser {
|
||||||
|
type Value = [[f64; 2]; 2];
|
||||||
|
type Error = InputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
if array.len() != 2 {
|
||||||
|
return Err(InputParserError::TwoRows.spanned(span));
|
||||||
|
}
|
||||||
|
Ok([
|
||||||
|
array[0].parse(&mut TransformMatrixRowParser)?,
|
||||||
|
array[1].parse(&mut TransformMatrixRowParser)?,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransformMatrixRowParser;
|
||||||
|
|
||||||
|
impl Parser for TransformMatrixRowParser {
|
||||||
|
type Value = [f64; 2];
|
||||||
|
type Error = InputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
if array.len() != 2 {
|
||||||
|
return Err(InputParserError::TwoColumns.spanned(span));
|
||||||
|
}
|
||||||
|
let extract = |v: &Spanned<Value>| match v.value {
|
||||||
|
Value::Float(f) => Ok(f),
|
||||||
|
Value::Integer(f) => Ok(f as _),
|
||||||
|
_ => Err(InputParserError::Float.spanned(v.span)),
|
||||||
|
};
|
||||||
|
Ok([extract(&array[0])?, extract(&array[1])?])
|
||||||
|
}
|
||||||
|
}
|
||||||
98
toml-config/src/config/parsers/input_match.rs
Normal file
98
toml-config/src/config/parsers/input_match.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{bol, opt, str, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
InputMatch,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InputMatchParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputMatchParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for InputMatchParser<'a> {
|
||||||
|
type Value = InputMatch;
|
||||||
|
type Error = InputMatchParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(self) {
|
||||||
|
Ok(m) => res.push(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(InputMatch::Any(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (
|
||||||
|
(
|
||||||
|
tag,
|
||||||
|
name,
|
||||||
|
syspath,
|
||||||
|
devnode,
|
||||||
|
is_keyboard,
|
||||||
|
is_pointer,
|
||||||
|
is_touch,
|
||||||
|
is_tablet_tool,
|
||||||
|
is_tablet_pad,
|
||||||
|
is_gesture,
|
||||||
|
),
|
||||||
|
(is_switch,),
|
||||||
|
) = ext.extract((
|
||||||
|
(
|
||||||
|
opt(str("tag")),
|
||||||
|
opt(str("name")),
|
||||||
|
opt(str("syspath")),
|
||||||
|
opt(str("devnode")),
|
||||||
|
opt(bol("is-keyboard")),
|
||||||
|
opt(bol("is-pointer")),
|
||||||
|
opt(bol("is-touch")),
|
||||||
|
opt(bol("is-tablet-tool")),
|
||||||
|
opt(bol("is-tablet-pad")),
|
||||||
|
opt(bol("is-gesture")),
|
||||||
|
),
|
||||||
|
(opt(bol("is-switch")),),
|
||||||
|
))?;
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
self.0.used.borrow_mut().inputs.push(tag.into());
|
||||||
|
}
|
||||||
|
Ok(InputMatch::All {
|
||||||
|
tag: tag.despan_into(),
|
||||||
|
name: name.despan_into(),
|
||||||
|
syspath: syspath.despan_into(),
|
||||||
|
devnode: devnode.despan_into(),
|
||||||
|
is_keyboard: is_keyboard.despan(),
|
||||||
|
is_pointer: is_pointer.despan(),
|
||||||
|
is_touch: is_touch.despan(),
|
||||||
|
is_tablet_tool: is_tablet_tool.despan(),
|
||||||
|
is_tablet_pad: is_tablet_pad.despan(),
|
||||||
|
is_gesture: is_gesture.despan(),
|
||||||
|
is_switch: is_switch.despan(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
123
toml-config/src/config/parsers/keymap.rs
Normal file
123
toml-config/src/config/parsers/keymap.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{opt, str, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
ConfigKeymap,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::{
|
||||||
|
config_dir,
|
||||||
|
keyboard::{parse_keymap, Keymap},
|
||||||
|
},
|
||||||
|
std::{io, path::PathBuf},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum KeymapParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extractor(#[from] ExtractorError),
|
||||||
|
#[error("The keymap is invalid")]
|
||||||
|
Invalid,
|
||||||
|
#[error("Keymap table must contain at least one of `name`, `map`")]
|
||||||
|
MissingField,
|
||||||
|
#[error("Keymap must have both `name` and `map` fields in this context")]
|
||||||
|
DefinitionRequired,
|
||||||
|
#[error("Could not read {0}")]
|
||||||
|
ReadFile(String, #[source] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct KeymapParser<'a> {
|
||||||
|
pub cx: &'a Context<'a>,
|
||||||
|
pub definition: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for KeymapParser<'_> {
|
||||||
|
type Value = ConfigKeymap;
|
||||||
|
type Error = KeymapParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Table];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
Ok(ConfigKeymap::Literal(parse(span, string)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.cx, span, table);
|
||||||
|
let (mut name_val, mut map_val, mut path) =
|
||||||
|
ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?;
|
||||||
|
if map_val.is_some() && path.is_some() {
|
||||||
|
log::warn!(
|
||||||
|
"Both `name` and `path` are specified. Ignoring `path`: {}",
|
||||||
|
self.cx.error3(span)
|
||||||
|
);
|
||||||
|
path = None;
|
||||||
|
}
|
||||||
|
let file_content;
|
||||||
|
if let Some(path) = path {
|
||||||
|
let mut root = PathBuf::from(config_dir());
|
||||||
|
root.push(path.value);
|
||||||
|
file_content = match std::fs::read_to_string(&root) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(KeymapParserError::ReadFile(root.display().to_string(), e)
|
||||||
|
.spanned(path.span))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
map_val = Some(file_content.as_str().spanned(path.span));
|
||||||
|
}
|
||||||
|
if self.definition && (name_val.is_none() || map_val.is_none()) {
|
||||||
|
return Err(KeymapParserError::DefinitionRequired.spanned(span));
|
||||||
|
}
|
||||||
|
if !self.definition && map_val.is_some() {
|
||||||
|
if let Some(val) = name_val {
|
||||||
|
log::warn!(
|
||||||
|
"Cannot use both `name` and `map` in this position. Ignoring `name`: {}",
|
||||||
|
self.cx.error3(val.span)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
name_val = None;
|
||||||
|
}
|
||||||
|
if let Some(name) = name_val {
|
||||||
|
if self.definition {
|
||||||
|
self.cx
|
||||||
|
.used
|
||||||
|
.borrow_mut()
|
||||||
|
.defined_keymaps
|
||||||
|
.insert(name.into());
|
||||||
|
} else {
|
||||||
|
self.cx.used.borrow_mut().keymaps.push(name.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let res = match (name_val, map_val) {
|
||||||
|
(Some(name_val), Some(map_val)) => ConfigKeymap::Defined {
|
||||||
|
name: name_val.value.to_string(),
|
||||||
|
map: parse(map_val.span, map_val.value)?,
|
||||||
|
},
|
||||||
|
(Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()),
|
||||||
|
(None, Some(map_val)) => ConfigKeymap::Literal(parse(map_val.span, map_val.value)?),
|
||||||
|
(None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(span: Span, string: &str) -> Result<Keymap, Spanned<KeymapParserError>> {
|
||||||
|
let map = parse_keymap(string);
|
||||||
|
match map.is_valid() {
|
||||||
|
true => Ok(map),
|
||||||
|
false => Err(KeymapParserError::Invalid.spanned(span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
37
toml-config/src/config/parsers/log_level.rs
Normal file
37
toml-config/src/config/parsers/log_level.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
toml::toml_span::{Span, SpannedExt},
|
||||||
|
},
|
||||||
|
jay_config::logging::LogLevel,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct LogLevelParser;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LogLevelParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
DataType(#[from] UnexpectedDataType),
|
||||||
|
#[error("Unknown log level {0}")]
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for LogLevelParser {
|
||||||
|
type Value = LogLevel;
|
||||||
|
type Error = LogLevelParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
use LogLevel::*;
|
||||||
|
let level = match string.to_ascii_lowercase().as_str() {
|
||||||
|
"error" => Error,
|
||||||
|
"warn" | "warning" => Warn,
|
||||||
|
"info" => Info,
|
||||||
|
"debug" => Debug,
|
||||||
|
"trace" => Trace,
|
||||||
|
_ => return Err(LogLevelParserError::Unknown(string.to_string()).spanned(span)),
|
||||||
|
};
|
||||||
|
Ok(level)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
toml-config/src/config/parsers/mode.rs
Normal file
47
toml-config/src/config/parsers/mode.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{flt, opt, s32, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
Mode,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ModeParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModeParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for ModeParser<'a> {
|
||||||
|
type Value = Mode;
|
||||||
|
type Error = ModeParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (width, height, refresh_rate) =
|
||||||
|
ext.extract((s32("width"), s32("height"), opt(flt("refresh-rate"))))?;
|
||||||
|
Ok(Mode {
|
||||||
|
width: width.value,
|
||||||
|
height: height.value,
|
||||||
|
refresh_rate: refresh_rate.despan(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
71
toml-config/src/config/parsers/modified_keysym.rs
Normal file
71
toml-config/src/config/parsers/modified_keysym.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
keysyms::KEYSYMS,
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
},
|
||||||
|
toml::toml_span::{Span, SpannedExt},
|
||||||
|
},
|
||||||
|
jay_config::keyboard::{
|
||||||
|
mods::{Modifiers, ALT, CAPS, CTRL, LOCK, LOGO, MOD1, MOD2, MOD3, MOD4, MOD5, NUM, SHIFT},
|
||||||
|
ModifiedKeySym,
|
||||||
|
},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ModifiedKeysymParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error("You cannot use more than one non-modifier key")]
|
||||||
|
MoreThanOneSym,
|
||||||
|
#[error("You must specify exactly one non-modifier key")]
|
||||||
|
MissingSym,
|
||||||
|
#[error("Unknown keysym {0}")]
|
||||||
|
UnknownKeysym(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ModifiedKeysymParser;
|
||||||
|
|
||||||
|
impl Parser for ModifiedKeysymParser {
|
||||||
|
type Value = ModifiedKeySym;
|
||||||
|
type Error = ModifiedKeysymParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::String];
|
||||||
|
|
||||||
|
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||||
|
let mut modifiers = Modifiers(0);
|
||||||
|
let mut sym = None;
|
||||||
|
for part in string.split("-") {
|
||||||
|
let modifier = match part {
|
||||||
|
"shift" => SHIFT,
|
||||||
|
"lock" => LOCK,
|
||||||
|
"ctrl" => CTRL,
|
||||||
|
"mod1" => MOD1,
|
||||||
|
"mod2" => MOD2,
|
||||||
|
"mod3" => MOD3,
|
||||||
|
"mod4" => MOD4,
|
||||||
|
"mod5" => MOD5,
|
||||||
|
"caps" => CAPS,
|
||||||
|
"alt" => ALT,
|
||||||
|
"num" => NUM,
|
||||||
|
"logo" => LOGO,
|
||||||
|
_ => match KEYSYMS.get(part) {
|
||||||
|
Some(new) if sym.is_none() => {
|
||||||
|
sym = Some(*new);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(_) => return Err(ModifiedKeysymParserError::MoreThanOneSym.spanned(span)),
|
||||||
|
_ => {
|
||||||
|
return Err(ModifiedKeysymParserError::UnknownKeysym(part.to_string())
|
||||||
|
.spanned(span))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
modifiers |= modifier;
|
||||||
|
}
|
||||||
|
match sym {
|
||||||
|
Some(s) => Ok(modifiers | s),
|
||||||
|
None => Err(ModifiedKeysymParserError::MissingSym.spanned(span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
toml-config/src/config/parsers/output.rs
Normal file
150
toml-config/src/config/parsers/output.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{fltorint, opt, recover, s32, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{
|
||||||
|
mode::ModeParser,
|
||||||
|
output_match::{OutputMatchParser, OutputMatchParserError},
|
||||||
|
},
|
||||||
|
Output,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::video::Transform,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum OutputParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Match(#[from] OutputMatchParserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OutputParser<'a> {
|
||||||
|
pub cx: &'a Context<'a>,
|
||||||
|
pub name_ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser for OutputParser<'a> {
|
||||||
|
type Value = Output;
|
||||||
|
type Error = OutputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.cx, span, table);
|
||||||
|
let (name, match_val, x, y, scale, transform, mode) = ext.extract((
|
||||||
|
opt(str("name")),
|
||||||
|
val("match"),
|
||||||
|
recover(opt(s32("x"))),
|
||||||
|
recover(opt(s32("y"))),
|
||||||
|
recover(opt(fltorint("scale"))),
|
||||||
|
recover(opt(str("transform"))),
|
||||||
|
opt(val("mode")),
|
||||||
|
))?;
|
||||||
|
let transform = match transform {
|
||||||
|
None => None,
|
||||||
|
Some(t) => match t.value {
|
||||||
|
"none" => Some(Transform::None),
|
||||||
|
"rotate-90" => Some(Transform::Rotate90),
|
||||||
|
"rotate-180" => Some(Transform::Rotate180),
|
||||||
|
"rotate-270" => Some(Transform::Rotate270),
|
||||||
|
"flip" => Some(Transform::Flip),
|
||||||
|
"flip-rotate-90" => Some(Transform::FlipRotate90),
|
||||||
|
"flip-rotate-180" => Some(Transform::FlipRotate180),
|
||||||
|
"flip-rotate-270" => Some(Transform::FlipRotate270),
|
||||||
|
_ => {
|
||||||
|
log::warn!("Unknown transform {}: {}", t.value, self.cx.error3(t.span));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mode = match mode {
|
||||||
|
Some(mode) => match mode.parse(&mut ModeParser(self.cx)) {
|
||||||
|
Ok(m) => Some(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse mode: {}", self.cx.error(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(name) = name {
|
||||||
|
if self.name_ok {
|
||||||
|
self.cx
|
||||||
|
.used
|
||||||
|
.borrow_mut()
|
||||||
|
.defined_outputs
|
||||||
|
.insert(name.into());
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"Output names have no effect in this position (did you mean match.name?): {}",
|
||||||
|
self.cx.error3(name.span)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Output {
|
||||||
|
name: name.despan().map(|v| v.to_string()),
|
||||||
|
match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?,
|
||||||
|
x: x.despan(),
|
||||||
|
y: y.despan(),
|
||||||
|
scale: scale.despan(),
|
||||||
|
transform,
|
||||||
|
mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OutputsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for OutputsParser<'a> {
|
||||||
|
type Value = Vec<Output>;
|
||||||
|
type Error = OutputParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(&mut OutputParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: true,
|
||||||
|
}) {
|
||||||
|
Ok(o) => res.push(o),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse output: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
log::warn!(
|
||||||
|
"`outputs` value should be an array: {}",
|
||||||
|
self.0.error3(span)
|
||||||
|
);
|
||||||
|
OutputParser {
|
||||||
|
cx: self.0,
|
||||||
|
name_ok: true,
|
||||||
|
}
|
||||||
|
.parse_table(span, table)
|
||||||
|
.map(|v| vec![v])
|
||||||
|
}
|
||||||
|
}
|
||||||
70
toml-config/src/config/parsers/output_match.rs
Normal file
70
toml-config/src/config/parsers/output_match.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{opt, str, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
OutputMatch,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum OutputMatchParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl<'a> Parser for OutputMatchParser<'a> {
|
||||||
|
type Value = OutputMatch;
|
||||||
|
type Error = OutputMatchParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||||
|
|
||||||
|
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for el in array {
|
||||||
|
match el.parse(self) {
|
||||||
|
Ok(m) => res.push(m),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not parse match rule: {}", self.0.error(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(OutputMatch::Any(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (name, connector, serial_number, manufacturer, model) = ext.extract((
|
||||||
|
opt(str("name")),
|
||||||
|
opt(str("connector")),
|
||||||
|
opt(str("serial-number")),
|
||||||
|
opt(str("manufacturer")),
|
||||||
|
opt(str("model")),
|
||||||
|
))?;
|
||||||
|
if let Some(name) = name {
|
||||||
|
self.0.used.borrow_mut().outputs.push(name.into());
|
||||||
|
}
|
||||||
|
Ok(OutputMatch::All {
|
||||||
|
name: name.despan_into(),
|
||||||
|
connector: connector.despan_into(),
|
||||||
|
serial_number: serial_number.despan_into(),
|
||||||
|
manufacturer: manufacturer.despan_into(),
|
||||||
|
model: model.despan_into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
72
toml-config/src/config/parsers/shortcuts.rs
Normal file
72
toml-config/src/config/parsers/shortcuts.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::{action::ActionParser, modified_keysym::ModifiedKeysymParser},
|
||||||
|
Action,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::keyboard::ModifiedKeySym,
|
||||||
|
std::collections::HashSet,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ShortcutsParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShortcutsParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for ShortcutsParser<'_> {
|
||||||
|
type Value = Vec<(ModifiedKeySym, Action)>;
|
||||||
|
type Error = ShortcutsParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
_span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut used_keys = HashSet::<Spanned<ModifiedKeySym>>::new();
|
||||||
|
let mut res = vec![];
|
||||||
|
for (key, value) in table.iter() {
|
||||||
|
let keysym = match ModifiedKeysymParser.parse_string(key.span, &key.value) {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse keysym: {}", self.0.error(e));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let action = match value.parse(&mut ActionParser(self.0)) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"Could not parse action for keysym {}: {}",
|
||||||
|
key.value,
|
||||||
|
self.0.error(e)
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let spanned = keysym.spanned(key.span);
|
||||||
|
if let Some(prev) = used_keys.get(&spanned) {
|
||||||
|
log::warn!(
|
||||||
|
"Duplicate key overrides previous definition: {}",
|
||||||
|
self.0.error3(spanned.span)
|
||||||
|
);
|
||||||
|
log::info!("Previous definition here: {}", self.0.error3(prev.span));
|
||||||
|
}
|
||||||
|
used_keys.insert(spanned);
|
||||||
|
res.push((keysym, action));
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
toml-config/src/config/parsers/status.rs
Normal file
81
toml-config/src/config/parsers/status.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{opt, recover, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::exec::{ExecParser, ExecParserError},
|
||||||
|
Status,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
jay_config::status::MessageFormat,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum StatusParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Exec(#[from] ExecParserError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extract(#[from] ExtractorError),
|
||||||
|
#[error("Expected `plain`, `pango`, or `i3bar` but found {0}")]
|
||||||
|
UnknownFormat(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StatusParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
impl Parser for StatusParser<'_> {
|
||||||
|
type Value = Status;
|
||||||
|
type Error = StatusParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (format, exec, separator) = ext.extract((
|
||||||
|
opt(str("format")),
|
||||||
|
val("exec"),
|
||||||
|
recover(opt(str("i3bar-separator"))),
|
||||||
|
))?;
|
||||||
|
let format = match format {
|
||||||
|
Some(f) => match f.value {
|
||||||
|
"plain" => MessageFormat::Plain,
|
||||||
|
"pango" => MessageFormat::Pango,
|
||||||
|
"i3bar" => MessageFormat::I3Bar,
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
StatusParserError::UnknownFormat(f.value.to_string()).spanned(f.span)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => MessageFormat::Plain,
|
||||||
|
};
|
||||||
|
let exec = exec.parse_map(&mut ExecParser(self.0))?;
|
||||||
|
let separator = match separator {
|
||||||
|
None => None,
|
||||||
|
Some(sep) if format == MessageFormat::I3Bar => Some(sep.value.to_string()),
|
||||||
|
Some(sep) => {
|
||||||
|
log::warn!(
|
||||||
|
"Separator has no effect for format {format:?}: {}",
|
||||||
|
self.0.error3(sep.span)
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Status {
|
||||||
|
format,
|
||||||
|
exec,
|
||||||
|
separator,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
119
toml-config/src/config/parsers/theme.rs
Normal file
119
toml-config/src/config/parsers/theme.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::{
|
||||||
|
context::Context,
|
||||||
|
extractor::{opt, recover, s32, str, val, Extractor, ExtractorError},
|
||||||
|
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||||
|
parsers::color::ColorParser,
|
||||||
|
Theme,
|
||||||
|
},
|
||||||
|
toml::{
|
||||||
|
toml_span::{DespanExt, Span, Spanned},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexmap::IndexMap,
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ThemeParser<'a>(pub &'a Context<'a>);
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ThemeParserError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Expected(#[from] UnexpectedDataType),
|
||||||
|
#[error(transparent)]
|
||||||
|
Extractor(#[from] ExtractorError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser for ThemeParser<'_> {
|
||||||
|
type Value = Theme;
|
||||||
|
type Error = ThemeParserError;
|
||||||
|
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||||
|
|
||||||
|
fn parse_table(
|
||||||
|
&mut self,
|
||||||
|
span: Span,
|
||||||
|
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> ParseResult<Self> {
|
||||||
|
let mut ext = Extractor::new(self.0, span, table);
|
||||||
|
let (
|
||||||
|
(
|
||||||
|
attention_requested_bg_color,
|
||||||
|
bg_color,
|
||||||
|
bar_bg_color,
|
||||||
|
bar_status_text_color,
|
||||||
|
border_color,
|
||||||
|
captured_focused_title_bg_color,
|
||||||
|
captured_unfocused_title_bg_color,
|
||||||
|
focused_inactive_title_bg_color,
|
||||||
|
focused_inactive_title_text_color,
|
||||||
|
focused_title_bg_color,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
focused_title_text_color,
|
||||||
|
separator_color,
|
||||||
|
unfocused_title_bg_color,
|
||||||
|
unfocused_title_text_color,
|
||||||
|
border_width,
|
||||||
|
title_height,
|
||||||
|
font,
|
||||||
|
),
|
||||||
|
) = ext.extract((
|
||||||
|
(
|
||||||
|
opt(val("attention-requested-bg-color")),
|
||||||
|
opt(val("bg-color")),
|
||||||
|
opt(val("bar-bg-color")),
|
||||||
|
opt(val("bar-status-text-color")),
|
||||||
|
opt(val("border-color")),
|
||||||
|
opt(val("captured-focused-title-bg-color")),
|
||||||
|
opt(val("captured-unfocused-title-bg-color")),
|
||||||
|
opt(val("focused-inactive-title-bg-color")),
|
||||||
|
opt(val("focused-inactive-title-text-color")),
|
||||||
|
opt(val("focused-title-bg-color")),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
opt(val("focused-title-text-color")),
|
||||||
|
opt(val("separator-color")),
|
||||||
|
opt(val("unfocused-title-bg-color")),
|
||||||
|
opt(val("unfocused-title-text-color")),
|
||||||
|
recover(opt(s32("border-width"))),
|
||||||
|
recover(opt(s32("title-height"))),
|
||||||
|
recover(opt(str("font"))),
|
||||||
|
),
|
||||||
|
))?;
|
||||||
|
macro_rules! color {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
None => None,
|
||||||
|
Some(v) => match v.parse(&mut ColorParser(self.0)) {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not parse a color: {}", self.0.error(e));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Theme {
|
||||||
|
attention_requested_bg_color: color!(attention_requested_bg_color),
|
||||||
|
bg_color: color!(bg_color),
|
||||||
|
bar_bg_color: color!(bar_bg_color),
|
||||||
|
bar_status_text_color: color!(bar_status_text_color),
|
||||||
|
border_color: color!(border_color),
|
||||||
|
captured_focused_title_bg_color: color!(captured_focused_title_bg_color),
|
||||||
|
captured_unfocused_title_bg_color: color!(captured_unfocused_title_bg_color),
|
||||||
|
focused_inactive_title_bg_color: color!(focused_inactive_title_bg_color),
|
||||||
|
focused_inactive_title_text_color: color!(focused_inactive_title_text_color),
|
||||||
|
focused_title_bg_color: color!(focused_title_bg_color),
|
||||||
|
focused_title_text_color: color!(focused_title_text_color),
|
||||||
|
separator_color: color!(separator_color),
|
||||||
|
unfocused_title_bg_color: color!(unfocused_title_bg_color),
|
||||||
|
unfocused_title_text_color: color!(unfocused_title_text_color),
|
||||||
|
border_width: border_width.despan(),
|
||||||
|
title_height: title_height.despan(),
|
||||||
|
font: font.map(|f| f.value.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
63
toml-config/src/config/spanned.rs
Normal file
63
toml-config/src/config/spanned.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::{
|
||||||
|
config::parser::{ParseResult, Parser},
|
||||||
|
toml::{toml_span::Spanned, toml_value::Value},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Spanned<&Value> {
|
||||||
|
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||||
|
self.value.parse(self.span, parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_map<P: Parser, E>(
|
||||||
|
&self,
|
||||||
|
parser: &mut P,
|
||||||
|
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||||
|
where
|
||||||
|
<P as Parser>::Error: Into<E>,
|
||||||
|
{
|
||||||
|
self.parse(parser).map_spanned_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spanned<Value> {
|
||||||
|
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||||
|
self.as_ref().parse(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_map<P: Parser, E>(
|
||||||
|
&self,
|
||||||
|
parser: &mut P,
|
||||||
|
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||||
|
where
|
||||||
|
<P as Parser>::Error: Into<E>,
|
||||||
|
{
|
||||||
|
self.as_ref().parse_map(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SpannedErrorExt {
|
||||||
|
type T;
|
||||||
|
type E;
|
||||||
|
|
||||||
|
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||||
|
where
|
||||||
|
F: FnOnce(Self::E) -> U;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> SpannedErrorExt for Result<T, Spanned<E>> {
|
||||||
|
type T = T;
|
||||||
|
type E = E;
|
||||||
|
|
||||||
|
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||||
|
where
|
||||||
|
F: FnOnce(Self::E) -> U,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(Spanned {
|
||||||
|
span: e.span,
|
||||||
|
value: f(e.value),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
toml-config/src/config/value.rs
Normal file
17
toml-config/src/config/value.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use crate::{
|
||||||
|
config::parser::{ParseResult, Parser},
|
||||||
|
toml::{toml_span::Span, toml_value::Value},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn parse<P: Parser>(&self, span: Span, parser: &mut P) -> ParseResult<P> {
|
||||||
|
match self {
|
||||||
|
Value::String(a) => parser.parse_string(span, a),
|
||||||
|
Value::Integer(a) => parser.parse_integer(span, *a),
|
||||||
|
Value::Float(a) => parser.parse_float(span, *a),
|
||||||
|
Value::Boolean(a) => parser.parse_bool(span, *a),
|
||||||
|
Value::Array(a) => parser.parse_array(span, a),
|
||||||
|
Value::Table(a) => parser.parse_table(span, a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
toml-config/src/default-config.toml
Normal file
75
toml-config/src/default-config.toml
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
keymap = """
|
||||||
|
xkb_keymap {
|
||||||
|
xkb_keycodes { include "evdev+aliases(qwerty)" };
|
||||||
|
xkb_types { include "complete" };
|
||||||
|
xkb_compat { include "complete" };
|
||||||
|
xkb_symbols { include "pc+us+inet(evdev)" };
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
on-graphics-initialized = { type = "exec", exec = "mako" }
|
||||||
|
|
||||||
|
[shortcuts]
|
||||||
|
alt-h = "focus-left"
|
||||||
|
alt-j = "focus-down"
|
||||||
|
alt-k = "focus-up"
|
||||||
|
alt-l = "focus-right"
|
||||||
|
|
||||||
|
alt-shift-h = "move-left"
|
||||||
|
alt-shift-j = "move-down"
|
||||||
|
alt-shift-k = "move-up"
|
||||||
|
alt-shift-l = "move-right"
|
||||||
|
|
||||||
|
alt-d = "split-horizontal"
|
||||||
|
alt-v = "split-vertical"
|
||||||
|
|
||||||
|
alt-t = "toggle-split"
|
||||||
|
alt-m = "toggle-mono"
|
||||||
|
alt-u = "toggle-fullscreen"
|
||||||
|
|
||||||
|
alt-f = "focus-parent"
|
||||||
|
alt-shift-c = "close"
|
||||||
|
alt-shift-f = "toggle-floating"
|
||||||
|
Super_L = { type = "exec", exec = "alacritty" }
|
||||||
|
alt-p = { type = "exec", exec = "bemenu-run" }
|
||||||
|
alt-q = "quit"
|
||||||
|
alt-shift-r = "reload-config-toml"
|
||||||
|
|
||||||
|
ctrl-alt-F1 = { type = "switch-to-vt", num = 1 }
|
||||||
|
ctrl-alt-F2 = { type = "switch-to-vt", num = 2 }
|
||||||
|
ctrl-alt-F3 = { type = "switch-to-vt", num = 3 }
|
||||||
|
ctrl-alt-F4 = { type = "switch-to-vt", num = 4 }
|
||||||
|
ctrl-alt-F5 = { type = "switch-to-vt", num = 5 }
|
||||||
|
ctrl-alt-F6 = { type = "switch-to-vt", num = 6 }
|
||||||
|
ctrl-alt-F7 = { type = "switch-to-vt", num = 7 }
|
||||||
|
ctrl-alt-F8 = { type = "switch-to-vt", num = 8 }
|
||||||
|
ctrl-alt-F9 = { type = "switch-to-vt", num = 9 }
|
||||||
|
ctrl-alt-F10 = { type = "switch-to-vt", num = 10 }
|
||||||
|
ctrl-alt-F11 = { type = "switch-to-vt", num = 11 }
|
||||||
|
ctrl-alt-F12 = { type = "switch-to-vt", num = 12 }
|
||||||
|
|
||||||
|
alt-F1 = { type = "show-workspace", name = "1" }
|
||||||
|
alt-F2 = { type = "show-workspace", name = "2" }
|
||||||
|
alt-F3 = { type = "show-workspace", name = "3" }
|
||||||
|
alt-F4 = { type = "show-workspace", name = "4" }
|
||||||
|
alt-F5 = { type = "show-workspace", name = "5" }
|
||||||
|
alt-F6 = { type = "show-workspace", name = "6" }
|
||||||
|
alt-F7 = { type = "show-workspace", name = "7" }
|
||||||
|
alt-F8 = { type = "show-workspace", name = "8" }
|
||||||
|
alt-F9 = { type = "show-workspace", name = "9" }
|
||||||
|
alt-F10 = { type = "show-workspace", name = "10" }
|
||||||
|
alt-F11 = { type = "show-workspace", name = "11" }
|
||||||
|
alt-F12 = { type = "show-workspace", name = "12" }
|
||||||
|
|
||||||
|
alt-shift-F1 = { type = "move-to-workspace", name = "1" }
|
||||||
|
alt-shift-F2 = { type = "move-to-workspace", name = "2" }
|
||||||
|
alt-shift-F3 = { type = "move-to-workspace", name = "3" }
|
||||||
|
alt-shift-F4 = { type = "move-to-workspace", name = "4" }
|
||||||
|
alt-shift-F5 = { type = "move-to-workspace", name = "5" }
|
||||||
|
alt-shift-F6 = { type = "move-to-workspace", name = "6" }
|
||||||
|
alt-shift-F7 = { type = "move-to-workspace", name = "7" }
|
||||||
|
alt-shift-F8 = { type = "move-to-workspace", name = "8" }
|
||||||
|
alt-shift-F9 = { type = "move-to-workspace", name = "9" }
|
||||||
|
alt-shift-F10 = { type = "move-to-workspace", name = "10" }
|
||||||
|
alt-shift-F11 = { type = "move-to-workspace", name = "11" }
|
||||||
|
alt-shift-F12 = { type = "move-to-workspace", name = "12" }
|
||||||
819
toml-config/src/lib.rs
Normal file
819
toml-config/src/lib.rs
Normal file
|
|
@ -0,0 +1,819 @@
|
||||||
|
#![allow(clippy::len_zero, clippy::single_char_pattern, clippy::collapsible_if)]
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod toml;
|
||||||
|
|
||||||
|
use {
|
||||||
|
crate::config::{
|
||||||
|
parse_config, Action, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||||
|
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
|
||||||
|
SimpleCommand, Status, Theme,
|
||||||
|
},
|
||||||
|
ahash::{AHashMap, AHashSet},
|
||||||
|
error_reporter::Report,
|
||||||
|
jay_config::{
|
||||||
|
config, config_dir,
|
||||||
|
exec::{set_env, unset_env, Command},
|
||||||
|
get_workspace,
|
||||||
|
input::{get_seat, input_devices, on_new_input_device, InputDevice, Seat},
|
||||||
|
is_reload,
|
||||||
|
keyboard::{Keymap, ModifiedKeySym},
|
||||||
|
logging::set_log_level,
|
||||||
|
on_devices_enumerated, on_idle, quit, reload, set_default_workspace_capture, set_idle,
|
||||||
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
|
switch_to_vt,
|
||||||
|
theme::{reset_colors, reset_font, reset_sizes, set_font},
|
||||||
|
video::{
|
||||||
|
connectors, drm_devices, on_connector_connected, on_graphics_initialized,
|
||||||
|
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
|
||||||
|
Connector, DrmDevice,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn default_seat() -> Seat {
|
||||||
|
get_seat("default")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
fn into_fn(self, state: &Rc<State>) -> Box<dyn FnMut()> {
|
||||||
|
let s = state.persistent.seat;
|
||||||
|
match self {
|
||||||
|
Action::SimpleCommand { cmd } => match cmd {
|
||||||
|
SimpleCommand::Focus(dir) => Box::new(move || s.focus(dir)),
|
||||||
|
SimpleCommand::Move(dir) => Box::new(move || s.move_(dir)),
|
||||||
|
SimpleCommand::Split(axis) => Box::new(move || s.create_split(axis)),
|
||||||
|
SimpleCommand::ToggleSplit => Box::new(move || s.toggle_split()),
|
||||||
|
SimpleCommand::ToggleMono => Box::new(move || s.toggle_mono()),
|
||||||
|
SimpleCommand::ToggleFullscreen => Box::new(move || s.toggle_fullscreen()),
|
||||||
|
SimpleCommand::FocusParent => Box::new(move || s.focus_parent()),
|
||||||
|
SimpleCommand::Close => Box::new(move || s.close()),
|
||||||
|
SimpleCommand::DisablePointerConstraint => {
|
||||||
|
Box::new(move || s.disable_pointer_constraint())
|
||||||
|
}
|
||||||
|
SimpleCommand::ToggleFloating => Box::new(move || s.toggle_floating()),
|
||||||
|
SimpleCommand::Quit => Box::new(quit),
|
||||||
|
SimpleCommand::ReloadConfigToml => {
|
||||||
|
let persistent = state.persistent.clone();
|
||||||
|
Box::new(move || load_config(false, &persistent))
|
||||||
|
}
|
||||||
|
SimpleCommand::ReloadConfigSo => Box::new(reload),
|
||||||
|
SimpleCommand::None => Box::new(|| ()),
|
||||||
|
},
|
||||||
|
Action::Multi { actions } => {
|
||||||
|
let mut actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||||
|
Box::new(move || {
|
||||||
|
for action in &mut actions {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Action::Exec { exec } => Box::new(move || create_command(&exec).spawn()),
|
||||||
|
Action::SwitchToVt { num } => Box::new(move || switch_to_vt(num)),
|
||||||
|
Action::ShowWorkspace { name } => {
|
||||||
|
let workspace = get_workspace(&name);
|
||||||
|
Box::new(move || s.show_workspace(workspace))
|
||||||
|
}
|
||||||
|
Action::ConfigureConnector { con } => Box::new(move || {
|
||||||
|
for c in connectors() {
|
||||||
|
if con.match_.matches(c) {
|
||||||
|
con.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Action::ConfigureInput { input } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || {
|
||||||
|
for c in input_devices() {
|
||||||
|
if input.match_.matches(c, &state) {
|
||||||
|
input.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Action::ConfigureOutput { out } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || {
|
||||||
|
for c in connectors() {
|
||||||
|
if out.match_.matches(c, &state) {
|
||||||
|
out.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Action::SetEnv { env } => Box::new(move || {
|
||||||
|
for (k, v) in &env {
|
||||||
|
set_env(k, v);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Action::UnsetEnv { env } => Box::new(move || {
|
||||||
|
for k in &env {
|
||||||
|
unset_env(k);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Action::SetKeymap { map } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || state.set_keymap(&map))
|
||||||
|
}
|
||||||
|
Action::SetStatus { status } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || state.set_status(&status))
|
||||||
|
}
|
||||||
|
Action::SetTheme { theme } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || state.apply_theme(&theme))
|
||||||
|
}
|
||||||
|
Action::SetLogLevel { level } => Box::new(move || set_log_level(level)),
|
||||||
|
Action::SetGfxApi { api } => Box::new(move || set_gfx_api(api)),
|
||||||
|
Action::ConfigureDirectScanout { enabled } => {
|
||||||
|
Box::new(move || set_direct_scanout_enabled(enabled))
|
||||||
|
}
|
||||||
|
Action::ConfigureDrmDevice { dev } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || {
|
||||||
|
for d in drm_devices() {
|
||||||
|
if dev.match_.matches(d, &state) {
|
||||||
|
dev.apply(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Action::SetRenderDevice { dev } => {
|
||||||
|
let state = state.clone();
|
||||||
|
Box::new(move || {
|
||||||
|
for d in drm_devices() {
|
||||||
|
if dev.matches(d, &state) {
|
||||||
|
d.make_render_device();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Action::ConfigureIdle { idle } => Box::new(move || set_idle(Some(idle))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_recursive_match<'a, U>(
|
||||||
|
type_name: &str,
|
||||||
|
list: &'a AHashMap<String, U>,
|
||||||
|
active: &mut AHashSet<&'a str>,
|
||||||
|
name: &'a str,
|
||||||
|
matches: impl FnOnce(&'a U, &mut AHashSet<&'a str>) -> bool,
|
||||||
|
) -> bool {
|
||||||
|
match list.get(name) {
|
||||||
|
None => {
|
||||||
|
log::warn!("{type_name} with name {name} does not exist");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Some(m) => {
|
||||||
|
if active.insert(name) {
|
||||||
|
let matches = matches(m, active);
|
||||||
|
active.remove(name);
|
||||||
|
matches
|
||||||
|
} else {
|
||||||
|
log::warn!("Recursion while evaluating match for {type_name} {name}");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigDrmDevice {
|
||||||
|
fn apply(&self, d: DrmDevice) {
|
||||||
|
if let Some(api) = self.gfx_api {
|
||||||
|
d.set_gfx_api(api);
|
||||||
|
}
|
||||||
|
if let Some(dse) = self.direct_scanout_enabled {
|
||||||
|
d.set_direct_scanout_enabled(dse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DrmDeviceMatch {
|
||||||
|
fn matches(&self, d: DrmDevice, state: &State) -> bool {
|
||||||
|
self.matches_(d, state, &mut AHashSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_<'a>(
|
||||||
|
&'a self,
|
||||||
|
d: DrmDevice,
|
||||||
|
state: &'a State,
|
||||||
|
active: &mut AHashSet<&'a str>,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
DrmDeviceMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
|
||||||
|
DrmDeviceMatch::All {
|
||||||
|
name,
|
||||||
|
syspath,
|
||||||
|
vendor,
|
||||||
|
vendor_name,
|
||||||
|
model,
|
||||||
|
model_name,
|
||||||
|
devnode,
|
||||||
|
} => {
|
||||||
|
if let Some(name) = name {
|
||||||
|
let matches = apply_recursive_match(
|
||||||
|
"drm device",
|
||||||
|
&state.drm_devices,
|
||||||
|
active,
|
||||||
|
name,
|
||||||
|
|m, active| m.matches_(d, state, active),
|
||||||
|
);
|
||||||
|
if !matches {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(syspath) = syspath {
|
||||||
|
if d.syspath() != *syspath {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(devnode) = devnode {
|
||||||
|
if d.devnode() != *devnode {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(model) = model_name {
|
||||||
|
if d.model() != *model {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(vendor) = vendor_name {
|
||||||
|
if d.vendor() != *vendor {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(vendor) = vendor {
|
||||||
|
if d.pci_id().vendor != *vendor {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(model) = model {
|
||||||
|
if d.pci_id().model != *model {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMatch {
|
||||||
|
fn matches(&self, d: InputDevice, state: &State) -> bool {
|
||||||
|
self.matches_(d, state, &mut AHashSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_<'a>(
|
||||||
|
&'a self,
|
||||||
|
d: InputDevice,
|
||||||
|
state: &'a State,
|
||||||
|
active: &mut AHashSet<&'a str>,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
InputMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
|
||||||
|
InputMatch::All {
|
||||||
|
tag,
|
||||||
|
name,
|
||||||
|
syspath,
|
||||||
|
devnode,
|
||||||
|
is_keyboard,
|
||||||
|
is_pointer,
|
||||||
|
is_touch,
|
||||||
|
is_tablet_tool,
|
||||||
|
is_tablet_pad,
|
||||||
|
is_gesture,
|
||||||
|
is_switch,
|
||||||
|
} => {
|
||||||
|
if let Some(name) = name {
|
||||||
|
if d.name() != *name {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
let matches = apply_recursive_match(
|
||||||
|
"input device",
|
||||||
|
&state.input_devices,
|
||||||
|
active,
|
||||||
|
tag,
|
||||||
|
|m, active| m.matches_(d, state, active),
|
||||||
|
);
|
||||||
|
if !matches {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(syspath) = syspath {
|
||||||
|
if d.syspath() != *syspath {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(devnode) = devnode {
|
||||||
|
if d.devnode() != *devnode {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! check_cap {
|
||||||
|
($is:expr, $cap:ident) => {
|
||||||
|
if let Some(is) = *$is {
|
||||||
|
if d.has_capability(jay_config::input::capability::$cap) != is {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
check_cap!(is_keyboard, CAP_KEYBOARD);
|
||||||
|
check_cap!(is_pointer, CAP_POINTER);
|
||||||
|
check_cap!(is_touch, CAP_TOUCH);
|
||||||
|
check_cap!(is_tablet_tool, CAP_TABLET_TOOL);
|
||||||
|
check_cap!(is_tablet_pad, CAP_TABLET_PAD);
|
||||||
|
check_cap!(is_gesture, CAP_GESTURE);
|
||||||
|
check_cap!(is_switch, CAP_SWITCH);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
fn apply(&self, c: InputDevice) {
|
||||||
|
if let Some(v) = self.accel_profile {
|
||||||
|
c.set_accel_profile(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.accel_speed {
|
||||||
|
c.set_accel_speed(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.tap_enabled {
|
||||||
|
c.set_tap_enabled(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.tap_drag_enabled {
|
||||||
|
c.set_drag_enabled(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.tap_drag_lock_enabled {
|
||||||
|
c.set_drag_lock_enabled(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.left_handed {
|
||||||
|
c.set_left_handed(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.natural_scrolling {
|
||||||
|
c.set_natural_scrolling_enabled(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.px_per_wheel_scroll {
|
||||||
|
c.set_px_per_wheel_scroll(v);
|
||||||
|
}
|
||||||
|
if let Some(v) = self.transform_matrix {
|
||||||
|
c.set_transform_matrix(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputMatch {
|
||||||
|
fn matches(&self, c: Connector, state: &State) -> bool {
|
||||||
|
if !c.connected() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.matches_(c, state, &mut AHashSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_<'a>(
|
||||||
|
&'a self,
|
||||||
|
c: Connector,
|
||||||
|
state: &'a State,
|
||||||
|
active: &mut AHashSet<&'a str>,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
OutputMatch::Any(m) => m.iter().any(|m| m.matches_(c, state, active)),
|
||||||
|
OutputMatch::All {
|
||||||
|
name,
|
||||||
|
connector,
|
||||||
|
serial_number,
|
||||||
|
manufacturer,
|
||||||
|
model,
|
||||||
|
} => {
|
||||||
|
if let Some(name) = name {
|
||||||
|
let matches = apply_recursive_match(
|
||||||
|
"output",
|
||||||
|
&state.outputs,
|
||||||
|
active,
|
||||||
|
name,
|
||||||
|
|m, active| m.matches_(c, state, active),
|
||||||
|
);
|
||||||
|
if !matches {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(connector) = &connector {
|
||||||
|
if c.name() != *connector {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(serial_number) = &serial_number {
|
||||||
|
if c.serial_number() != *serial_number {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(manufacturer) = &manufacturer {
|
||||||
|
if c.manufacturer() != *manufacturer {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(model) = &model {
|
||||||
|
if c.model() != *model {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectorMatch {
|
||||||
|
fn matches(&self, c: Connector) -> bool {
|
||||||
|
if !c.exists() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
ConnectorMatch::Any(m) => m.iter().any(|m| m.matches(c)),
|
||||||
|
ConnectorMatch::All { connector } => {
|
||||||
|
if let Some(connector) = &connector {
|
||||||
|
if c.name() != *connector {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigConnector {
|
||||||
|
fn apply(&self, c: Connector) {
|
||||||
|
c.set_enabled(self.enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
fn apply(&self, c: Connector) {
|
||||||
|
if self.x.is_some() || self.y.is_some() {
|
||||||
|
let (old_x, old_y) = c.position();
|
||||||
|
c.set_position(self.x.unwrap_or(old_x), self.y.unwrap_or(old_y));
|
||||||
|
}
|
||||||
|
if let Some(scale) = self.scale {
|
||||||
|
c.set_scale(scale);
|
||||||
|
}
|
||||||
|
if let Some(transform) = self.transform {
|
||||||
|
c.set_transform(transform);
|
||||||
|
}
|
||||||
|
if let Some(mode) = &self.mode {
|
||||||
|
let modes = c.modes();
|
||||||
|
let m = modes.iter().find(|m| {
|
||||||
|
if m.width() != mode.width || m.height() != mode.height {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match mode.refresh_rate {
|
||||||
|
None => true,
|
||||||
|
Some(rr) => m.refresh_rate() as f64 / 1000.0 == rr,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
match m {
|
||||||
|
None => {
|
||||||
|
log::warn!("Output {} does not support mode {mode}", c.name());
|
||||||
|
}
|
||||||
|
Some(m) => c.set_mode(m.width(), m.height(), Some(m.refresh_rate())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
outputs: AHashMap<String, OutputMatch>,
|
||||||
|
drm_devices: AHashMap<String, DrmDeviceMatch>,
|
||||||
|
input_devices: AHashMap<String, InputMatch>,
|
||||||
|
persistent: Rc<PersistentState>,
|
||||||
|
keymaps: AHashMap<String, Keymap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for State {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for keymap in self.keymaps.values() {
|
||||||
|
keymap.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn unbind_all(&self) {
|
||||||
|
let mut binds = self.persistent.binds.borrow_mut();
|
||||||
|
for bind in binds.drain() {
|
||||||
|
self.persistent.seat.unbind(bind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_shortcuts(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
shortcuts: impl IntoIterator<Item = (ModifiedKeySym, Action)>,
|
||||||
|
) {
|
||||||
|
let mut binds = self.persistent.binds.borrow_mut();
|
||||||
|
for (key, value) in shortcuts {
|
||||||
|
if let Action::SimpleCommand {
|
||||||
|
cmd: SimpleCommand::None,
|
||||||
|
} = value
|
||||||
|
{
|
||||||
|
self.persistent.seat.unbind(key);
|
||||||
|
binds.remove(&key);
|
||||||
|
} else {
|
||||||
|
self.persistent.seat.bind(key, value.into_fn(self));
|
||||||
|
binds.insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_keymap(&self, map: &ConfigKeymap) {
|
||||||
|
let map = match map {
|
||||||
|
ConfigKeymap::Named(n) => match self.keymaps.get(n) {
|
||||||
|
None => {
|
||||||
|
log::warn!("Unknown keymap {n}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(m) => *m,
|
||||||
|
},
|
||||||
|
ConfigKeymap::Defined { map, .. } => *map,
|
||||||
|
ConfigKeymap::Literal(map) => *map,
|
||||||
|
};
|
||||||
|
self.persistent.seat.set_keymap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_status(&self, status: &Option<Status>) {
|
||||||
|
set_status("");
|
||||||
|
match status {
|
||||||
|
None => unset_status_command(),
|
||||||
|
Some(s) => {
|
||||||
|
set_i3bar_separator(s.separator.as_deref().unwrap_or(" | "));
|
||||||
|
set_status_command(s.format, create_command(&s.exec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_theme(&self, theme: &Theme) {
|
||||||
|
use jay_config::theme::{colors::*, sized::*};
|
||||||
|
macro_rules! color {
|
||||||
|
($colorable:ident, $field:ident) => {
|
||||||
|
if let Some(color) = theme.$field {
|
||||||
|
$colorable.set_color(color)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
color!(
|
||||||
|
ATTENTION_REQUESTED_BACKGROUND_COLOR,
|
||||||
|
attention_requested_bg_color
|
||||||
|
);
|
||||||
|
color!(BACKGROUND_COLOR, bg_color);
|
||||||
|
color!(BAR_BACKGROUND_COLOR, bar_bg_color);
|
||||||
|
color!(BAR_STATUS_TEXT_COLOR, bar_status_text_color);
|
||||||
|
color!(BORDER_COLOR, border_color);
|
||||||
|
color!(
|
||||||
|
CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
|
||||||
|
captured_focused_title_bg_color
|
||||||
|
);
|
||||||
|
color!(
|
||||||
|
CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
|
||||||
|
captured_unfocused_title_bg_color
|
||||||
|
);
|
||||||
|
color!(
|
||||||
|
FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
|
||||||
|
focused_inactive_title_bg_color
|
||||||
|
);
|
||||||
|
color!(
|
||||||
|
FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
|
||||||
|
focused_inactive_title_text_color
|
||||||
|
);
|
||||||
|
color!(FOCUSED_TITLE_BACKGROUND_COLOR, focused_title_bg_color);
|
||||||
|
color!(FOCUSED_TITLE_TEXT_COLOR, focused_title_text_color);
|
||||||
|
color!(SEPARATOR_COLOR, separator_color);
|
||||||
|
color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
|
||||||
|
color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
|
||||||
|
macro_rules! size {
|
||||||
|
($sized:ident, $field:ident) => {
|
||||||
|
if let Some(size) = theme.$field {
|
||||||
|
$sized.set(size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
size!(BORDER_WIDTH, border_width);
|
||||||
|
size!(TITLE_HEIGHT, title_height);
|
||||||
|
if let Some(font) = &theme.font {
|
||||||
|
set_font(font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
struct OutputId {
|
||||||
|
manufacturer: String,
|
||||||
|
model: String,
|
||||||
|
serial_number: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PersistentState {
|
||||||
|
seen_outputs: RefCell<AHashSet<OutputId>>,
|
||||||
|
default: Config,
|
||||||
|
seat: Seat,
|
||||||
|
binds: RefCell<AHashSet<ModifiedKeySym>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
|
let mut path = PathBuf::from(config_dir());
|
||||||
|
path.push("config.toml");
|
||||||
|
let config = match std::fs::read(&path) {
|
||||||
|
Ok(input) => match parse_config(&input, |e| {
|
||||||
|
log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
|
||||||
|
}) {
|
||||||
|
None if initial_load => {
|
||||||
|
log::warn!("Using default config instead");
|
||||||
|
persistent.default.clone()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::warn!("Ignoring config reload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(c) => c,
|
||||||
|
},
|
||||||
|
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||||
|
log::info!("{} does not exist. Using default config.", path.display());
|
||||||
|
persistent.default.clone()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("Could not load {}: {}", path.display(), Report::new(e));
|
||||||
|
log::warn!("Ignoring config reload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut outputs = AHashMap::new();
|
||||||
|
for output in &config.outputs {
|
||||||
|
if let Some(name) = &output.name {
|
||||||
|
let prev = outputs.insert(name.clone(), output.match_.clone());
|
||||||
|
if prev.is_some() {
|
||||||
|
log::warn!("Duplicate output name {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut keymaps = AHashMap::new();
|
||||||
|
for keymap in config.keymaps {
|
||||||
|
match keymap {
|
||||||
|
ConfigKeymap::Defined { name, map } => {
|
||||||
|
keymaps.insert(name, map);
|
||||||
|
}
|
||||||
|
_ => log::warn!("Keymap is not in defined form in top-level context"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut input_devices = AHashMap::new();
|
||||||
|
for input in &config.inputs {
|
||||||
|
if let Some(tag) = &input.tag {
|
||||||
|
let prev = input_devices.insert(tag.clone(), input.match_.clone());
|
||||||
|
if prev.is_some() {
|
||||||
|
log::warn!("Duplicate input tag {tag}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut named_drm_device = AHashMap::new();
|
||||||
|
for drm_device in &config.drm_devices {
|
||||||
|
if let Some(name) = &drm_device.name {
|
||||||
|
let prev = named_drm_device.insert(name.clone(), drm_device.match_.clone());
|
||||||
|
if prev.is_some() {
|
||||||
|
log::warn!("Duplicate drm device name {name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let state = Rc::new(State {
|
||||||
|
outputs,
|
||||||
|
drm_devices: named_drm_device,
|
||||||
|
input_devices,
|
||||||
|
persistent: persistent.clone(),
|
||||||
|
keymaps,
|
||||||
|
});
|
||||||
|
state.set_status(&config.status);
|
||||||
|
match config.on_graphics_initialized {
|
||||||
|
None => on_graphics_initialized(|| ()),
|
||||||
|
Some(a) => on_graphics_initialized(a.into_fn(&state)),
|
||||||
|
}
|
||||||
|
match config.on_idle {
|
||||||
|
None => on_idle(|| ()),
|
||||||
|
Some(a) => on_idle(a.into_fn(&state)),
|
||||||
|
}
|
||||||
|
state.unbind_all();
|
||||||
|
state.apply_shortcuts(config.shortcuts);
|
||||||
|
if let Some(keymap) = config.keymap {
|
||||||
|
state.set_keymap(&keymap);
|
||||||
|
}
|
||||||
|
on_new_connector(move |c| {
|
||||||
|
for connector in &config.connectors {
|
||||||
|
if connector.match_.matches(c) {
|
||||||
|
connector.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_connector_connected({
|
||||||
|
let state = state.clone();
|
||||||
|
move |c| {
|
||||||
|
let id = OutputId {
|
||||||
|
manufacturer: c.manufacturer(),
|
||||||
|
model: c.model(),
|
||||||
|
serial_number: c.serial_number(),
|
||||||
|
};
|
||||||
|
if state.persistent.seen_outputs.borrow_mut().insert(id) {
|
||||||
|
for output in &config.outputs {
|
||||||
|
if output.match_.matches(c, &state) {
|
||||||
|
output.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set_default_workspace_capture(config.workspace_capture);
|
||||||
|
for (k, v) in config.env {
|
||||||
|
set_env(&k, &v);
|
||||||
|
}
|
||||||
|
if initial_load && !is_reload() {
|
||||||
|
if let Some(on_startup) = config.on_startup {
|
||||||
|
on_startup.into_fn(&state)();
|
||||||
|
}
|
||||||
|
if let Some(level) = config.log_level {
|
||||||
|
set_log_level(level);
|
||||||
|
}
|
||||||
|
if let Some(idle) = config.idle {
|
||||||
|
set_idle(Some(idle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on_devices_enumerated({
|
||||||
|
let state = state.clone();
|
||||||
|
move || {
|
||||||
|
if let Some(dev) = config.render_device {
|
||||||
|
for d in drm_devices() {
|
||||||
|
if dev.matches(d, &state) {
|
||||||
|
d.make_render_device();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reset_colors();
|
||||||
|
reset_font();
|
||||||
|
reset_sizes();
|
||||||
|
state.apply_theme(&config.theme);
|
||||||
|
if let Some(api) = config.gfx_api {
|
||||||
|
set_gfx_api(api);
|
||||||
|
}
|
||||||
|
if let Some(dse) = config.direct_scanout_enabled {
|
||||||
|
set_direct_scanout_enabled(dse);
|
||||||
|
}
|
||||||
|
on_new_drm_device({
|
||||||
|
let state = state.clone();
|
||||||
|
move |d| {
|
||||||
|
for dev in &config.drm_devices {
|
||||||
|
if dev.match_.matches(d, &state) {
|
||||||
|
dev.apply(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_new_input_device({
|
||||||
|
let state = state.clone();
|
||||||
|
move |c| {
|
||||||
|
for input in &config.inputs {
|
||||||
|
if input.match_.matches(c, &state) {
|
||||||
|
input.apply(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_command(exec: &Exec) -> Command {
|
||||||
|
let mut command = Command::new(&exec.prog);
|
||||||
|
for arg in &exec.args {
|
||||||
|
command.arg(arg);
|
||||||
|
}
|
||||||
|
for (k, v) in &exec.envs {
|
||||||
|
command.env(k, v);
|
||||||
|
}
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT: &[u8] = include_bytes!("default-config.toml");
|
||||||
|
|
||||||
|
pub fn configure() {
|
||||||
|
let default = parse_config(DEFAULT, |e| {
|
||||||
|
panic!("Could not parse the default config: {}", Report::new(e))
|
||||||
|
});
|
||||||
|
let persistent = Rc::new(PersistentState {
|
||||||
|
seen_outputs: Default::default(),
|
||||||
|
default: default.unwrap(),
|
||||||
|
seat: default_seat(),
|
||||||
|
binds: Default::default(),
|
||||||
|
});
|
||||||
|
load_config(true, &persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
config!(configure);
|
||||||
6
toml-config/src/toml.rs
Normal file
6
toml-config/src/toml.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
mod toml_lexer;
|
||||||
|
pub mod toml_parser;
|
||||||
|
pub mod toml_span;
|
||||||
|
pub mod toml_value;
|
||||||
133
toml-config/src/toml/tests.rs
Normal file
133
toml-config/src/toml/tests.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
config::error::SpannedError,
|
||||||
|
toml::{
|
||||||
|
toml_parser::{parse, ErrorHandler, ParserError},
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bstr::{BStr, ByteSlice},
|
||||||
|
std::{
|
||||||
|
convert::Infallible,
|
||||||
|
os::unix::ffi::OsStrExt,
|
||||||
|
panic::{catch_unwind, AssertUnwindSafe},
|
||||||
|
str::FromStr,
|
||||||
|
},
|
||||||
|
walkdir::WalkDir,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
let mut have_failures = false;
|
||||||
|
let mut num = 0;
|
||||||
|
for path in WalkDir::new("./toml-test/tests/valid") {
|
||||||
|
let path = path.unwrap();
|
||||||
|
if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") {
|
||||||
|
num += 1;
|
||||||
|
let res = catch_unwind(AssertUnwindSafe(|| {
|
||||||
|
have_failures |= run_test(prefix.as_bstr());
|
||||||
|
}));
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("panic while running {}", prefix.as_bstr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if have_failures {
|
||||||
|
panic!("There were test failures");
|
||||||
|
}
|
||||||
|
eprintln!("ran {num} tests");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_test(prefix: &BStr) -> bool {
|
||||||
|
let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap();
|
||||||
|
let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap();
|
||||||
|
|
||||||
|
let json: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||||
|
let json_as_toml = json_to_value(json);
|
||||||
|
let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix, &toml)) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("toml could not be parsed in test {}", prefix);
|
||||||
|
NoErrorHandler(prefix, &toml).handle(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if toml != json_as_toml {
|
||||||
|
eprintln!("toml and json differ in test {}", prefix);
|
||||||
|
eprintln!("toml: {:#?}", toml);
|
||||||
|
eprintln!("json: {:#?}", json_as_toml);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_to_value(json: serde_json::Value) -> Spanned<Value> {
|
||||||
|
let span = Span { lo: 0, hi: 0 };
|
||||||
|
let val = match json {
|
||||||
|
serde_json::Value::String(_)
|
||||||
|
| serde_json::Value::Number(_)
|
||||||
|
| serde_json::Value::Null
|
||||||
|
| serde_json::Value::Bool(_) => panic!("Unexpected type"),
|
||||||
|
serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()),
|
||||||
|
serde_json::Value::Object(v) => {
|
||||||
|
if v.len() == 2 && v.contains_key("type") && v.contains_key("value") {
|
||||||
|
let ty = v.get("type").unwrap().as_str().unwrap();
|
||||||
|
let val = v.get("value").unwrap().as_str().unwrap();
|
||||||
|
match ty {
|
||||||
|
"string" => Value::String(val.to_owned()),
|
||||||
|
"integer" => Value::Integer(i64::from_str(val).unwrap()),
|
||||||
|
"float" => Value::Float(f64::from_str(val).unwrap()),
|
||||||
|
"bool" => Value::Boolean(bool::from_str(val).unwrap()),
|
||||||
|
_ => panic!("unexpected type {}", ty),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Table(
|
||||||
|
v.into_iter()
|
||||||
|
.map(|(k, v)| (k.spanned(span), json_to_value(v)))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
val.spanned(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoErrorHandler<'a>(&'a BStr, &'a [u8]);
|
||||||
|
|
||||||
|
impl<'a> ErrorHandler for NoErrorHandler<'a> {
|
||||||
|
fn handle(&self, err: Spanned<ParserError>) {
|
||||||
|
eprintln!(
|
||||||
|
"{}: An error occurred during validation: {}",
|
||||||
|
self.0,
|
||||||
|
SpannedError {
|
||||||
|
input: self.1.into(),
|
||||||
|
span: err.span,
|
||||||
|
cause: Some(err.value),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
|
||||||
|
eprintln!(
|
||||||
|
"{}: Redefinition: {}",
|
||||||
|
self.0,
|
||||||
|
SpannedError {
|
||||||
|
input: self.1.into(),
|
||||||
|
span: err.span,
|
||||||
|
cause: Some(err.value),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
eprintln!(
|
||||||
|
"{}: Previous: {}",
|
||||||
|
self.0,
|
||||||
|
SpannedError {
|
||||||
|
input: self.1.into(),
|
||||||
|
span: prev,
|
||||||
|
cause: None::<Infallible>,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
193
toml-config/src/toml/toml_lexer.rs
Normal file
193
toml-config/src/toml/toml_lexer.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
use crate::toml::toml_span::{Span, Spanned, SpannedExt};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Token<'a> {
|
||||||
|
Dot,
|
||||||
|
Equals,
|
||||||
|
Comma,
|
||||||
|
LeftBracket,
|
||||||
|
RightBracket,
|
||||||
|
LeftBrace,
|
||||||
|
RightBrace,
|
||||||
|
LiteralString(&'a [u8]),
|
||||||
|
CookedString(&'a [u8]),
|
||||||
|
Literal(&'a [u8]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Token<'a> {
|
||||||
|
pub fn name(self, value_context: bool) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Token::Dot => "`.`",
|
||||||
|
Token::Equals => "`=`",
|
||||||
|
Token::Comma => "`,`",
|
||||||
|
Token::LeftBracket => "`[`",
|
||||||
|
Token::RightBracket => "`]`",
|
||||||
|
Token::LeftBrace => "`{`",
|
||||||
|
Token::RightBrace => "`}`",
|
||||||
|
Token::LiteralString(_) | Token::CookedString(_) => "a string",
|
||||||
|
Token::Literal(_) if value_context => "a literal",
|
||||||
|
Token::Literal(_) => "a key",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lexer<'a> {
|
||||||
|
input: &'a [u8],
|
||||||
|
pos: usize,
|
||||||
|
peek: Option<Spanned<Token<'a>>>,
|
||||||
|
peek_value_context: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
pub fn new(input: &'a [u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
input,
|
||||||
|
pos: 0,
|
||||||
|
peek: None,
|
||||||
|
peek_value_context: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pos(&mut self) -> usize {
|
||||||
|
self.skip_ws();
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_ws(&mut self) {
|
||||||
|
while let Some(char) = self.input.get(self.pos).copied() {
|
||||||
|
match char {
|
||||||
|
b' ' | b'\t' | b'\n' => self.pos += 1,
|
||||||
|
b'#' => {
|
||||||
|
self.pos += 1;
|
||||||
|
while let Some(char) = self.input.get(self.pos).copied() {
|
||||||
|
self.pos += 1;
|
||||||
|
if char == b'\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||||
|
let next = self.next(value_context);
|
||||||
|
self.peek = next;
|
||||||
|
self.peek_value_context = value_context;
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||||
|
if let Some(peek) = self.peek.take() {
|
||||||
|
if self.peek_value_context == value_context {
|
||||||
|
return Some(peek);
|
||||||
|
}
|
||||||
|
self.pos = peek.span.lo;
|
||||||
|
}
|
||||||
|
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
|
macro_rules! get {
|
||||||
|
($off:expr) => {
|
||||||
|
self.input.get(self.pos + $off).copied()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.skip_ws();
|
||||||
|
|
||||||
|
let Some(c) = get!(0) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let pos = self.pos;
|
||||||
|
|
||||||
|
macro_rules! span {
|
||||||
|
() => {
|
||||||
|
Span {
|
||||||
|
lo: pos,
|
||||||
|
hi: self.pos,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
'simple: {
|
||||||
|
let t = match c {
|
||||||
|
b'.' => Dot,
|
||||||
|
b',' => Comma,
|
||||||
|
b'=' => Equals,
|
||||||
|
b'[' => LeftBracket,
|
||||||
|
b']' => RightBracket,
|
||||||
|
b'{' => LeftBrace,
|
||||||
|
b'}' => RightBrace,
|
||||||
|
_ => break 'simple,
|
||||||
|
};
|
||||||
|
self.pos += 1;
|
||||||
|
return Some(t.spanned(span!()));
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! try_string {
|
||||||
|
($delim:expr, $escaping:expr, $ident:ident) => {
|
||||||
|
if c == $delim {
|
||||||
|
'ml_string: {
|
||||||
|
let delim = ($delim, Some($delim), Some($delim));
|
||||||
|
if (c, get!(1), get!(2)) != delim {
|
||||||
|
break 'ml_string;
|
||||||
|
}
|
||||||
|
self.pos += 3;
|
||||||
|
if get!(0) == Some(b'\n') {
|
||||||
|
self.pos += 1;
|
||||||
|
}
|
||||||
|
let start = self.pos;
|
||||||
|
let end = loop {
|
||||||
|
let c = match get!(0) {
|
||||||
|
Some(c) => c,
|
||||||
|
_ => break self.pos,
|
||||||
|
};
|
||||||
|
self.pos += 1;
|
||||||
|
if $escaping && c == b'\\' {
|
||||||
|
self.pos += 1;
|
||||||
|
} else if c == $delim {
|
||||||
|
if (c, get!(0), get!(1)) == delim && get!(2) != Some($delim) {
|
||||||
|
self.pos += 2;
|
||||||
|
break self.pos - 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||||
|
}
|
||||||
|
self.pos += 1;
|
||||||
|
let start = self.pos;
|
||||||
|
let end = loop {
|
||||||
|
let c = match get!(0) {
|
||||||
|
Some(c) => c,
|
||||||
|
_ => break self.pos,
|
||||||
|
};
|
||||||
|
self.pos += 1;
|
||||||
|
if $escaping && c == b'\\' {
|
||||||
|
self.pos += 1;
|
||||||
|
} else if c == $delim {
|
||||||
|
break self.pos - 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try_string!(b'\'', false, LiteralString);
|
||||||
|
try_string!(b'"', true, CookedString);
|
||||||
|
|
||||||
|
let start = self.pos;
|
||||||
|
while let Some(c) = get!(0) {
|
||||||
|
match c {
|
||||||
|
b' ' | b'\t' | b'\n' | b'#' | b',' | b'=' | b'{' | b'}' | b'[' | b']' => break,
|
||||||
|
b'.' if !value_context => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.pos += 1;
|
||||||
|
}
|
||||||
|
let end = self.pos;
|
||||||
|
|
||||||
|
Some(Literal(&self.input[start..end]).spanned(span!()))
|
||||||
|
}
|
||||||
|
}
|
||||||
534
toml-config/src/toml/toml_parser.rs
Normal file
534
toml-config/src/toml/toml_parser.rs
Normal file
|
|
@ -0,0 +1,534 @@
|
||||||
|
use {
|
||||||
|
crate::toml::{
|
||||||
|
toml_lexer::{Lexer, Token},
|
||||||
|
toml_span::{Span, Spanned, SpannedExt},
|
||||||
|
toml_value::Value,
|
||||||
|
},
|
||||||
|
bstr::ByteSlice,
|
||||||
|
indexmap::{
|
||||||
|
map::{raw_entry_v1::RawEntryMut, RawEntryApiV1},
|
||||||
|
IndexMap,
|
||||||
|
},
|
||||||
|
std::{collections::VecDeque, mem, str::FromStr},
|
||||||
|
thiserror::Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait ErrorHandler {
|
||||||
|
fn handle(&self, err: Spanned<ParserError>);
|
||||||
|
fn redefinition(&self, err: Spanned<ParserError>, prev: Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ParserError {
|
||||||
|
#[error("Unexpected end of file")]
|
||||||
|
UnexpectedEof,
|
||||||
|
#[error("Expected a key")]
|
||||||
|
MissingKey,
|
||||||
|
#[error("Expected {0} but found {1}")]
|
||||||
|
Expected(&'static str, &'static str),
|
||||||
|
#[error("Duplicate key overwrites the previous definition")]
|
||||||
|
Redefined,
|
||||||
|
#[error("Literal is not valid UTF-8")]
|
||||||
|
NonUtf8Literal,
|
||||||
|
#[error("Could not parse the literal")]
|
||||||
|
UnknownLiteral,
|
||||||
|
#[error("Ignoring key due to following error")]
|
||||||
|
IgnoringKey,
|
||||||
|
#[error("Unnecessary comma")]
|
||||||
|
UnnecessaryComma,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(
|
||||||
|
input: &[u8],
|
||||||
|
error_handler: &dyn ErrorHandler,
|
||||||
|
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let parser = Parser {
|
||||||
|
lexer: Lexer::new(input),
|
||||||
|
error_handler,
|
||||||
|
last_span: None,
|
||||||
|
};
|
||||||
|
parser.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser<'a, 'b> {
|
||||||
|
lexer: Lexer<'a>,
|
||||||
|
error_handler: &'b dyn ErrorHandler,
|
||||||
|
last_span: Option<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key = VecDeque<Spanned<String>>;
|
||||||
|
|
||||||
|
impl<'a, 'b> Parser<'a, 'b> {
|
||||||
|
fn parse(mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
self.parse_document()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unexpected_eof(&self) -> Spanned<ParserError> {
|
||||||
|
let span = self.last_span.unwrap_or(Span { lo: 0, hi: 0 });
|
||||||
|
ParserError::UnexpectedEof.spanned(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||||
|
match self.lexer.next(value_context) {
|
||||||
|
Some(t) => {
|
||||||
|
self.last_span = Some(t.span);
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
_ => Err(self.unexpected_eof()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||||
|
match self.lexer.peek(value_context) {
|
||||||
|
Some(t) => Ok(t),
|
||||||
|
_ => Err(self.unexpected_eof()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_value(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let token = self.peek(true)?;
|
||||||
|
match token.value {
|
||||||
|
Token::LiteralString(s) => self.parse_literal_string(s),
|
||||||
|
Token::CookedString(s) => self.parse_cooked_string(s),
|
||||||
|
Token::LeftBracket => self.parse_array(),
|
||||||
|
Token::Literal(l) => self.parse_literal_value(l),
|
||||||
|
Token::LeftBrace => self.parse_inline_table(),
|
||||||
|
Token::Dot | Token::Equals | Token::Comma | Token::RightBrace | Token::RightBracket => {
|
||||||
|
Err(ParserError::Expected("a value", token.value.name(true)).spanned(token.span))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_literal_value(
|
||||||
|
&mut self,
|
||||||
|
literal: &[u8],
|
||||||
|
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let span = self.next(true)?.span;
|
||||||
|
let Ok(s) = std::str::from_utf8(literal) else {
|
||||||
|
return Err(ParserError::NonUtf8Literal.spanned(span));
|
||||||
|
};
|
||||||
|
if s == "true" {
|
||||||
|
return Ok(Value::Boolean(true).spanned(span));
|
||||||
|
}
|
||||||
|
if s == "false" {
|
||||||
|
return Ok(Value::Boolean(false).spanned(span));
|
||||||
|
}
|
||||||
|
let s = s.replace('_', "");
|
||||||
|
if let Ok(n) = i64::from_str(&s) {
|
||||||
|
return Ok(Value::Integer(n).spanned(span));
|
||||||
|
}
|
||||||
|
'radix: {
|
||||||
|
let b = s.as_bytes();
|
||||||
|
if b.len() >= 2 && b[0] == b'0' {
|
||||||
|
let radix = match b[1] {
|
||||||
|
b'x' => 16,
|
||||||
|
b'o' => 8,
|
||||||
|
b'b' => 2,
|
||||||
|
_ => break 'radix,
|
||||||
|
};
|
||||||
|
if let Ok(n) = i64::from_str_radix(&s[2..], radix) {
|
||||||
|
return Ok(Value::Integer(n).spanned(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(n) = f64::from_str(&s) {
|
||||||
|
return Ok(Value::Float(n).spanned(span));
|
||||||
|
}
|
||||||
|
Err(ParserError::UnknownLiteral.spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_literal_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let span = self.next(true)?.span;
|
||||||
|
let s = s.as_bstr().to_string();
|
||||||
|
Ok(Value::String(s).spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cooked_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let span = self.next(true)?.span;
|
||||||
|
let s = self.cook_string(s);
|
||||||
|
Ok(Value::String(s).spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cook_string(&self, s: &[u8]) -> String {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
if !s.contains(&b'\\') {
|
||||||
|
return s.as_bstr().to_string();
|
||||||
|
}
|
||||||
|
let mut res = vec![];
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos < s.len() {
|
||||||
|
let c = s[pos];
|
||||||
|
pos += 1;
|
||||||
|
match c {
|
||||||
|
b'\\' => {
|
||||||
|
let c = s[pos];
|
||||||
|
pos += 1;
|
||||||
|
match c {
|
||||||
|
b'\\' => res.push(b'\\'),
|
||||||
|
b'"' => res.push(b'"'),
|
||||||
|
b'b' => res.push(0x8),
|
||||||
|
b'f' => res.push(0xc),
|
||||||
|
b'n' => res.push(b'\n'),
|
||||||
|
b'r' => res.push(b'\r'),
|
||||||
|
b't' => res.push(b'\t'),
|
||||||
|
b'e' => res.push(0x1b),
|
||||||
|
b'x' | b'u' | b'U' => 'unicode: {
|
||||||
|
let len = match c {
|
||||||
|
b'x' => 2,
|
||||||
|
b'u' => 4,
|
||||||
|
_ => 8,
|
||||||
|
};
|
||||||
|
if s.len() - pos >= len {
|
||||||
|
if let Ok(s) = std::str::from_utf8(&s[pos..pos + len]) {
|
||||||
|
if let Ok(n) = u32::from_str_radix(s, 16) {
|
||||||
|
if let Some(c) = char::from_u32(n) {
|
||||||
|
pos += len;
|
||||||
|
let _ = write!(res, "{}", c);
|
||||||
|
break 'unicode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.extend_from_slice(&s[pos - 2..]);
|
||||||
|
}
|
||||||
|
b' ' | b'\t' | b'\n' => {
|
||||||
|
let mut t = pos;
|
||||||
|
let mut saw_nl = c == b'\n';
|
||||||
|
while t < s.len() && matches!(s[t], b' ' | b'\t' | b'\n') {
|
||||||
|
saw_nl |= s[t] == b'\n';
|
||||||
|
t += 1;
|
||||||
|
}
|
||||||
|
if saw_nl {
|
||||||
|
pos = t;
|
||||||
|
} else {
|
||||||
|
res.extend_from_slice(&[b'\\', c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
res.extend_from_slice(&[b'\\', c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => res.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.as_bstr().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_array(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let lo = self.next(true)?.span.lo;
|
||||||
|
let mut entries = vec![];
|
||||||
|
let mut consumed_comma = false;
|
||||||
|
loop {
|
||||||
|
if let Some(v) = self.lexer.peek(true) {
|
||||||
|
if v.value == Token::RightBracket {
|
||||||
|
let _ = self.next(true);
|
||||||
|
let hi = v.span.hi;
|
||||||
|
let span = Span { lo, hi };
|
||||||
|
return Ok(Value::Array(entries).spanned(span));
|
||||||
|
}
|
||||||
|
if entries.len() > 0 && !mem::take(&mut consumed_comma) {
|
||||||
|
self.error_handler.handle(
|
||||||
|
ParserError::Expected("`,` or `]`", v.value.name(true)).spanned(v.span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.parse_value() {
|
||||||
|
Ok(v) => {
|
||||||
|
entries.push(v);
|
||||||
|
consumed_comma = self.skip_comma(true);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.skip_tree(Token::LeftBracket, Token::RightBracket);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_inline_table(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let lo = self.next(true)?.span.lo;
|
||||||
|
let mut map = IndexMap::new();
|
||||||
|
let mut consumed_comma = false;
|
||||||
|
loop {
|
||||||
|
let token = match self.peek(false) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
self.error_handler.handle(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if token.value == Token::RightBrace {
|
||||||
|
let _ = self.next(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !map.is_empty() && !mem::take(&mut consumed_comma) {
|
||||||
|
self.error_handler.handle(
|
||||||
|
ParserError::Expected("`,` or `}`", token.value.name(false))
|
||||||
|
.spanned(token.span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let res = match self.parse_key_value_with_recovery() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
self.skip_tree(Token::LeftBrace, Token::RightBrace);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some((mut key, value)) = res {
|
||||||
|
self.insert(&mut map, &mut key, value, false, false);
|
||||||
|
};
|
||||||
|
consumed_comma = self.skip_comma(false);
|
||||||
|
}
|
||||||
|
let hi = self.last_span().hi;
|
||||||
|
let span = Span { lo, hi };
|
||||||
|
Ok(Value::Table(map).spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_comma(&mut self, value_context: bool) -> bool {
|
||||||
|
if let Some(token) = self.lexer.peek(value_context) {
|
||||||
|
if token.value != Token::Comma {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let _ = self.next(value_context);
|
||||||
|
}
|
||||||
|
while let Some(token) = self.lexer.peek(value_context) {
|
||||||
|
if token.value != Token::Comma {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let _ = self.next(value_context);
|
||||||
|
self.error_handler
|
||||||
|
.handle(ParserError::UnnecessaryComma.spanned(token.span));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_document(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||||
|
let mut map = IndexMap::new();
|
||||||
|
self.parse_table_body(&mut map)?;
|
||||||
|
while self.lexer.peek(false).is_some() {
|
||||||
|
let (mut key, append) = self.parse_table_header()?;
|
||||||
|
let mut inner_map = IndexMap::new();
|
||||||
|
self.parse_table_body(&mut inner_map)?;
|
||||||
|
let value = Value::Table(inner_map).spanned(key.span);
|
||||||
|
self.insert(&mut map, &mut key.value, value, true, append);
|
||||||
|
}
|
||||||
|
let hi = self.last_span().hi;
|
||||||
|
let span = Span { lo: 0, hi };
|
||||||
|
Ok(Value::Table(map).spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table_header(&mut self) -> Result<(Spanned<Key>, bool), Spanned<ParserError>> {
|
||||||
|
let lo = self.next(false)?.span.lo;
|
||||||
|
let mut append = false;
|
||||||
|
if let Some(token) = self.lexer.peek(false) {
|
||||||
|
if token.value == Token::LeftBracket {
|
||||||
|
let _ = self.next(false);
|
||||||
|
append = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let key = self.parse_key()?;
|
||||||
|
let mut hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||||
|
if append {
|
||||||
|
hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||||
|
}
|
||||||
|
let span = Span { lo, hi };
|
||||||
|
Ok((key.spanned(span), append))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table_body(
|
||||||
|
&mut self,
|
||||||
|
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
) -> Result<(), Spanned<ParserError>> {
|
||||||
|
while let Some(e) = self.lexer.peek(false) {
|
||||||
|
if e.value == Token::LeftBracket {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let Some((mut key, value)) = self.parse_key_value_with_recovery()? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.insert(dst, &mut key, value, false, false);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(
|
||||||
|
&self,
|
||||||
|
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||||
|
keys: &mut Key,
|
||||||
|
value: Spanned<Value>,
|
||||||
|
modify_array_element: bool,
|
||||||
|
append_last: bool,
|
||||||
|
) {
|
||||||
|
let key = keys.pop_front().unwrap();
|
||||||
|
if keys.is_empty() {
|
||||||
|
if let RawEntryMut::Occupied(mut old) =
|
||||||
|
dst.raw_entry_mut_v1().from_key(key.value.as_str())
|
||||||
|
{
|
||||||
|
if append_last {
|
||||||
|
if let Value::Array(array) = &mut old.get_mut().value {
|
||||||
|
array.push(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Value::Table(old) = &mut old.get_mut().value {
|
||||||
|
if let Value::Table(new) = value.value {
|
||||||
|
for (k, v) in new {
|
||||||
|
let mut keys = Key::new();
|
||||||
|
keys.push_back(k);
|
||||||
|
self.insert(old, &mut keys, v, false, false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.error_handler
|
||||||
|
.redefinition(ParserError::Redefined.spanned(key.span), old.key().span);
|
||||||
|
old.shift_remove();
|
||||||
|
}
|
||||||
|
let span = value.span;
|
||||||
|
let value = match append_last {
|
||||||
|
true => Value::Array(vec![value]).spanned(span),
|
||||||
|
false => value,
|
||||||
|
};
|
||||||
|
dst.insert(key, value);
|
||||||
|
} else {
|
||||||
|
if let RawEntryMut::Occupied(mut o) = dst.raw_entry_mut_v1().from_key(&key) {
|
||||||
|
match &mut o.get_mut().value {
|
||||||
|
Value::Table(dst) => {
|
||||||
|
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Value::Array(array) if modify_array_element => {
|
||||||
|
if let Some(Value::Table(dst)) =
|
||||||
|
array.last_mut().as_mut().map(|v| &mut v.value)
|
||||||
|
{
|
||||||
|
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.error_handler
|
||||||
|
.redefinition(ParserError::Redefined.spanned(key.span), o.key().span);
|
||||||
|
o.shift_remove();
|
||||||
|
}
|
||||||
|
let mut map = IndexMap::new();
|
||||||
|
let span = value.span;
|
||||||
|
self.insert(&mut map, keys, value, modify_array_element, append_last);
|
||||||
|
dst.insert(key, Value::Table(map).spanned(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key_value_with_recovery(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<Option<(Key, Spanned<Value>)>, Spanned<ParserError>> {
|
||||||
|
let pos = self.lexer.pos();
|
||||||
|
match self.parse_key_value() {
|
||||||
|
Ok(kv) => Ok(Some(kv)),
|
||||||
|
Err((e, key)) => {
|
||||||
|
if let Some(key) = key {
|
||||||
|
let span = key.back().unwrap().span;
|
||||||
|
self.error_handler
|
||||||
|
.handle(ParserError::IgnoringKey.spanned(span));
|
||||||
|
}
|
||||||
|
if self.lexer.pos() == pos {
|
||||||
|
Err(e)
|
||||||
|
} else {
|
||||||
|
self.error_handler.handle(e);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn parse_key_value(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<(Key, Spanned<Value>), (Spanned<ParserError>, Option<Key>)> {
|
||||||
|
let key = self.parse_key();
|
||||||
|
let eq = self.parse_exact(Token::Equals, true);
|
||||||
|
let value = self.parse_value();
|
||||||
|
let key = match key {
|
||||||
|
Ok(k) => k,
|
||||||
|
Err(e) => return Err((e, None)),
|
||||||
|
};
|
||||||
|
if let Err(e) = eq {
|
||||||
|
return Err((e, Some(key)));
|
||||||
|
}
|
||||||
|
let value = match value {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Err((e, Some(key))),
|
||||||
|
};
|
||||||
|
Ok((key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key(&mut self) -> Result<Key, Spanned<ParserError>> {
|
||||||
|
let mut parts = Key::new();
|
||||||
|
loop {
|
||||||
|
if parts.len() > 0 {
|
||||||
|
if self.parse_exact(Token::Dot, false).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(token) = self.lexer.peek(false) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let s = match token.value {
|
||||||
|
Token::LiteralString(s) => s.as_bstr().to_string(),
|
||||||
|
Token::CookedString(s) => self.cook_string(s),
|
||||||
|
Token::Literal(l) => l.as_bstr().to_string(),
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
parts.push_back(s.spanned(token.span));
|
||||||
|
let _ = self.next(false);
|
||||||
|
}
|
||||||
|
if parts.is_empty() {
|
||||||
|
Err(ParserError::MissingKey.spanned(self.next_span()))
|
||||||
|
} else {
|
||||||
|
Ok(parts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_exact(
|
||||||
|
&mut self,
|
||||||
|
token: Token<'a>,
|
||||||
|
value_context: bool,
|
||||||
|
) -> Result<Span, Spanned<ParserError>> {
|
||||||
|
let actual = match self.peek(value_context) {
|
||||||
|
Ok(t) if t.value == token => {
|
||||||
|
let _ = self.next(value_context);
|
||||||
|
return Ok(t.span);
|
||||||
|
}
|
||||||
|
Ok(t) => t.value.name(value_context),
|
||||||
|
Err(_) => "end of file",
|
||||||
|
};
|
||||||
|
let span = self.next_span();
|
||||||
|
Err(ParserError::Expected(token.name(value_context), actual).spanned(span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn last_span(&self) -> Span {
|
||||||
|
self.last_span.unwrap_or(Span { lo: 0, hi: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_span(&mut self) -> Span {
|
||||||
|
self.lexer.peek(false).map(|v| v.span).unwrap_or_else(|| {
|
||||||
|
let hi = self.last_span().hi;
|
||||||
|
Span { lo: hi, hi }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_tree(&mut self, start: Token, end: Token) {
|
||||||
|
let mut depth = 1;
|
||||||
|
while let Ok(next) = self.next(false) {
|
||||||
|
if next.value == start {
|
||||||
|
depth += 1;
|
||||||
|
} else if next.value == end {
|
||||||
|
depth -= 1;
|
||||||
|
if depth == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
toml-config/src/toml/toml_span.rs
Normal file
124
toml-config/src/toml/toml_span.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Span {
|
||||||
|
pub lo: usize,
|
||||||
|
pub hi: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Span {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}..{}", self.lo, self.hi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq)]
|
||||||
|
pub struct Spanned<T> {
|
||||||
|
pub span: Span,
|
||||||
|
pub value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Spanned<T> {
|
||||||
|
pub fn as_ref(&self) -> Spanned<&T> {
|
||||||
|
Spanned {
|
||||||
|
span: self.span,
|
||||||
|
value: &self.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
|
||||||
|
Spanned {
|
||||||
|
span: self.span,
|
||||||
|
value: f(self.value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into<U>(self) -> Spanned<U>
|
||||||
|
where
|
||||||
|
T: Into<U>,
|
||||||
|
{
|
||||||
|
Spanned {
|
||||||
|
span: self.span,
|
||||||
|
value: self.value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for Spanned<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.value.fmt(f)?;
|
||||||
|
write!(f, " @ {:?}", self.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.value.eq(&other.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Hash> Hash for Spanned<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.value.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for Spanned<String> {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SpannedExt: Sized {
|
||||||
|
fn spanned(self, span: Span) -> Spanned<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SpannedExt for T {
|
||||||
|
fn spanned(self, span: Span) -> Spanned<Self> {
|
||||||
|
Spanned { span, value: self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DespanExt: Sized {
|
||||||
|
type T;
|
||||||
|
|
||||||
|
fn despan(self) -> Option<Self::T>;
|
||||||
|
fn despan_into<U>(self) -> Option<U>
|
||||||
|
where
|
||||||
|
Self::T: Into<U>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DespanExt for Option<Spanned<T>> {
|
||||||
|
type T = T;
|
||||||
|
|
||||||
|
fn despan(self) -> Option<Self::T> {
|
||||||
|
self.map(|v| v.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn despan_into<U>(self) -> Option<U>
|
||||||
|
where
|
||||||
|
Self::T: Into<U>,
|
||||||
|
{
|
||||||
|
self.map(|v| v.value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SpannedResultExt1: Sized {
|
||||||
|
type T;
|
||||||
|
type E;
|
||||||
|
|
||||||
|
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> SpannedResultExt1 for Result<Spanned<T>, E> {
|
||||||
|
type T = T;
|
||||||
|
type E = E;
|
||||||
|
|
||||||
|
fn map_spanned<U, F: FnOnce(Self::T) -> U>(self, f: F) -> Result<Spanned<U>, Self::E> {
|
||||||
|
self.map(|v| v.map(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
63
toml-config/src/toml/toml_value.rs
Normal file
63
toml-config/src/toml/toml_value.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use {
|
||||||
|
crate::toml::toml_span::Spanned,
|
||||||
|
indexmap::IndexMap,
|
||||||
|
std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum Value {
|
||||||
|
String(String),
|
||||||
|
Integer(i64),
|
||||||
|
Float(f64),
|
||||||
|
Boolean(bool),
|
||||||
|
Array(Vec<Spanned<Value>>),
|
||||||
|
Table(IndexMap<Spanned<String>, Spanned<Value>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Value::String(_) => "a string",
|
||||||
|
Value::Integer(_) => "an integer",
|
||||||
|
Value::Float(_) => "a float",
|
||||||
|
Value::Boolean(_) => "a boolean",
|
||||||
|
Value::Array(_) => "an array",
|
||||||
|
Value::Table(_) => "a table",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Value {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::String(v) => v.fmt(f),
|
||||||
|
Value::Integer(v) => v.fmt(f),
|
||||||
|
Value::Float(v) => v.fmt(f),
|
||||||
|
Value::Boolean(v) => v.fmt(f),
|
||||||
|
Value::Array(v) => v.fmt(f),
|
||||||
|
Value::Table(v) => v.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Value {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Value::String(v1), Value::String(v2)) => v1 == v2,
|
||||||
|
(Value::Integer(v1), Value::Integer(v2)) => v1 == v2,
|
||||||
|
(Value::Float(v1), Value::Float(v2)) => {
|
||||||
|
if v1.is_nan() && v2.is_nan() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
v1.total_cmp(v2) == Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Value::Boolean(v1), Value::Boolean(v2)) => v1 == v2,
|
||||||
|
(Value::Array(v1), Value::Array(v2)) => v1 == v2,
|
||||||
|
(Value::Table(v1), Value::Table(v2)) => v1 == v2,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
toml-spec/Cargo.toml
Normal file
12
toml-spec/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "toml-spec"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
serde_yaml = "0.9.32"
|
||||||
|
anyhow = "1.0.81"
|
||||||
|
indexmap = { version = "2.2.5", features = ["serde"] }
|
||||||
|
error_reporter = "1.0.0"
|
||||||
|
serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
||||||
1100
toml-spec/spec/spec.generated.json
Normal file
1100
toml-spec/spec/spec.generated.json
Normal file
File diff suppressed because it is too large
Load diff
2302
toml-spec/spec/spec.generated.md
Normal file
2302
toml-spec/spec/spec.generated.md
Normal file
File diff suppressed because it is too large
Load diff
1936
toml-spec/spec/spec.yaml
Normal file
1936
toml-spec/spec/spec.yaml
Normal file
File diff suppressed because it is too large
Load diff
10
toml-spec/spec/template.md
Normal file
10
toml-spec/spec/template.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Jay TOML Config
|
||||||
|
|
||||||
|
This document describes the format of the TOML configuration of the Jay compositor.
|
||||||
|
|
||||||
|
A JSON Schema for this format is available at [spec.generated.json](./spec.generated.json). You can include this file in your editor to get auto completion.
|
||||||
|
|
||||||
|
Start at the top-level type: [Config](#types-config).
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
194
toml-spec/src/json_schema.rs
Normal file
194
toml-spec/src/json_schema.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
use {
|
||||||
|
crate::types::{
|
||||||
|
ArraySpec, Described, MapSpec, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
|
||||||
|
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
|
||||||
|
},
|
||||||
|
anyhow::Result,
|
||||||
|
serde_json::{json, Map, Value},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_json_schema(
|
||||||
|
types_sorted: &[(&String, &Described<TopLevelTypeSpec>)],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut types = Map::new();
|
||||||
|
for (name, ty) in types_sorted {
|
||||||
|
types.insert(name.to_string(), create_top_level_schema(ty));
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"$id": "jay_toml_schema",
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$ref": "#/$defs/Config",
|
||||||
|
"$defs": types,
|
||||||
|
});
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&json).unwrap();
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.json"),
|
||||||
|
json.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_top_level_schema(spec: &Described<TopLevelTypeSpec>) -> Value {
|
||||||
|
match &spec.value {
|
||||||
|
TopLevelTypeSpec::Variable { variants } => {
|
||||||
|
let mut cases = vec![];
|
||||||
|
for variant in variants {
|
||||||
|
cases.push(create_variant_schema(&variant.description, &variant.value));
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"description": spec.description,
|
||||||
|
"anyOf": cases,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
TopLevelTypeSpec::Single(variant) => create_variant_schema(&spec.description, variant),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_variant_schema(description: &str, spec: &VariantSpec) -> Value {
|
||||||
|
macro_rules! spec {
|
||||||
|
($v:expr) => {
|
||||||
|
match $v {
|
||||||
|
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
|
||||||
|
RefOrSpec::Spec(s) => s,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match spec {
|
||||||
|
VariantSpec::String(ss) => {
|
||||||
|
let ss = spec!(ss);
|
||||||
|
create_string_spec(description, ss)
|
||||||
|
}
|
||||||
|
VariantSpec::Number(ns) => {
|
||||||
|
let ns = spec!(ns);
|
||||||
|
create_number_spec(description, ns)
|
||||||
|
}
|
||||||
|
VariantSpec::Boolean => create_boolean_spec(description),
|
||||||
|
VariantSpec::Array(s) => {
|
||||||
|
let s = spec!(s);
|
||||||
|
create_array_spec(description, s)
|
||||||
|
}
|
||||||
|
VariantSpec::Table(ts) => {
|
||||||
|
let ts = spec!(ts);
|
||||||
|
match ts {
|
||||||
|
TableSpec::Tagged { types } => {
|
||||||
|
let mut variants = vec![];
|
||||||
|
for (name, ty) in types {
|
||||||
|
variants.push(create_single_table_spec(
|
||||||
|
&ty.description,
|
||||||
|
&ty.value,
|
||||||
|
Some(name),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"description": description,
|
||||||
|
"anyOf": variants,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
TableSpec::Single(s) => create_single_table_spec(description, s, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_single_table_spec(
|
||||||
|
description: &str,
|
||||||
|
spec: &SingleTableSpec,
|
||||||
|
type_: Option<&str>,
|
||||||
|
) -> Value {
|
||||||
|
let mut properties = Map::new();
|
||||||
|
let mut required = vec![];
|
||||||
|
if let Some(type_) = type_ {
|
||||||
|
properties.insert("type".into(), json!({ "const": type_ }));
|
||||||
|
required.push("type".into());
|
||||||
|
}
|
||||||
|
for (key, val) in &spec.fields {
|
||||||
|
properties.insert(
|
||||||
|
key.into(),
|
||||||
|
create_nestable_type_spec(&val.description, &val.value.kind),
|
||||||
|
);
|
||||||
|
if val.value.required {
|
||||||
|
required.push(key.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"description": description,
|
||||||
|
"type": "object",
|
||||||
|
"properties": properties,
|
||||||
|
"required": required,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_ref_spec(description: &str, name: &str) -> Value {
|
||||||
|
let path = format!("#/$defs/{name}");
|
||||||
|
json!({
|
||||||
|
"description": description,
|
||||||
|
"$ref": path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_nestable_type_spec(description: &str, spec: &RefOrSpec<NestableTypesSpec>) -> Value {
|
||||||
|
let spec = match spec {
|
||||||
|
RefOrSpec::Ref { name } => return create_ref_spec(description, name),
|
||||||
|
RefOrSpec::Spec(s) => s,
|
||||||
|
};
|
||||||
|
match spec {
|
||||||
|
NestableTypesSpec::String(s) => create_string_spec(description, s),
|
||||||
|
NestableTypesSpec::Number(s) => create_number_spec(description, s),
|
||||||
|
NestableTypesSpec::Boolean => create_boolean_spec(description),
|
||||||
|
NestableTypesSpec::Array(s) => create_array_spec(description, s),
|
||||||
|
NestableTypesSpec::Map(s) => create_map_spec(description, s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_map_spec(description: &str, spec: &MapSpec) -> Value {
|
||||||
|
json!({
|
||||||
|
"description": description,
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": create_nestable_type_spec("", &spec.values),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_string_spec(description: &str, spec: &StringSpec) -> Value {
|
||||||
|
let mut res = Map::new();
|
||||||
|
res.insert("type".into(), json!("string"));
|
||||||
|
res.insert("description".into(), json!(description));
|
||||||
|
if let Some(values) = &spec.values {
|
||||||
|
let strings: Vec<_> = values.iter().map(|v| &v.value.value).collect();
|
||||||
|
res.insert("enum".into(), json!(strings));
|
||||||
|
}
|
||||||
|
res.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_array_spec(description: &str, spec: &ArraySpec) -> Value {
|
||||||
|
json!({
|
||||||
|
"type": "array",
|
||||||
|
"description": description,
|
||||||
|
"items": create_nestable_type_spec("", &spec.items),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_number_spec(description: &str, spec: &NumberSpec) -> Value {
|
||||||
|
let ty = match spec.integer_only {
|
||||||
|
true => "integer",
|
||||||
|
false => "number",
|
||||||
|
};
|
||||||
|
let mut res = Map::new();
|
||||||
|
res.insert("type".into(), json!(ty));
|
||||||
|
res.insert("description".into(), json!(description));
|
||||||
|
if let Some(minimum) = spec.minimum {
|
||||||
|
let key = match spec.exclusive_minimum {
|
||||||
|
true => "exclusiveMinimum",
|
||||||
|
false => "minimum",
|
||||||
|
};
|
||||||
|
res.insert(key.into(), json!(minimum));
|
||||||
|
}
|
||||||
|
res.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_boolean_spec(description: &str) -> Value {
|
||||||
|
json!({"type": "boolean", "description": description})
|
||||||
|
}
|
||||||
27
toml-spec/src/main.rs
Normal file
27
toml-spec/src/main.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
json_schema::generate_json_schema,
|
||||||
|
markdown::generate_markdown,
|
||||||
|
types::{Described, TopLevelTypeSpec},
|
||||||
|
},
|
||||||
|
anyhow::Result,
|
||||||
|
indexmap::IndexMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod json_schema;
|
||||||
|
mod markdown;
|
||||||
|
mod types;
|
||||||
|
|
||||||
|
fn parse() -> Result<IndexMap<String, Described<TopLevelTypeSpec>>> {
|
||||||
|
let file = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.yaml"))?;
|
||||||
|
Ok(serde_yaml::from_str(&file)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let types = parse()?;
|
||||||
|
let mut types_sorted: Vec<_> = types.iter().collect();
|
||||||
|
types_sorted.sort_by_key(|t| t.0);
|
||||||
|
generate_markdown(&types_sorted)?;
|
||||||
|
generate_json_schema(&types_sorted)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
259
toml-spec/src/markdown.rs
Normal file
259
toml-spec/src/markdown.rs
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
use {
|
||||||
|
crate::types::{
|
||||||
|
ArraySpec, Described, NestableTypesSpec, NumberSpec, RefOrSpec, SingleTableSpec,
|
||||||
|
StringSpec, TableSpec, TopLevelTypeSpec, VariantSpec,
|
||||||
|
},
|
||||||
|
anyhow::Result,
|
||||||
|
std::io::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate_markdown(types: &[(&String, &Described<TopLevelTypeSpec>)]) -> Result<()> {
|
||||||
|
const TEMPLATE: &str = include_str!("../spec/template.md");
|
||||||
|
|
||||||
|
let mut buf = vec![];
|
||||||
|
buf.extend_from_slice(TEMPLATE.as_bytes());
|
||||||
|
|
||||||
|
for (name, ty) in types {
|
||||||
|
write_top_level_type_spec(&mut buf, name, ty)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
concat!(env!("CARGO_MANIFEST_DIR"), "/spec/spec.generated.md"),
|
||||||
|
&buf,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_top_level_type_spec(
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
name: &str,
|
||||||
|
spec: &Described<TopLevelTypeSpec>,
|
||||||
|
) -> Result<()> {
|
||||||
|
writeln!(buf, "<a name=\"types-{name}\"></a>")?;
|
||||||
|
writeln!(buf, "### `{name}`")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
writeln!(buf, "{}", spec.description.trim())?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
match &spec.value {
|
||||||
|
TopLevelTypeSpec::Variable { variants } => {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"Values of this type should have one of the following forms:"
|
||||||
|
)?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for variant in variants {
|
||||||
|
write!(buf, "#### ")?;
|
||||||
|
let name = match &variant.value {
|
||||||
|
VariantSpec::String(_) => "A string",
|
||||||
|
VariantSpec::Number(_) => "A number",
|
||||||
|
VariantSpec::Boolean => "A boolean",
|
||||||
|
VariantSpec::Array(_) => "An array",
|
||||||
|
VariantSpec::Table(_) => "A table",
|
||||||
|
};
|
||||||
|
writeln!(buf, "{name}")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
writeln!(buf, "{}", variant.description.trim())?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
write_variant_spec(buf, &variant.value)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TopLevelTypeSpec::Single(variant) => {
|
||||||
|
let name = match &variant {
|
||||||
|
VariantSpec::String(_) => "strings",
|
||||||
|
VariantSpec::Number(_) => "numbers",
|
||||||
|
VariantSpec::Boolean => "booleans",
|
||||||
|
VariantSpec::Array(_) => "arrays",
|
||||||
|
VariantSpec::Table(_) => "tables",
|
||||||
|
};
|
||||||
|
writeln!(buf, "Values of this type should be {name}.")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
write_variant_spec(buf, variant)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(buf)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_variant_spec(buf: &mut Vec<u8>, spec: &VariantSpec) -> Result<()> {
|
||||||
|
macro_rules! spec {
|
||||||
|
($v:expr) => {
|
||||||
|
match $v {
|
||||||
|
RefOrSpec::Ref { name } => {
|
||||||
|
writeln!(buf, "The value should be a [{name}](#types-{name}).")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
RefOrSpec::Spec(s) => s,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match spec {
|
||||||
|
VariantSpec::String(ss) => {
|
||||||
|
let ss = spec!(ss);
|
||||||
|
write_string_spec(buf, ss, "")?;
|
||||||
|
}
|
||||||
|
VariantSpec::Number(ns) => {
|
||||||
|
let ns = spec!(ns);
|
||||||
|
write_number_spec(buf, ns, "")?;
|
||||||
|
}
|
||||||
|
VariantSpec::Boolean => {}
|
||||||
|
VariantSpec::Array(s) => {
|
||||||
|
let s = spec!(s);
|
||||||
|
write_array_spec(buf, s, "")?;
|
||||||
|
}
|
||||||
|
VariantSpec::Table(ts) => {
|
||||||
|
let ts = spec!(ts);
|
||||||
|
match ts {
|
||||||
|
TableSpec::Tagged { types } => {
|
||||||
|
writeln!(buf, "This table is a tagged union. The variant is determined by the `type` field. It takes one of the following values:")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for (name, spec) in types {
|
||||||
|
writeln!(buf, "- `{name}`:")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for line in spec.description.trim().lines() {
|
||||||
|
writeln!(buf, " {line}")?;
|
||||||
|
}
|
||||||
|
writeln!(buf)?;
|
||||||
|
write_single_table_spec(buf, &spec.value, " ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableSpec::Single(s) => {
|
||||||
|
write_single_table_spec(buf, s, "")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_single_table_spec(buf: &mut Vec<u8>, spec: &SingleTableSpec, pad: &str) -> Result<()> {
|
||||||
|
writeln!(buf, "{pad}The table has the following fields:")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for (name, fs) in &spec.fields {
|
||||||
|
let optional = match fs.value.required {
|
||||||
|
true => "required",
|
||||||
|
false => "optional",
|
||||||
|
};
|
||||||
|
writeln!(buf, "{pad}- `{name}` ({optional}):")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for line in fs.description.trim().lines() {
|
||||||
|
writeln!(buf, "{pad} {line}")?;
|
||||||
|
}
|
||||||
|
writeln!(buf)?;
|
||||||
|
write!(buf, "{pad} The value of this field should be ")?;
|
||||||
|
let spec = write_nestable_type_spec(buf, &fs.value.kind, false)?;
|
||||||
|
writeln!(buf, ".")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
if let Some(spec) = spec {
|
||||||
|
let pad = format!("{pad} ");
|
||||||
|
write_nestable_type_restrictions(buf, spec, &pad)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_nestable_type_spec<'a>(
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
spec: &'a RefOrSpec<NestableTypesSpec>,
|
||||||
|
plural: bool,
|
||||||
|
) -> Result<Option<&'a NestableTypesSpec>> {
|
||||||
|
let spec = match spec {
|
||||||
|
RefOrSpec::Ref { name } => {
|
||||||
|
if plural {
|
||||||
|
write!(buf, "[{name}s](#types-{name})")?;
|
||||||
|
} else {
|
||||||
|
write!(buf, "a [{name}](#types-{name})")?;
|
||||||
|
}
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
RefOrSpec::Spec(s) => s,
|
||||||
|
};
|
||||||
|
let name = match (spec, plural) {
|
||||||
|
(NestableTypesSpec::String(_), false) => "a string",
|
||||||
|
(NestableTypesSpec::String(_), true) => "strings",
|
||||||
|
(NestableTypesSpec::Number(_), false) => "a number",
|
||||||
|
(NestableTypesSpec::Number(_), true) => "numbers",
|
||||||
|
(NestableTypesSpec::Boolean, false) => "a boolean",
|
||||||
|
(NestableTypesSpec::Boolean, true) => "booleans",
|
||||||
|
(NestableTypesSpec::Map(s), _) => {
|
||||||
|
let name = match plural {
|
||||||
|
true => "tables",
|
||||||
|
false => "a table",
|
||||||
|
};
|
||||||
|
write!(buf, "{name} whose values are ")?;
|
||||||
|
return write_nestable_type_spec(buf, &s.values, true);
|
||||||
|
}
|
||||||
|
(NestableTypesSpec::Array(s), _) => {
|
||||||
|
let name = match plural {
|
||||||
|
true => "arrays",
|
||||||
|
false => "an array",
|
||||||
|
};
|
||||||
|
write!(buf, "{name} of ")?;
|
||||||
|
return write_nestable_type_spec(buf, &s.items, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
write!(buf, "{name}")?;
|
||||||
|
Ok(Some(spec))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_nestable_type_restrictions(
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
spec: &NestableTypesSpec,
|
||||||
|
pad: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
match spec {
|
||||||
|
NestableTypesSpec::String(s) => write_string_spec(buf, s, pad),
|
||||||
|
NestableTypesSpec::Number(s) => write_number_spec(buf, s, pad),
|
||||||
|
NestableTypesSpec::Boolean => Ok(()),
|
||||||
|
NestableTypesSpec::Array(_) => Ok(()),
|
||||||
|
NestableTypesSpec::Map(_) => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_string_spec(buf: &mut Vec<u8>, spec: &StringSpec, pad: &str) -> Result<()> {
|
||||||
|
if let Some(values) = &spec.values {
|
||||||
|
writeln!(
|
||||||
|
buf,
|
||||||
|
"{pad}The string should have one of the following values:"
|
||||||
|
)?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for value in values {
|
||||||
|
writeln!(buf, "{pad}- `{}`:", value.value.value)?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
for line in value.description.lines() {
|
||||||
|
writeln!(buf, "{pad} {line}")?;
|
||||||
|
}
|
||||||
|
writeln!(buf)?;
|
||||||
|
}
|
||||||
|
writeln!(buf)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_array_spec(buf: &mut Vec<u8>, spec: &ArraySpec, pad: &str) -> Result<()> {
|
||||||
|
write!(buf, "{pad}Each element of this array should be ")?;
|
||||||
|
let spec = write_nestable_type_spec(buf, &spec.items, false)?;
|
||||||
|
writeln!(buf, ".")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
if let Some(spec) = spec {
|
||||||
|
write_nestable_type_restrictions(buf, spec, pad)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_number_spec(buf: &mut Vec<u8>, spec: &NumberSpec, pad: &str) -> Result<()> {
|
||||||
|
if spec.integer_only {
|
||||||
|
writeln!(buf, "{pad}The numbers should be integers.")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
}
|
||||||
|
if let Some(minimum) = spec.minimum {
|
||||||
|
let greater = match spec.exclusive_minimum {
|
||||||
|
true => "strictly greater than",
|
||||||
|
false => "greater than or equal to",
|
||||||
|
};
|
||||||
|
writeln!(buf, "{pad}The numbers should be {greater} {minimum}.")?;
|
||||||
|
writeln!(buf)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
184
toml-spec/src/types.rs
Normal file
184
toml-spec/src/types.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
use {
|
||||||
|
error_reporter::Report,
|
||||||
|
indexmap::IndexMap,
|
||||||
|
serde::{
|
||||||
|
de::{DeserializeOwned, Error},
|
||||||
|
Deserialize, Deserializer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Described<T> {
|
||||||
|
pub description: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TopLevelTypeSpec {
|
||||||
|
Variable {
|
||||||
|
variants: Vec<Described<VariantSpec>>,
|
||||||
|
},
|
||||||
|
Single(VariantSpec),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TableSpec {
|
||||||
|
Tagged {
|
||||||
|
types: IndexMap<String, Described<SingleTableSpec>>,
|
||||||
|
},
|
||||||
|
Single(SingleTableSpec),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct SingleTableSpec {
|
||||||
|
pub fields: IndexMap<String, Described<TableFieldSpec>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct TableFieldSpec {
|
||||||
|
pub required: bool,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub kind: RefOrSpec<NestableTypesSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum RefOrSpec<T> {
|
||||||
|
Ref { name: String },
|
||||||
|
Spec(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct StringSpec {
|
||||||
|
pub values: Option<Vec<Described<StringSpecValue>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct StringSpecValue {
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct NumberSpec {
|
||||||
|
#[serde(default)]
|
||||||
|
pub integer_only: bool,
|
||||||
|
pub minimum: Option<f64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub exclusive_minimum: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", tag = "kind")]
|
||||||
|
pub enum VariantSpec {
|
||||||
|
String(RefOrSpec<StringSpec>),
|
||||||
|
Number(RefOrSpec<NumberSpec>),
|
||||||
|
Boolean,
|
||||||
|
Array(RefOrSpec<ArraySpec>),
|
||||||
|
Table(RefOrSpec<TableSpec>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ArraySpec {
|
||||||
|
pub items: Box<RefOrSpec<NestableTypesSpec>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", tag = "kind")]
|
||||||
|
pub enum NestableTypesSpec {
|
||||||
|
String(StringSpec),
|
||||||
|
Number(NumberSpec),
|
||||||
|
Boolean,
|
||||||
|
Array(ArraySpec),
|
||||||
|
Map(MapSpec),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct MapSpec {
|
||||||
|
pub values: Box<RefOrSpec<NestableTypesSpec>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TopLevelTypeSpec {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Variable {
|
||||||
|
variants: Vec<Described<VariantSpec>>,
|
||||||
|
}
|
||||||
|
let variable = Variable::deserialize(&v);
|
||||||
|
let single = VariantSpec::deserialize(&v);
|
||||||
|
let res = match (variable, single) {
|
||||||
|
(Ok(variable), _) => Self::Variable {
|
||||||
|
variants: variable.variants,
|
||||||
|
},
|
||||||
|
(_, Ok(single)) => Self::Single(single),
|
||||||
|
(Err(e1), Err(e2)) => {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"spec must define either variants or a single variant. failures: {} ----- {}",
|
||||||
|
Report::new(e1),
|
||||||
|
Report::new(e2)
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TableSpec {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Tagged {
|
||||||
|
types: IndexMap<String, Described<SingleTableSpec>>,
|
||||||
|
}
|
||||||
|
let tagged = Tagged::deserialize(&v);
|
||||||
|
let single = SingleTableSpec::deserialize(&v);
|
||||||
|
let res = match (tagged, single) {
|
||||||
|
(Ok(tagged), _) => Self::Tagged {
|
||||||
|
types: tagged.types,
|
||||||
|
},
|
||||||
|
(_, Ok(single)) => Self::Single(single),
|
||||||
|
(Err(e1), Err(e2)) => {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"spec must define either types or fields. failures: {} ----- {}",
|
||||||
|
Report::new(e1),
|
||||||
|
Report::new(e2)
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, U: DeserializeOwned> Deserialize<'de> for RefOrSpec<U> {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let v = serde_yaml::Value::deserialize(deserializer)?;
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Ref {
|
||||||
|
#[serde(rename = "ref")]
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
let name = Ref::deserialize(&v);
|
||||||
|
let single = U::deserialize(&v);
|
||||||
|
let res = match (name, single) {
|
||||||
|
(Ok(name), _) => Self::Ref { name: name.name },
|
||||||
|
(_, Ok(single)) => Self::Spec(single),
|
||||||
|
(Err(e1), Err(e2)) => {
|
||||||
|
return Err(Error::custom(format!(
|
||||||
|
"spec must define either a ref or a spec. failures: {} ----- {}",
|
||||||
|
Report::new(e1),
|
||||||
|
Report::new(e2)
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue