diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d54fa2f..d166573 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,21 +2,25 @@ name: Rust on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: - branches: [ "master" ] + branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: - + strategy: + matrix: + rust: + - stable + - 1.64.0 runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: Swatinem/rust-cache@v2 + with: + key: rust-${{ matrix.rust }} + - run: rustup default ${{ matrix.rust }} + - run: cargo test --verbose diff --git a/.gitignore b/.gitignore index 86badbd..7647dfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /target output.log -Cargo.lock .envrc -.aider* \ No newline at end of file +.aider* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..92aa796 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1226 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "basic-toml" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" +dependencies = [ + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", + "gloo-utils 0.1.7", + "gloo-worker 0.2.1", +] + +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.1", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events 0.2.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" +dependencies = [ + "gloo-events 0.1.2", + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4022e82f5f9e03cb1251b13c0a967e0600e97aa179c617f6519bac40640160" +dependencies = [ + "getrandom", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.1", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console 0.2.3", + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "implicit-clone" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a50fed6f6aa778e7946f697a88e474b4b4a83fb0849cec1ac65fa8e869319dd" +dependencies = [ + "indexmap", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.38", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.1", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba92964781421b6cef36bf0d7da26d201e96d84e1b10e7ae6ed416e516906d" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tokio" +version = "1.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +dependencies = [ + "backtrace", + "pin-project-lite", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "trybuild" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] + +[[package]] +name = "yew" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.10.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-autoprops" +version = "0.3.0" +dependencies = [ + "implicit-clone", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.38", + "trybuild", + "yew", +] + +[[package]] +name = "yew-macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/Cargo.toml b/Cargo.toml index 774d4ea..5140745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,5 +23,7 @@ [dev-dependencies] - yew = { version = "0.21" } - trybuild = "1" + yew = { version = "0.21" } + implicit-clone = "0.4.6" + trybuild = "1" + rustversion = "1" diff --git a/README.md b/README.md index c6217e6..731afc6 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ No more extra one-off Props structs! # Examples ```rust -use yew_autoprops::autoprops_component; +use yew_autoprops::autoprops; use yew::prelude::*; -#[autoprops_component] +#[autoprops] +#[function_component] fn CoolComponent(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { html! {
@@ -24,10 +25,11 @@ fn CoolComponent(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { ``` ```rust -use yew_autoprops::autoprops_component; +use yew_autoprops::autoprops; use yew::prelude::*; -#[autoprops_component(CoolComponent)] +#[autoprops(CoolComponentProps)] +#[function_component(CoolComponent)] fn cool_component(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { html! {
diff --git a/src/autoprops.rs b/src/autoprops.rs new file mode 100644 index 0000000..d62ba7b --- /dev/null +++ b/src/autoprops.rs @@ -0,0 +1,241 @@ +use crate::function_component::FunctionComponentName; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; + +#[derive(Clone)] +pub struct Autoprops { + item_fn: syn::ItemFn, + properties_name: syn::Ident, +} + +impl syn::parse::Parse for Autoprops { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let parsed: syn::Item = input.parse()?; + + let item_fn = match parsed { + syn::Item::Fn(m) => m, + item => { + return Err(syn::Error::new_spanned( + item, + "`autoprops` attribute can only be applied to functions", + )) + } + }; + + let syn::ItemFn { attrs, sig, .. } = &item_fn; + + let mut component_name = item_fn.sig.ident.clone(); + + for attr in attrs { + match &attr.meta { + syn::Meta::List(syn::MetaList { path, tokens, .. }) => { + if let Some(last_segment) = path.segments.last() { + if last_segment.ident == "function_component" { + if let Ok(attr) = syn::parse2::(tokens.clone()) { + if let Some(name) = attr.component_name { + component_name = name; + } + } + break; + } + } + } + _ => {} + } + } + + for input in &sig.inputs { + if let syn::FnArg::Typed(syn::PatType { pat, .. }) = input { + if let syn::Pat::Wild(wild) = pat.as_ref() { + return Err(syn::Error::new_spanned( + wild, + "cannot use `_` as field name", + )); + } + } + } + + let properties_name = quote::format_ident!("{}Props", component_name); + + Ok(Self { + properties_name, + item_fn, + }) + } +} + +impl Autoprops { + pub fn apply_args(&mut self, args: AutopropsArgs) { + if let Some(name) = args.properties_name { + self.properties_name = name; + } + } + + fn print_function_component(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { + attrs, + vis, + sig, + block, + } = &self.item_fn; + + let fn_name = &sig.ident; + let (_impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); + let inputs = if self.needs_a_properties_struct() { + // NOTE: function components currently don't accept receivers, we're just passing the + // information to the next macro to fail and give its own error message + let receivers = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Receiver(receiver) => Some(receiver), + _ => None, + }) + .collect::>(); + let args = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, .. }) => Some(quote! { #pat, }), + _ => None, + }) + .collect::>(); + let phantom = sig.generics.type_params().next().is_some().then(|| { + quote! { + __yew_autoprops_phantom: _, + } + }); + quote! { + #(#receivers,)* #properties_name { + #(#args)* + #phantom + }: &#properties_name #type_generics + } + } else { + quote! {} + }; + let clones = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) + if !matches!(**ty, syn::Type::Reference(_)) => + { + Some(quote! { + let #pat = ::yew::html::ImplicitClone::implicit_clone(#pat); + }) + } + _ => None, + }) + .collect::>(); + let output = &sig.output; + let impl_generics_with_defaults = &sig.generics; + + quote! { + #(#attrs)* + #vis fn #fn_name #impl_generics_with_defaults (#inputs) #output #where_clause { + #(#clones)* + #block + } + } + } + + fn print_properties_struct(&self) -> proc_macro2::TokenStream { + let properties_name = &self.properties_name; + let syn::ItemFn { vis, sig, .. } = &self.item_fn; + + if !self.needs_a_properties_struct() { + return quote! {}; + } + + let (impl_generics, type_generics, where_clause) = sig.generics.split_for_impl(); + let fields = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { attrs, pat, ty, .. }) => match ty.as_ref() { + syn::Type::Reference(syn::TypeReference { elem, .. }) => { + Some(quote! { #(#attrs)* #vis #pat: #elem, }) + } + _ => Some(quote! { #(#attrs)* #vis #pat: #ty, }), + }, + _ => None, + }) + .collect::>(); + let type_params = sig + .generics + .type_params() + .map(|param| ¶m.ident) + .collect::>(); + let phantom = (!type_params.is_empty()).then(|| { + quote! { + #[prop_or_default] + #vis __yew_autoprops_phantom: ::std::marker::PhantomData <( #(#type_params),* )>, + } + }); + let fields_eq = sig + .inputs + .iter() + .filter_map(|arg| match arg { + syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => Some(quote_spanned! { + ty.span() => self.#pat == rhs.#pat && + }), + _ => None, + }) + .collect::>(); + let impl_generics_with_defaults = &sig.generics; + + quote! { + #[derive(::yew::Properties)] + #vis struct #properties_name #impl_generics_with_defaults #where_clause { + #(#fields)* + #phantom + } + + impl #impl_generics ::std::cmp::PartialEq for #properties_name #type_generics + #where_clause { + fn eq(&self, rhs: &Self) -> ::std::primitive::bool { + #(#fields_eq)* true + } + } + } + } + + fn needs_a_properties_struct(&self) -> bool { + let syn::ItemFn { sig, .. } = &self.item_fn; + !sig.inputs.is_empty() + } +} + +impl quote::ToTokens for Autoprops { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let function_component = self.print_function_component(); + let properties_struct = self.print_properties_struct(); + + tokens.extend(quote! { + #function_component + #properties_struct + }) + } +} + +pub struct AutopropsArgs { + pub properties_name: Option, +} + +impl syn::parse::Parse for AutopropsArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self { + properties_name: None, + }); + } + + let properties_name = input.parse()?; + + Ok(Self { + properties_name: Some(properties_name), + }) + } +} diff --git a/src/function_component.rs b/src/function_component.rs new file mode 100644 index 0000000..eae5172 --- /dev/null +++ b/src/function_component.rs @@ -0,0 +1,24 @@ +// copy-pasted from yew-macro because proc-macro crates cannot export any items + +use syn::parse::{Parse, ParseStream}; +use syn::Ident; + +pub struct FunctionComponentName { + pub component_name: Option, +} + +impl Parse for FunctionComponentName { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self { + component_name: None, + }); + } + + let component_name = input.parse()?; + + Ok(Self { + component_name: Some(component_name), + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0467478..7440107 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,170 +1,16 @@ -//! proc-macro to automatically derive Properties structs from args for Yew components -//! -//! No more extra one-off Props structs! -//! -//! -//! # Examples -//! -//! ``` -//! use yew_autoprops::autoprops_component; -//! use yew::prelude::*; -//! -//! #[autoprops_component] -//! fn CoolComponent(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { -//! html! { -//!
-//!

{ smth }

-//!
-//! } -//! } -//! ``` -//! -//! ``` -//! use yew_autoprops::autoprops_component; -//! use yew::prelude::*; -//! -//! #[autoprops_component(CoolComponent)] -//! fn cool_component(#[prop_or_default] hidden: bool, smth: &AttrValue) -> Html { -//! html! { -//!
-//!

{ smth }

-//!
-//! } -//! } -//! ``` -//! -extern crate proc_macro; +mod autoprops; +mod function_component; -use core::panic; - -use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::{quote, ToTokens}; -use syn::{parse_macro_input, FnArg, ItemFn, Pat, PatType, Type}; +use quote::ToTokens; #[proc_macro_attribute] -pub fn autoprops_component( - args: proc_macro::TokenStream, - input: proc_macro::TokenStream, +pub fn autoprops( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let args = TokenStream::from(args).into_iter().collect::>(); - - let function = parse_macro_input!(input as ItemFn); - - let fn_name = &function.sig.ident; - let visibility = &function.vis; - let generics = &function.sig.generics; - - let (component_name, struct_name) = match args.len() { - 0 => ( - None, - Some(syn::Ident::new( - &format!("{}Props", fn_name), - Span::call_site().into(), - )), - ), - 1 => { - let TokenTree::Ident(name) = &args[0] else { - panic!("Invalid argument: {}", args[0].to_string()); - }; - - ( - Some(name), - Some(syn::Ident::new( - &format!("{}Props", name), - Span::call_site().into(), - )), - ) - } - 3 => { - let TokenTree::Ident(name) = &args[0] else { - panic!("Invalid argument: {}", args[0].to_string()); - }; - - let TokenTree::Ident(props) = args[2].clone() else { - panic!("Invalid argument: {}", args[2].to_string()); - }; - - (Some(name), Some(props)) - } - _ => panic!("Invalid arguments: {:?}", args), - }; - - let arg_names = function - .sig - .inputs - .iter() - .map(|input| { - if let FnArg::Typed(PatType { pat, .. }) = input { - if let Pat::Ident(ident) = pat.as_ref() { - return &ident.ident; - } - } - panic!("Invalid argument: {}", input.to_token_stream()); - }) - .collect::>(); - - let mut fields = Vec::new(); - let mut arg_types = Vec::new(); - let mut clones = Vec::new(); - let mut partial_eq_constraints = Vec::new(); - for input in function.sig.inputs.iter() { - if let FnArg::Typed(PatType { pat, ty, attrs, .. }) = input { - let mut end_ty = ty; - - if let Type::Reference(ty_ref) = ty.as_ref() { - end_ty = &ty_ref.elem; - } else { - clones.push(quote! { - let #pat = #pat.clone(); - }); - } - - fields.push(quote! { - #(#attrs)* - pub #pat: #end_ty - }); - arg_types.push(ty.clone()); - partial_eq_constraints.push(quote! { #end_ty: PartialEq, }); - } else { - panic!("Invalid argument"); - } - } - - let (impl_generics, ty_generics, _) = generics.split_for_impl(); - let bounds = generics.where_clause.clone(); - - let where_clause = if generics.params.is_empty() { - quote! {} - } else { - quote! { - where - #(#partial_eq_constraints)* - #bounds - } - }; - - let destructure = quote! { - #struct_name { #(#arg_names),* } - }; - - let function_block = function.block; - - let tokens = quote! { - #[derive(::yew::Properties, PartialEq)] - #visibility struct #struct_name #impl_generics #where_clause { - #(#fields),* - } - - #[::yew::function_component(#component_name)] - #[allow(non_snake_case)] - #visibility fn #fn_name #impl_generics (#destructure: &#struct_name #ty_generics) -> ::yew::Html #where_clause { - #(#clones)* - #function_block - } - }; - - // panic!("\n{}", tokens.to_string()); + let mut autoprops = syn::parse_macro_input!(item as autoprops::Autoprops); + let args = syn::parse_macro_input!(attr as autoprops::AutopropsArgs); + autoprops.apply_args(args); - // Return the new tokens - tokens.into() + proc_macro::TokenStream::from(autoprops.into_token_stream()) } diff --git a/tests/cases/allow-refs-pass.rs b/tests/cases/allow-refs-pass.rs deleted file mode 100644 index 6de445c..0000000 --- a/tests/cases/allow-refs-pass.rs +++ /dev/null @@ -1,15 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn CoolComponent(test: i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -fn main() {} diff --git a/tests/cases/attrs-pass.rs b/tests/cases/attrs-pass.rs deleted file mode 100644 index 9396f0a..0000000 --- a/tests/cases/attrs-pass.rs +++ /dev/null @@ -1,43 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn CoolComponent(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -#[autoprops_component] -fn CoolComponent2(me: &i8, #[prop_or_default] pls: &usize) -> Html { - println!("pls: {}", pls); - - html! { -
-

{ me }

-
- } -} - -#[autoprops_component] -fn CoolComponent3(me: &i8, #[prop_or(2)] pls: &usize) -> Html { - println!("pls: {}", pls); - - html! { -
-

{ me }

-
- } -} - -fn main() { - fn main() { - let _ = html! { }; - let _ = html! { }; - let _ = html! { }; - } -} diff --git a/tests/cases/generics-pass.rs b/tests/cases/generics-pass.rs deleted file mode 100644 index 727a320..0000000 --- a/tests/cases/generics-pass.rs +++ /dev/null @@ -1,20 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component] -fn GenericComponent(value: &T) -> Html { - html! { -
- { value.to_string() } -
- } -} - -fn main() { - html! { - <> - value=1 /> - value="hi" /> - - }; -} diff --git a/tests/cases/hooks-pass.rs b/tests/cases/hooks-pass.rs deleted file mode 100644 index a49287f..0000000 --- a/tests/cases/hooks-pass.rs +++ /dev/null @@ -1,20 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component(CoolComponent)] -fn cool_component(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - let test_hook = use_state(|| 0); - - html! { -
-

{ smth }

-

{ *test_hook }

-
- } -} - -fn main() { - let _ = html! { }; -} diff --git a/tests/cases/name-pass.rs b/tests/cases/name-pass.rs deleted file mode 100644 index e54870b..0000000 --- a/tests/cases/name-pass.rs +++ /dev/null @@ -1,17 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -#[autoprops_component(CoolComponent)] -fn cool_component(#[prop_or_default] test: &i8, smth: &usize) -> Html { - println!("test: {}", test); - - html! { -
-

