feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"b3600d1ea4f6b24905e1c720c2dcee3af059ed2af45ed1e2a836124bb0bd3db2","Cargo.lock":"1172c81c3a3a5488583feab8b6432fe43faf5d3871e97ff6812dd40d6421ccb1","Cargo.toml":"95caf890d66a74e0f26f1e7f04ae392c85065b03d0f702eb4fbe2de32e7f91f6","Cargo.toml.orig":"42d8d439b03ca6b4340c03c86c96e21ca91c7eb64167dc5cfbeaffea10a4af46","README.md":"d6ed995b649e247fc5e3deb556da4a1f254a3bf8e1bdfc37f3eda8ad4d8486eb","src/attributes.rs":"12253a2573811764e7853cd94a01fa21d93f93e867b3cca1c7f089608a3739ca","src/lib.rs":"feb547fb387cb4af779e6b7d1633af5769b6e250d382ba4a34e4e53ea79e588c"},"package":"780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "b129d4cbf4cc499eff7743365dcb0fe5d8d2e820"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate"
|
||||
version = "0.13.5"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
"macrotest",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "macrotest"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bf02346400dec0d7e4af0aa787c28acf174ce54a9c77f6507a1ee62e2aa2ca2"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"fastrand",
|
||||
"glob",
|
||||
"prettyplease",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2018"
|
||||
name = "delegate"
|
||||
version = "0.13.5"
|
||||
authors = [
|
||||
"Godfrey Chan <godfreykfc@gmail.com>",
|
||||
"Jakub Beránek <berykubik@gmail.com>",
|
||||
]
|
||||
build = false
|
||||
include = [
|
||||
"src/*.rs",
|
||||
"Cargo.toml",
|
||||
"README.md",
|
||||
]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Method delegation with less boilerplate"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/kobzol/rust-delegate"
|
||||
|
||||
[lib]
|
||||
name = "delegate"
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.proc-macro2]
|
||||
version = "1"
|
||||
|
||||
[dependencies.quote]
|
||||
version = "1"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2"
|
||||
features = [
|
||||
"full",
|
||||
"visit-mut",
|
||||
]
|
||||
|
||||
[dev-dependencies.async-trait]
|
||||
version = "0.1.50"
|
||||
|
||||
[dev-dependencies.futures]
|
||||
version = "0.3.16"
|
||||
|
||||
[dev-dependencies.macrotest]
|
||||
version = "1.0.12"
|
||||
|
||||
[dev-dependencies.tokio]
|
||||
version = "1.16.1"
|
||||
features = ["sync"]
|
||||
Vendored
+532
@@ -0,0 +1,532 @@
|
||||
# Method delegation with less boilerplate
|
||||
|
||||
[](https://github.com/kobzol/rust-delegate/actions)
|
||||
[](https://crates.io/crates/delegate)
|
||||
|
||||
This crate removes some boilerplate for structs that simply delegate some of
|
||||
their methods to one or more of their fields.
|
||||
|
||||
It gives you the `delegate!` macro, which delegates method calls to selected
|
||||
expressions (usually inner fields).
|
||||
|
||||
## Example:
|
||||
|
||||
A Stack data structure implemented using an inner Vec via delegation.
|
||||
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Stack<T> {
|
||||
inner: Vec<T>,
|
||||
}
|
||||
impl<T> Stack<T> {
|
||||
pub fn new() -> Self <T> {
|
||||
Self { inner: vec![] }
|
||||
}
|
||||
|
||||
delegate! {
|
||||
to self.inner {
|
||||
pub fn is_empty(&self) -> bool;
|
||||
pub fn push(&mut self, value: T);
|
||||
pub fn pop(&mut self) -> Option<T>;
|
||||
pub fn clear(&mut self);
|
||||
|
||||
#[call(len)]
|
||||
pub fn size(&self) -> usize;
|
||||
|
||||
#[call(last)]
|
||||
pub fn peek(&self) -> Option<&T>;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Delegate to a method with a different name
|
||||
```rust
|
||||
struct Stack {
|
||||
inner: Vec<u32>
|
||||
}
|
||||
impl Stack {
|
||||
delegate! {
|
||||
to self.inner {
|
||||
#[call(push)]
|
||||
pub fn add(&mut self, value: u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use an arbitrary inner field expression
|
||||
```rust
|
||||
struct Wrapper {
|
||||
inner: Rc<RefCell<Vec<u32>>>
|
||||
}
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
to self.inner.deref().borrow_mut() {
|
||||
pub fn push(&mut self, val: u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delegate to enum variants
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
enum Enum {
|
||||
A(A),
|
||||
B(B),
|
||||
C { v: C },
|
||||
}
|
||||
|
||||
struct A {
|
||||
val: usize,
|
||||
}
|
||||
|
||||
impl A {
|
||||
fn dbg_inner(&self) -> usize {
|
||||
dbg!(self.val);
|
||||
1
|
||||
}
|
||||
}
|
||||
struct B {
|
||||
val_a: String,
|
||||
}
|
||||
|
||||
impl B {
|
||||
fn dbg_inner(&self) -> usize {
|
||||
dbg!(self.val_a.clone());
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
struct C {
|
||||
val_c: f64,
|
||||
}
|
||||
|
||||
impl C {
|
||||
fn dbg_inner(&self) -> usize {
|
||||
dbg!(self.val_c);
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
impl Enum {
|
||||
delegate! {
|
||||
// transformed to
|
||||
//
|
||||
// ```rust
|
||||
// match self {
|
||||
// Enum::A(a) => a.dbg_inner(),
|
||||
// Enum::B(b) => { println!("i am b"); b }.dbg_inner(),
|
||||
// Enum::C { v: c } => { c }.dbg_inner(),
|
||||
// }
|
||||
// ```
|
||||
to match self {
|
||||
Enum::A(a) => a,
|
||||
Enum::B(b) => { println!("i am b"); b },
|
||||
Enum::C { v: c } => { c },
|
||||
} {
|
||||
fn dbg_inner(&self) -> usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use modifiers that alter the generated method body
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
struct Inner;
|
||||
impl Inner {
|
||||
pub fn method(&self, num: u32) -> u32 { num }
|
||||
pub fn method_res(&self, num: u32) -> Result<u32, ()> { Ok(num) }
|
||||
}
|
||||
struct Wrapper {
|
||||
inner: Inner
|
||||
}
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
to self.inner {
|
||||
// calls method, converts result to u64 using `From`
|
||||
#[into]
|
||||
pub fn method(&self, num: u32) -> u64;
|
||||
|
||||
// calls method, returns ()
|
||||
#[call(method)]
|
||||
pub fn method_noreturn(&self, num: u32);
|
||||
|
||||
// calls method, converts result to i6 using `TryFrom`
|
||||
#[try_into]
|
||||
#[call(method)]
|
||||
pub fn method2(&self, num: u32) -> Result<u16, std::num::TryFromIntError>;
|
||||
|
||||
// calls method_res, unwraps the result
|
||||
#[unwrap]
|
||||
pub fn method_res(&self, num: u32) -> u32;
|
||||
|
||||
// calls method_res, unwraps the result, then calls into
|
||||
#[unwrap]
|
||||
#[into]
|
||||
#[call(method_res)]
|
||||
pub fn method_res_into(&self, num: u32) -> u64;
|
||||
|
||||
// specify explicit type for into
|
||||
#[into(u64)]
|
||||
#[call(method)]
|
||||
pub fn method_into_explicit(&self, num: u32) -> u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom called expression
|
||||
|
||||
The `#[expr()]` attribute can be used to modify the delegated call. You can use the `$` sigil as a placeholder for what delegate would normally expand to, and wrap that expression with custom code.
|
||||
|
||||
_Note:_ the `$` placeholder isn't required and can be present multiple times if you want.
|
||||
|
||||
```rust
|
||||
struct A(Vec<u8>);
|
||||
|
||||
impl A {
|
||||
delegate! {
|
||||
to self.0 {
|
||||
#[expr(*$.unwrap())]
|
||||
/// Here `$` == `self.0.get(idx)`
|
||||
/// Will expand to `*self.0.get(idx).unwrap()`
|
||||
fn get(&self, idx: usize) -> u8;
|
||||
|
||||
#[call(get)]
|
||||
#[expr($?.checked_pow(2))]
|
||||
/// Here `$` == `self.0.get(idx)`
|
||||
/// Will expand to `self.0.get(idx)?.checked_pow(2)`
|
||||
fn get_checked_pow_2(&self, idx: usize) -> Option<u8>;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add additional arguments to method
|
||||
```rust
|
||||
struct Inner(u32);
|
||||
impl Inner {
|
||||
pub fn new(m: u32) -> Self {
|
||||
// some "very complex" constructing work
|
||||
Self(m)
|
||||
}
|
||||
pub fn method(&self, n: u32) -> u32 {
|
||||
self.0 + n
|
||||
}
|
||||
}
|
||||
|
||||
struct Wrapper {
|
||||
inner: OnceCell<Inner>,
|
||||
}
|
||||
|
||||
impl Wrapper {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
fn content(&self, val: u32) -> &Inner {
|
||||
self.inner.get_or_init(|| Inner(val))
|
||||
}
|
||||
delegate! {
|
||||
to |k: u32| self.content(k) {
|
||||
// `wrapper.method(k, num)` will call `self.content(k).method(num)`
|
||||
pub fn method(&self, num: u32) -> u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Call `await` on async functions
|
||||
```rust
|
||||
struct Inner;
|
||||
impl Inner {
|
||||
pub async fn method(&self, num: u32) -> u32 { num }
|
||||
}
|
||||
struct Wrapper {
|
||||
inner: Inner
|
||||
}
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
to self.inner {
|
||||
// calls method(num).await, returns impl Future<Output = u32>
|
||||
pub async fn method(&self, num: u32) -> u32;
|
||||
// calls method(num).await.into(), returns impl Future<Output = u64>
|
||||
#[into]
|
||||
#[call(method)]
|
||||
pub async fn method_into(&self, num: u32) -> u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use the `#[await(true/false)]` attribute on delegated methods to specify
|
||||
if `.await` should be generated after the delegated expression. It will be
|
||||
generated by default if the delegated method is `async`.
|
||||
|
||||
### Delegate to multiple fields
|
||||
```rust
|
||||
struct MultiStack {
|
||||
left: Vec<u32>,
|
||||
right: Vec<u32>,
|
||||
}
|
||||
impl MultiStack {
|
||||
delegate! {
|
||||
to self.left {
|
||||
/// Push an item to the top of the left stack
|
||||
#[call(push)]
|
||||
pub fn push_left(&mut self, value: u32);
|
||||
}
|
||||
to self.right {
|
||||
/// Push an item to the top of the right stack
|
||||
#[call(push)]
|
||||
pub fn push_right(&mut self, value: u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inline attributes
|
||||
`rust-delegate` inserts `#[inline(always)]` automatically. You can override that decision by specifying `#[inline]`
|
||||
manually on the delegated method.
|
||||
|
||||
### Segment attributes
|
||||
You can use an attribute on a whole delegation segment to automatically apply it to all methods in that segment:
|
||||
|
||||
```rust
|
||||
struct Wrapper {
|
||||
inner: Inner
|
||||
}
|
||||
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
#[unwrap]
|
||||
to self.inner {
|
||||
fn foo(&self) -> u32; // calls self.inner.foo().unwrap()
|
||||
fn bar(&self) -> u32; // calls self.inner.bar().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Adding additional arguments
|
||||
You can specify expressions in the signature that will be used as delegated arguments:
|
||||
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
struct Inner;
|
||||
impl Inner {
|
||||
pub fn polynomial(&self, a: i32, x: i32, b: i32, y: i32, c: i32) -> i32 {
|
||||
a + x * x + b * y + c
|
||||
}
|
||||
}
|
||||
struct Wrapper {
|
||||
inner: Inner,
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: i32
|
||||
}
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
to self.inner {
|
||||
// Calls `polynomial` on `inner` with `self.a`, `self.b` and
|
||||
// `self.c` passed as arguments `a`, `b`, and `c`, effectively
|
||||
// calling `polynomial(self.a, x, self.b, y, self.c)`.
|
||||
pub fn polynomial(&self, [ self.a ], x: i32, [ self.b ], y: i32, [ self.c ]) -> i32 ;
|
||||
// Calls `polynomial` on `inner` with `0`s passed for arguments
|
||||
// `a` and `x`, and `self.b` and `self.c` for `b` and `c`,
|
||||
// effectively calling `polynomial(0, 0, self.b, y, self.c)`.
|
||||
#[call(polynomial)]
|
||||
pub fn linear(&self, [ 0 ], [ 0 ], [ self.b ], y: i32, [ self.c ]) -> i32 ;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parameter modifiers
|
||||
You can modify how will an input parameter be passed to the delegated method with parameter attribute modifiers. Currently, the following modifiers are supported:
|
||||
- `#[into]`: Calls `.into()` on the parameter passed to the delegated method.
|
||||
- `#[as_ref]`: Calls `.as_ref()` on the parameter passed to the delegated method.
|
||||
- `#[newtype]`: Accesses the first tuple element (`.0`) of the parameter passed to the delegated method.
|
||||
|
||||
> Note that these modifiers might be removed in the future, try to use the more general `#[expr]` mechanism to achieve this functionality.
|
||||
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
struct InnerType {}
|
||||
impl InnerType {
|
||||
fn foo(&self, other: Self) {}
|
||||
}
|
||||
|
||||
impl From<Wrapper> for InnerType {
|
||||
fn from(wrapper: Wrapper) -> Self {
|
||||
wrapper.0
|
||||
}
|
||||
}
|
||||
|
||||
struct Wrapper(InnerType);
|
||||
impl Wrapper {
|
||||
delegate! {
|
||||
to self.0 {
|
||||
// Calls `self.0.foo(other.into());`
|
||||
pub fn foo(&self, #[into] other: Self);
|
||||
// Calls `self.0.bar(other.0);`
|
||||
pub fn bar(&self, #[newtype] other: Self);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delegate associated functions
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
struct A {}
|
||||
impl A {
|
||||
fn foo(a: u32) -> u32 {
|
||||
a + 1
|
||||
}
|
||||
}
|
||||
|
||||
struct B;
|
||||
|
||||
impl B {
|
||||
delegate! {
|
||||
to A {
|
||||
fn foo(a: u32) -> u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(B::foo(1), 2);
|
||||
```
|
||||
|
||||
### Delegate associated constants
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
trait WithConst {
|
||||
const TOTO: u8;
|
||||
}
|
||||
|
||||
struct A;
|
||||
impl WithConst for A {
|
||||
const TOTO: u8 = 1;
|
||||
}
|
||||
|
||||
struct B;
|
||||
impl WithConst for B {
|
||||
const TOTO: u8 = 2;
|
||||
}
|
||||
struct C;
|
||||
impl WithConst for C {
|
||||
const TOTO: u8 = 2;
|
||||
}
|
||||
|
||||
enum Enum {
|
||||
A(A),
|
||||
B(B),
|
||||
C(C),
|
||||
}
|
||||
|
||||
impl Enum {
|
||||
delegate! {
|
||||
to match self {
|
||||
Self::A(a) => a,
|
||||
Self::B(b) => b,
|
||||
Self::C(c) => { println!("hello from c"); c },
|
||||
} {
|
||||
#[const(WithConst::TOTO)]
|
||||
fn get_toto(&self) -> u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(Enum::A(A).get_toto(), <A as WithConst>::TOTO);
|
||||
```
|
||||
|
||||
### Delegate to fields
|
||||
```rust
|
||||
use delegate::delegate;
|
||||
|
||||
struct Datum {
|
||||
value: u32,
|
||||
error: u32,
|
||||
}
|
||||
|
||||
struct DatumWrapper(Datum);
|
||||
|
||||
impl DatumWrapper {
|
||||
delegate! {
|
||||
to self.0 {
|
||||
/// Get the value of a nested field with the same name
|
||||
#[field]
|
||||
fn value(&self) -> u32;
|
||||
|
||||
/// Get the value of a nested field with a different name
|
||||
#[field(value)]
|
||||
fn renamed_value(&self) -> u32;
|
||||
|
||||
/// Get shared reference to a nested field
|
||||
#[field(&value)]
|
||||
fn value_ref(&self) -> &u32;
|
||||
|
||||
/// Get mutable reference to a nested field
|
||||
#[field(&mut value)]
|
||||
fn value_ref_mut(&mut self) -> &mut u32;
|
||||
|
||||
/// Get mutable reference to a nested field with the same name
|
||||
#[field(&)]
|
||||
fn error(&self) -> &u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This project uses a standard test suite for quality control, as well as a set of
|
||||
"expansion" tests that utilize the `macrotest` crate to ensure the macro expands
|
||||
as expected. PRs implementing new features should add both standard and expansion
|
||||
tests where appropriate.
|
||||
|
||||
To add an expansion test, place a Rust source file in the `tests/expand/` directory
|
||||
with methods demonstrating the new feature. Next, run `cargo test` to run the test
|
||||
suite and generate a `*.expanded.rs` file in the same directory. Next, carefully
|
||||
inspect the contents of the generated file to confirm that all methods expanded as
|
||||
expected. Finally, commit both files to the git repository. Future test suite runs
|
||||
will now include expanding the source file and comparing it to the expanded file.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
## Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
## Conduct
|
||||
|
||||
Please follow the [Rust Code of Conduct]. For escalation or moderation issues
|
||||
please contact the crate author(s) listed in [`Cargo.toml`](./Cargo.toml).
|
||||
|
||||
[Rust Code of Conduct]: https://www.rust-lang.org/conduct.html
|
||||
Vendored
+563
@@ -0,0 +1,563 @@
|
||||
use proc_macro2::{Delimiter, TokenStream, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Not;
|
||||
use syn::parse::ParseStream;
|
||||
use syn::{Attribute, Error, Meta, Path, PathSegment, Token, TypePath};
|
||||
|
||||
pub struct CallMethodAttribute {
|
||||
name: syn::Ident,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for CallMethodAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
Ok(CallMethodAttribute {
|
||||
name: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct GetFieldAttribute {
|
||||
reference: Option<(Token![&], Option<Token![mut]>)>,
|
||||
member: Option<syn::Member>,
|
||||
}
|
||||
|
||||
impl GetFieldAttribute {
|
||||
pub fn reference_tokens(&self) -> Option<TokenStream> {
|
||||
let (ref_, mut_) = self.reference.as_ref()?;
|
||||
let mut tokens = ref_.to_token_stream();
|
||||
mut_.to_tokens(&mut tokens);
|
||||
Some(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for GetFieldAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
let mut reference = None;
|
||||
if let Ok(ref_) = input.parse::<syn::Token![&]>() {
|
||||
reference = Some((ref_, None));
|
||||
}
|
||||
if let Some((_, mut_)) = &mut reference {
|
||||
*mut_ = input.parse::<syn::Token![mut]>().ok();
|
||||
}
|
||||
let member = input.is_empty().not().then(|| input.parse()).transpose()?;
|
||||
Ok(GetFieldAttribute { reference, member })
|
||||
}
|
||||
}
|
||||
|
||||
struct GenerateAwaitAttribute {
|
||||
literal: syn::LitBool,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for GenerateAwaitAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
Ok(GenerateAwaitAttribute {
|
||||
literal: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IntoAttribute {
|
||||
type_path: Option<TypePath>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for IntoAttribute {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
let type_path: TypePath = input.parse().map_err(|error| {
|
||||
Error::new(
|
||||
input.span(),
|
||||
format!("{error}\nExpected type name, e.g. #[into(u32)]"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(IntoAttribute {
|
||||
type_path: Some(type_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssociatedConstant {
|
||||
pub const_name: PathSegment,
|
||||
pub trait_path: Path,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for AssociatedConstant {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
let mut path = input.parse::<syn::Path>().map_err(|error| {
|
||||
Error::new(
|
||||
input.span(),
|
||||
format!(
|
||||
"{error}\nExpected const path, e.g. #[const(path::to::MyTrait::CONST_NAME)]"
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let const_name = path.segments.pop().ok_or_else(|| {
|
||||
Error::new_spanned(
|
||||
&path,
|
||||
"Expected a path. e.g. #[const(path::to::MyTrait::CONST_NAME)]",
|
||||
)
|
||||
})?;
|
||||
// poping a segment leads to trailing `::`
|
||||
path.segments.pop_punct().ok_or_else(|| {
|
||||
Error::new_spanned(
|
||||
&path,
|
||||
"Expected a multipart path. e.g. #[const(path::to::MyTrait::CONST_NAME)]",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
const_name: const_name.into_value(),
|
||||
trait_path: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Represent the placeholder `$` found inside an expr attribute's template
|
||||
pub struct ExprPlaceHolder;
|
||||
|
||||
impl syn::parse::Parse for ExprPlaceHolder {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
input.parse::<syn::Token![$]>()?;
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Kind of allowed placeholders in an `expr` attribute template
|
||||
#[derive(Clone)]
|
||||
enum Placeholder {
|
||||
ExprPlaceholder(ExprPlaceHolder),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Tokens found in the expr attribute's template
|
||||
/// Token are either
|
||||
/// - a replacable pattern (placeholder)
|
||||
/// - a normal token
|
||||
/// - a group containing a recursive representation of template tokens
|
||||
enum TemplateToken {
|
||||
Normal(TokenTree),
|
||||
Placeholder(Placeholder),
|
||||
Group(Delimiter, TemplateExpr),
|
||||
}
|
||||
|
||||
impl TemplateToken {
|
||||
/// Replace relevant placeholder tokens with the provided tokens
|
||||
fn replace(&self, replacement: &TokenStream) -> TokenStream {
|
||||
match self {
|
||||
Self::Group(del, template) => {
|
||||
let replaced_tokens = template
|
||||
.tokens
|
||||
.iter()
|
||||
.map(|token| token.replace(replacement));
|
||||
proc_macro2::Group::new(*del, quote::quote! { #(#replaced_tokens)* })
|
||||
.to_token_stream()
|
||||
}
|
||||
Self::Normal(token_tree) => token_tree.to_token_stream(),
|
||||
Self::Placeholder(_) => replacement.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// An expr attribute's template
|
||||
pub struct TemplateExpr {
|
||||
tokens: Vec<TemplateToken>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TemplateExpr {
|
||||
/// Parsing a template means storing the raw template while differenciating
|
||||
/// placeholders and "normal" tokens
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut tokens = Vec::new();
|
||||
while !input.is_empty() {
|
||||
if input.fork().parse::<ExprPlaceHolder>().is_ok() {
|
||||
let placeholder = input.parse()?;
|
||||
tokens.push(TemplateToken::Placeholder(Placeholder::ExprPlaceholder(
|
||||
placeholder,
|
||||
)));
|
||||
continue;
|
||||
}
|
||||
|
||||
match input.parse()? {
|
||||
TokenTree::Group(group) => {
|
||||
let inner_stream = group.stream();
|
||||
let inner_expr = syn::parse2(inner_stream)?;
|
||||
tokens.push(TemplateToken::Group(group.delimiter(), inner_expr));
|
||||
}
|
||||
other => {
|
||||
tokens.push(TemplateToken::Normal(other));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TemplateExpr { tokens })
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateExpr {
|
||||
/// returns the template after expanding the relevant placeholders
|
||||
pub fn expand_template(&self, replacement: &TokenStream) -> TokenStream {
|
||||
self.tokens.iter().fold(TokenStream::new(), |mut ts, tok| {
|
||||
ts.extend(tok.replace(replacement));
|
||||
ts
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TraitTarget {
|
||||
type_path: TypePath,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for TraitTarget {
|
||||
fn parse(input: ParseStream) -> Result<Self, Error> {
|
||||
let type_path: TypePath = input.parse().map_err(|error| {
|
||||
Error::new(
|
||||
input.span(),
|
||||
format!("{error}\nExpected trait path, e.g. #[through(foo::MyTrait)]"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(TraitTarget { type_path })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ReturnExpression {
|
||||
Into(Option<TypePath>),
|
||||
TryInto,
|
||||
Unwrap,
|
||||
}
|
||||
|
||||
pub enum TargetSpecifier {
|
||||
Field(GetFieldAttribute),
|
||||
Method(CallMethodAttribute),
|
||||
}
|
||||
|
||||
impl TargetSpecifier {
|
||||
pub fn get_member(&self, default: &syn::Ident) -> syn::Member {
|
||||
match self {
|
||||
Self::Field(GetFieldAttribute {
|
||||
member: Some(member),
|
||||
..
|
||||
}) => member.clone(),
|
||||
Self::Field(_) => default.clone().into(),
|
||||
Self::Method(method) => method.name.clone().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ParsedAttribute {
|
||||
ReturnExpression(ReturnExpression),
|
||||
Await(bool),
|
||||
TargetSpecifier(TargetSpecifier),
|
||||
ThroughTrait(TraitTarget),
|
||||
ConstantAccess(AssociatedConstant),
|
||||
Expr(TemplateExpr),
|
||||
}
|
||||
|
||||
fn parse_attributes(
|
||||
attrs: &[Attribute],
|
||||
) -> (
|
||||
impl Iterator<Item = ParsedAttribute> + '_,
|
||||
impl Iterator<Item = &Attribute>,
|
||||
) {
|
||||
let (parsed, other): (Vec<_>, Vec<_>) = attrs
|
||||
.iter()
|
||||
.map(|attribute| {
|
||||
let parsed = if let syn::AttrStyle::Outer = attribute.style {
|
||||
let name = attribute
|
||||
.path()
|
||||
.get_ident()
|
||||
.map(|i| i.to_string())
|
||||
.unwrap_or_default();
|
||||
match name.as_str() {
|
||||
"call" => {
|
||||
let target = attribute
|
||||
.parse_args::<CallMethodAttribute>()
|
||||
.expect("Cannot parse `call` attribute");
|
||||
let spec = TargetSpecifier::Method(target);
|
||||
Some(ParsedAttribute::TargetSpecifier(spec))
|
||||
}
|
||||
"field" => {
|
||||
let target = if let syn::Meta::Path(_) = &attribute.meta {
|
||||
GetFieldAttribute::default()
|
||||
} else {
|
||||
attribute
|
||||
.parse_args::<GetFieldAttribute>()
|
||||
.expect("Cannot parse `field` attribute")
|
||||
};
|
||||
let spec = TargetSpecifier::Field(target);
|
||||
Some(ParsedAttribute::TargetSpecifier(spec))
|
||||
}
|
||||
"into" => {
|
||||
let into = match &attribute.meta {
|
||||
Meta::NameValue(_) => {
|
||||
panic!("Cannot parse `into` attribute: expected parentheses")
|
||||
}
|
||||
Meta::Path(_) => IntoAttribute { type_path: None },
|
||||
Meta::List(meta) => meta
|
||||
.parse_args::<IntoAttribute>()
|
||||
.expect("Cannot parse `into` attribute"),
|
||||
};
|
||||
Some(ParsedAttribute::ReturnExpression(ReturnExpression::Into(
|
||||
into.type_path,
|
||||
)))
|
||||
}
|
||||
"try_into" => {
|
||||
if let Meta::List(meta) = &attribute.meta {
|
||||
meta.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("unwrap") {
|
||||
panic!(
|
||||
"Replace #[try_into(unwrap)] with\n#[try_into]\n#[unwrap]",
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.expect("Invalid `try_into` arguments");
|
||||
}
|
||||
Some(ParsedAttribute::ReturnExpression(ReturnExpression::TryInto))
|
||||
}
|
||||
"unwrap" => Some(ParsedAttribute::ReturnExpression(ReturnExpression::Unwrap)),
|
||||
"await" => {
|
||||
let generate = attribute
|
||||
.parse_args::<GenerateAwaitAttribute>()
|
||||
.expect("Cannot parse `await` attribute");
|
||||
Some(ParsedAttribute::Await(generate.literal.value))
|
||||
}
|
||||
"through" => Some(ParsedAttribute::ThroughTrait(
|
||||
attribute
|
||||
.parse_args::<TraitTarget>()
|
||||
.expect("Cannot parse `through` attribute"),
|
||||
)),
|
||||
"const" => Some(ParsedAttribute::ConstantAccess(
|
||||
attribute
|
||||
.parse_args::<AssociatedConstant>()
|
||||
.expect("Cannot parse `const` attribute"),
|
||||
)),
|
||||
"expr" => Some(ParsedAttribute::Expr(
|
||||
attribute
|
||||
.parse_args::<TemplateExpr>()
|
||||
.expect("Cannot parse `expr` attribute"),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
(parsed, attribute)
|
||||
})
|
||||
.partition(|(parsed, _)| parsed.is_some());
|
||||
(
|
||||
parsed.into_iter().map(|(parsed, _)| parsed.unwrap()),
|
||||
other.into_iter().map(|(_, attr)| attr),
|
||||
)
|
||||
}
|
||||
|
||||
pub struct MethodAttributes<'a> {
|
||||
pub attributes: Vec<&'a Attribute>,
|
||||
pub target_specifier: Option<TargetSpecifier>,
|
||||
pub expressions: VecDeque<ReturnExpression>,
|
||||
pub generate_await: Option<bool>,
|
||||
pub target_trait: Option<TypePath>,
|
||||
pub associated_constant: Option<AssociatedConstant>,
|
||||
pub expr_attr: Option<TemplateExpr>,
|
||||
}
|
||||
|
||||
/// Iterates through the attributes of a method and filters special attributes.
|
||||
/// - call => sets the name of the target method to call
|
||||
/// - into => generates a `into()` call after the delegated expression
|
||||
/// - try_into => generates a `try_into()` call after the delegated expression
|
||||
/// - await => generates an `.await` expression after the delegated expression
|
||||
/// - unwrap => generates a `unwrap()` call after the delegated expression
|
||||
/// - through => generates a UFCS call (`Target::method(&<expr>, ...)`) around the delegated expression
|
||||
/// - const => generates a getter to a trait associated constant
|
||||
pub fn parse_method_attributes<'a>(
|
||||
attrs: &'a [Attribute],
|
||||
method: &syn::TraitItemFn,
|
||||
) -> MethodAttributes<'a> {
|
||||
let mut target_spec: Option<TargetSpecifier> = None;
|
||||
let mut expressions: Vec<ReturnExpression> = vec![];
|
||||
let mut generate_await: Option<bool> = None;
|
||||
let mut target_trait: Option<TraitTarget> = None;
|
||||
let mut associated_constant: Option<AssociatedConstant> = None;
|
||||
let mut expr_attr: Option<TemplateExpr> = None;
|
||||
|
||||
let (parsed, other) = parse_attributes(attrs);
|
||||
for attr in parsed {
|
||||
match attr {
|
||||
ParsedAttribute::ReturnExpression(expr) => expressions.push(expr),
|
||||
ParsedAttribute::Await(value) => {
|
||||
if generate_await.is_some() {
|
||||
panic!(
|
||||
"Multiple `await` attributes specified for {}",
|
||||
method.sig.ident
|
||||
)
|
||||
}
|
||||
generate_await = Some(value);
|
||||
}
|
||||
ParsedAttribute::TargetSpecifier(spec) => {
|
||||
if target_spec.is_some() {
|
||||
panic!(
|
||||
"Multiple field/call attributes specified for {}",
|
||||
method.sig.ident
|
||||
)
|
||||
}
|
||||
target_spec = Some(spec);
|
||||
}
|
||||
ParsedAttribute::ThroughTrait(target) => {
|
||||
if target_trait.is_some() {
|
||||
panic!(
|
||||
"Multiple through attributes specified for {}",
|
||||
method.sig.ident
|
||||
)
|
||||
}
|
||||
target_trait = Some(target);
|
||||
}
|
||||
ParsedAttribute::ConstantAccess(const_attr) => {
|
||||
if associated_constant.is_some() {
|
||||
panic!(
|
||||
"Multiple const attributes specified for {}",
|
||||
method.sig.ident
|
||||
)
|
||||
}
|
||||
associated_constant = Some(const_attr);
|
||||
}
|
||||
ParsedAttribute::Expr(token_tree) => {
|
||||
if expr_attr.is_some() {
|
||||
panic!(
|
||||
"Multiple expr attributes specified for {}",
|
||||
method.sig.ident
|
||||
)
|
||||
}
|
||||
expr_attr = Some(token_tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if associated_constant.is_some() && target_spec.is_some() {
|
||||
panic!("Cannot use both `call`/`field` and `const` attributes.");
|
||||
}
|
||||
|
||||
MethodAttributes {
|
||||
attributes: other.into_iter().collect(),
|
||||
target_specifier: target_spec,
|
||||
generate_await,
|
||||
expressions: expressions.into(),
|
||||
target_trait: target_trait.map(|t| t.type_path),
|
||||
associated_constant,
|
||||
expr_attr,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SegmentAttributes {
|
||||
pub expressions: Vec<ReturnExpression>,
|
||||
pub generate_await: Option<bool>,
|
||||
pub target_trait: Option<TypePath>,
|
||||
pub other_attrs: Vec<Attribute>,
|
||||
pub expr_attr: Option<TemplateExpr>,
|
||||
}
|
||||
|
||||
pub fn parse_segment_attributes(attrs: &[Attribute]) -> SegmentAttributes {
|
||||
let mut expressions: Vec<ReturnExpression> = vec![];
|
||||
let mut generate_await: Option<bool> = None;
|
||||
let mut target_trait: Option<TraitTarget> = None;
|
||||
let mut expr_attr: Option<TemplateExpr> = None;
|
||||
|
||||
let (parsed, other) = parse_attributes(attrs);
|
||||
|
||||
for attribute in parsed {
|
||||
match attribute {
|
||||
ParsedAttribute::ReturnExpression(expr) => expressions.push(expr),
|
||||
ParsedAttribute::Await(value) => {
|
||||
if generate_await.is_some() {
|
||||
panic!("Multiple `await` attributes specified for segment");
|
||||
}
|
||||
generate_await = Some(value);
|
||||
}
|
||||
ParsedAttribute::ThroughTrait(target) => {
|
||||
if target_trait.is_some() {
|
||||
panic!("Multiple `through` attributes specified for segment");
|
||||
}
|
||||
target_trait = Some(target);
|
||||
}
|
||||
ParsedAttribute::TargetSpecifier(_) => {
|
||||
panic!("Field/call attribute cannot be specified on a `to <expr>` segment.");
|
||||
}
|
||||
ParsedAttribute::ConstantAccess(_) => {
|
||||
panic!("Const attribute cannot be specified on a `to <expr>` segment.");
|
||||
}
|
||||
ParsedAttribute::Expr(token_tree) => {
|
||||
if expr_attr.is_some() {
|
||||
panic!("Multiple `expr` attributes specified for segment");
|
||||
}
|
||||
expr_attr = Some(token_tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
SegmentAttributes {
|
||||
expressions,
|
||||
generate_await,
|
||||
target_trait: target_trait.map(|t| t.type_path),
|
||||
other_attrs: other.cloned().collect::<Vec<_>>(),
|
||||
expr_attr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies default values from the segment and adds them to the method attributes.
|
||||
pub fn combine_attributes<'a>(
|
||||
mut method_attrs: MethodAttributes<'a>,
|
||||
segment_attrs: &'a SegmentAttributes,
|
||||
) -> MethodAttributes<'a> {
|
||||
let SegmentAttributes {
|
||||
expressions,
|
||||
generate_await,
|
||||
target_trait,
|
||||
other_attrs,
|
||||
expr_attr,
|
||||
} = segment_attrs;
|
||||
|
||||
if method_attrs.generate_await.is_none() {
|
||||
method_attrs.generate_await = *generate_await;
|
||||
}
|
||||
|
||||
if method_attrs.target_trait.is_none() {
|
||||
method_attrs.target_trait.clone_from(target_trait);
|
||||
}
|
||||
|
||||
if method_attrs.expr_attr.is_none() {
|
||||
method_attrs.expr_attr.clone_from(expr_attr);
|
||||
}
|
||||
|
||||
for expr in expressions {
|
||||
match expr {
|
||||
ReturnExpression::Into(path) => {
|
||||
if !method_attrs
|
||||
.expressions
|
||||
.iter()
|
||||
.any(|expr| matches!(expr, ReturnExpression::Into(_)))
|
||||
{
|
||||
method_attrs
|
||||
.expressions
|
||||
.push_front(ReturnExpression::Into(path.clone()));
|
||||
}
|
||||
}
|
||||
_ => method_attrs.expressions.push_front(expr.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
for other_attr in other_attrs {
|
||||
if !method_attrs
|
||||
.attributes
|
||||
.iter()
|
||||
.any(|attr| attr.path().get_ident() == other_attr.path().get_ident())
|
||||
{
|
||||
method_attrs.attributes.push(other_attr);
|
||||
}
|
||||
}
|
||||
|
||||
method_attrs
|
||||
}
|
||||
Vendored
+1177
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user