{ smth }

-
- } -} - -fn main() { - let _ = html! { }; -} diff --git a/tests/cases/partialeq-fail.rs b/tests/cases/partialeq-fail.rs deleted file mode 100644 index d00603b..0000000 --- a/tests/cases/partialeq-fail.rs +++ /dev/null @@ -1,29 +0,0 @@ -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -struct TestPEqStruct { - t: T, -} - -impl PartialEq for TestPEqStruct { - fn eq(&self, other: &Self) -> bool { - self.t == other.t - } -} - -struct NotPartialEq(); - -#[autoprops_component] -fn TestComponent(test_struct: &TestPEqStruct) -> Html { - html! { -
- { "TestComponent" } -
- } -} - -fn main() { - html! { - - }; -} diff --git a/tests/cases/partialeq-fail.stderr b/tests/cases/partialeq-fail.stderr deleted file mode 100644 index f35afa2..0000000 --- a/tests/cases/partialeq-fail.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0369]: binary operation `==` cannot be applied to type `TestPEqStruct` - --> tests/cases/partialeq-fail.rs:16:1 - | -16 | #[autoprops_component] - | ^^^^^^^^^^^^^^^^^^^^^^ - | -note: an implementation of `PartialEq` might be missing for `NotPartialEq` - --> tests/cases/partialeq-fail.rs:14:1 - | -14 | struct NotPartialEq(); - | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq` - = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` - | -14 + #[derive(PartialEq)] -15 | struct NotPartialEq(); - | diff --git a/tests/cases/partialeq-pass.rs b/tests/cases/partialeq-pass.rs deleted file mode 100644 index a873158..0000000 --- a/tests/cases/partialeq-pass.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::marker::PhantomData; -use yew::prelude::*; -use yew_autoprops::autoprops_component; - -struct TestPEqStruct { - _phantom: PhantomData, -} - -impl PartialEq for TestPEqStruct { - fn eq(&self, _other: &Self) -> bool { - true - } -} - -struct NotPartialEq(); - -#[autoprops_component] -fn TestComponent(test_struct: &TestPEqStruct) -> Html { - assert!(test_struct == test_struct); - html! { -
- { "TestComponent" } -
- } -} - -fn main() { - html! { - - }; -} diff --git a/tests/function_attr_test.rs b/tests/function_attr_test.rs new file mode 100644 index 0000000..0c4d894 --- /dev/null +++ b/tests/function_attr_test.rs @@ -0,0 +1,13 @@ +#[allow(dead_code)] +#[test] +fn tests_pass() { + let t = trybuild::TestCases::new(); + t.pass("tests/function_component_attr/*-pass.rs"); +} + +#[allow(dead_code)] +#[rustversion::attr(stable(1.64), test)] +fn tests_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/function_component_attr/*-fail.rs"); +} diff --git a/tests/function_component_attr/autoprops-fail.rs b/tests/function_component_attr/autoprops-fail.rs new file mode 100644 index 0000000..f426292 --- /dev/null +++ b/tests/function_component_attr/autoprops-fail.rs @@ -0,0 +1,80 @@ +use yew::prelude::*; +use yew::html::ImplicitClone; +use yew_autoprops::*; + +#[autoprops] +#[function_component] +fn CantAcceptReceiver(&self, b: bool) -> Html { + html! { +

{b}

+ } +} + +#[function_component(WrongAttrsOrder)] +#[autoprops] +fn wrong_attrs_order(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[autoprops] +#[function_component(let)] +fn BadFunctionComponent(b: bool) -> Html { + html! { +

{b}

+ } +} + +#[derive(PartialEq)] +struct NotClonable(u32); + +#[autoprops] +#[function_component] +fn TypeIsNotClone(stuff: NotClonable) -> Html { + drop(stuff); + html! { +

+ } +} + +#[derive(Clone)] +struct NotPartialEq(u32); + +impl ImplicitClone for NotPartialEq {} + +#[autoprops] +#[function_component] +fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + drop(stuff); + html! { +

+ } +} + +#[autoprops] +#[function_component] +fn InvalidFieldName(_: u32) -> Html { + html! { +

+ } +} + +mod private_module { + #[::yew_autoprops::autoprops] + #[::yew::function_component(CompPrivateTest)] + fn comp_private_test(#[prop_or_default] b: ::std::primitive::bool) -> ::yew::Html + { + let _: ::std::primitive::bool = b; + ::yew::html! { +

+ } + } +} + +#[allow(unused_imports)] +use private_module::*; + +fn main() { + let _ = html! { }; +} diff --git a/tests/function_component_attr/autoprops-fail.stderr b/tests/function_component_attr/autoprops-fail.stderr new file mode 100644 index 0000000..1d23b88 --- /dev/null +++ b/tests/function_component_attr/autoprops-fail.stderr @@ -0,0 +1,66 @@ +error: function components can't accept a receiver + --> tests/function_component_attr/autoprops-fail.rs:7:23 + | +7 | fn CantAcceptReceiver(&self, b: bool) -> Html { + | ^^^^^ + +error: expected a reference to a `Properties` type (try: `&bool`) + --> tests/function_component_attr/autoprops-fail.rs:15:25 + | +15 | fn wrong_attrs_order(b: bool) -> Html { + | ^^^^ + +error: expected identifier, found keyword `let` + --> tests/function_component_attr/autoprops-fail.rs:22:22 + | +22 | #[function_component(let)] + | ^^^ + +error: cannot use `_` as field name + --> tests/function_component_attr/autoprops-fail.rs:57:21 + | +57 | fn InvalidFieldName(_: u32) -> Html { + | ^ + +error[E0412]: cannot find type `CompPrivateTest` in this scope + --> tests/function_component_attr/autoprops-fail.rs:79:22 + | +79 | let _ = html! { }; + | ^^^^^^^^^^^^^^^ not found in this scope + | +note: struct `crate::private_module::CompPrivateTest` exists but is inaccessible + --> tests/function_component_attr/autoprops-fail.rs:65:5 + | +65 | #[::yew::function_component(CompPrivateTest)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not accessible + = note: this error originates in the attribute macro `::yew::function_component` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `NotClonable: ImplicitClone` is not satisfied + --> tests/function_component_attr/autoprops-fail.rs:34:19 + | +32 | #[autoprops] + | ------------ required by a bound introduced by this call +33 | #[function_component] +34 | fn TypeIsNotClone(stuff: NotClonable) -> Html { + | ^^^^^ expected an implementor of trait `ImplicitClone` + | +help: consider borrowing here + | +34 | fn TypeIsNotClone(&stuff: NotClonable) -> Html { + | + + +error[E0369]: binary operation `==` cannot be applied to type `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:48:30 + | +48 | fn TypeIsNotPartialEq(stuff: NotPartialEq) -> Html { + | ^^^^^^^^^^^^ + | +note: an implementation of `PartialEq<_>` might be missing for `NotPartialEq` + --> tests/function_component_attr/autoprops-fail.rs:42:1 + | +42 | struct NotPartialEq(u32); + | ^^^^^^^^^^^^^^^^^^^ must implement `PartialEq<_>` +help: consider annotating `NotPartialEq` with `#[derive(PartialEq)]` + | +42 | #[derive(PartialEq)] + | diff --git a/tests/function_component_attr/autoprops-pass.rs b/tests/function_component_attr/autoprops-pass.rs new file mode 100644 index 0000000..45ca1d9 --- /dev/null +++ b/tests/function_component_attr/autoprops-pass.rs @@ -0,0 +1,158 @@ +#![no_implicit_prelude] + +// Shadow primitives +#[allow(non_camel_case_types)] +pub struct bool; +#[allow(non_camel_case_types)] +pub struct char; +#[allow(non_camel_case_types)] +pub struct f32; +#[allow(non_camel_case_types)] +pub struct f64; +#[allow(non_camel_case_types)] +pub struct i128; +#[allow(non_camel_case_types)] +pub struct i16; +#[allow(non_camel_case_types)] +pub struct i32; +#[allow(non_camel_case_types)] +pub struct i64; +#[allow(non_camel_case_types)] +pub struct i8; +#[allow(non_camel_case_types)] +pub struct isize; +#[allow(non_camel_case_types)] +pub struct str; +#[allow(non_camel_case_types)] +pub struct u128; +#[allow(non_camel_case_types)] +pub struct u16; +#[allow(non_camel_case_types)] +pub struct u32; +#[allow(non_camel_case_types)] +pub struct u64; +#[allow(non_camel_case_types)] +pub struct u8; +#[allow(non_camel_case_types)] +pub struct usize; + +#[::yew_autoprops::autoprops] +#[::yew::function_component] +fn CompUseFnName() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompNoProperties)] +fn comp_no_properties() -> ::yew::Html +{ + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompNoGenerics)] +fn comp_no_generics(#[prop_or_default] b: ::std::primitive::bool, a: &::yew::AttrValue) -> ::yew::Html +{ + let _: ::std::primitive::bool = b; + let _: &::yew::AttrValue = a; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompSingleGeneric)] +fn comp_single_generic() -> ::yew::Html { + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompGenerics)] +fn comp_generics(b: T1, a: &T2) -> ::yew::Html +where + T1: ::std::cmp::PartialEq + ::implicit_clone::ImplicitClone, + T2: ::std::cmp::PartialEq, +{ + let _: T1 = b; + let _: &T2 = a; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(CompGenericsWithoutField)] +fn comp_generics_without_field(b: ::std::primitive::bool) -> ::yew::Html { + let _ = b; + ::yew::html! { +

+ } +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component(ConstGenerics)] +fn const_generics(xs: [::std::primitive::u32; N]) -> ::yew::Html { + let _: [::std::primitive::u32; N] = xs; + ::yew::html! { +
+ { N } +
+ } +} + +mod private_module { + #[::yew_autoprops::autoprops] + #[::yew::function_component(CompPrivateTest)] + pub fn comp_private_test(#[prop_or_default] b: ::std::primitive::bool) -> ::yew::Html + { + let _: ::std::primitive::bool = b; + ::yew::html! { +

+ } + } +} + +use private_module::*; + +#[::yew_autoprops::autoprops] +#[::yew::function_component] +fn CompHtmlResult() -> ::yew::HtmlResult { + ::std::result::Result::Ok(::yew::html! { +

+ }) +} + +#[::yew_autoprops::autoprops] +#[::yew::function_component] +fn CompWithDefaultGeneric() -> ::yew::Html +where + T: ::std::default::Default, +{ + let _ = T::default(); + ::yew::html! { +

+ } +} + +fn compile_pass() { + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { /> }; + let _ = ::yew::html! { b=true a="foo" /> }; + let _ = ::yew::html! { xs={[1_u32, 2_u32]} /> }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { }; + let _ = ::yew::html! { /> }; +} + +fn main() {} diff --git a/tests/test.rs b/tests/test.rs deleted file mode 100644 index bc7edaa..0000000 --- a/tests/test.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[test] -fn tests() { - let t = trybuild::TestCases::new(); - t.pass("tests/cases/*-pass.rs"); - t.compile_fail("tests/cases/*-fail.rs"); -}