Initial commit
This commit is contained in:
commit
9d17672a76
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
173
Cargo.lock
generated
Normal file
173
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "e"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-easy-default",
|
||||
"e-easy-from",
|
||||
"e-easy-get",
|
||||
"e-impl-for-refs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-default"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-easy-default-core",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-default-core"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-from"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-easy-from-core",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-from-core"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-get"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-easy-get-core",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-easy-get-core"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-easy-default",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-impl-for-refs"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"e-impl-for-refs-core",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e-impl-for-refs-core"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[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.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[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.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[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"
|
||||
29
Cargo.toml
Normal file
29
Cargo.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "e"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"proc-macros/easy-default/core",
|
||||
"proc-macros/easy-default",
|
||||
"proc-macros/easy-from/core",
|
||||
"proc-macros/easy-from",
|
||||
"proc-macros/easy-get/core",
|
||||
"proc-macros/easy-get",
|
||||
"proc-macros/impl-for-refs/core",
|
||||
"proc-macros/impl-for-refs",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[dependencies]
|
||||
e-easy-default = { path = "proc-macros/easy-default" }
|
||||
e-easy-from = { path = "proc-macros/easy-from" }
|
||||
e-easy-get = { path = "proc-macros/easy-get" }
|
||||
e-impl-for-refs = { path = "proc-macros/impl-for-refs" }
|
||||
|
||||
[workspace.dependencies]
|
||||
proc-macro-error = "1.0"
|
||||
proc-macro2 = "1"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
quote = "1"
|
||||
97
flake.lock
Normal file
97
flake.lock
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710483719,
|
||||
"narHash": "sha256-Ev/hJ59IAA3dWfTB3CWxMv/V/owO1yKyq0nwsek/d9o=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "d0439c495e5cd13ff252ade520ca620f52abb40b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1707347730,
|
||||
"narHash": "sha256-0etC/exQIaqC9vliKhc3eZE2Mm2wgLa0tj93ZF/egvM=",
|
||||
"path": "/nix/store/l6ymqz1y4fiy57xh6f3y23zwp0i9lr4f-source",
|
||||
"rev": "6832d0d99649db3d65a0e15fa51471537b2c56a6",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1710430493,
|
||||
"narHash": "sha256-KfmUsf/d62ANcFhSTR3BDIpk2ww0AcxXdi9lpZJ5UtQ=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "14558af15ee3d471bf8f4212f7609ae1f9647bc5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
26
flake.nix
Normal file
26
flake.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
inputs.nixpkgs.url = "nixpkgs";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, fenix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
fenix' = fenix.packages.${system};
|
||||
nightly = fenix'.default;
|
||||
stable = fenix'.stable;
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [ (fenix'.combine [
|
||||
(stable.withComponents [
|
||||
"cargo" "rustc" "rust-src" "rust-analyzer" "clippy"
|
||||
])
|
||||
nightly.rustfmt
|
||||
]) pkgs.cargo-watch ];
|
||||
};
|
||||
});
|
||||
}
|
||||
15
proc-macros/easy-default/Cargo.toml
Normal file
15
proc-macros/easy-default/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "e-easy-default"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
e-easy-default-core = { path = "./core" }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
10
proc-macros/easy-default/core/Cargo.toml
Normal file
10
proc-macros/easy-default/core/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "e-easy-default-core"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
213
proc-macros/easy-default/core/src/lib.rs
Normal file
213
proc-macros/easy-default/core/src/lib.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{Fields, ItemStruct, Meta};
|
||||
|
||||
pub fn easy_default_core(input: TokenStream) -> TokenStream {
|
||||
let input: ItemStruct = match syn::parse2(input) {
|
||||
Ok(i) => i,
|
||||
Err(e) => return e.to_compile_error(),
|
||||
};
|
||||
|
||||
let struct_name = input.ident;
|
||||
let (ipl, ty, _) = input.generics.split_for_impl();
|
||||
|
||||
match input.fields {
|
||||
Fields::Named(n) => {
|
||||
let mut exprs = Vec::new();
|
||||
|
||||
for field in n.named {
|
||||
let default_expr = field.attrs.iter().find_map(|a| {
|
||||
let default_expr = match &a.meta {
|
||||
Meta::List(list) => match list.path.get_ident() {
|
||||
Some(i) if i == "default" => &list.tokens,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(default_expr)
|
||||
});
|
||||
|
||||
let ident = field.ident.expect("named field should have name");
|
||||
match default_expr {
|
||||
Some(expr) => exprs.push(quote! {
|
||||
#ident: #expr,
|
||||
}),
|
||||
None => exprs.push(quote! {
|
||||
#ident: ::core::default::Default::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
let output = quote! {
|
||||
impl #ipl ::core::default::Default for #struct_name #ty {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#(#exprs)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
output
|
||||
},
|
||||
Fields::Unnamed(u) => {
|
||||
let mut exprs = Vec::new();
|
||||
|
||||
for field in u.unnamed {
|
||||
let default_expr = field.attrs.iter().find_map(|a| {
|
||||
let default_expr = match &a.meta {
|
||||
Meta::List(list) => match list.path.get_ident() {
|
||||
Some(i) if i == "default" => &list.tokens,
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(default_expr)
|
||||
});
|
||||
|
||||
match default_expr {
|
||||
Some(expr) => exprs.push(quote! {
|
||||
#expr
|
||||
}),
|
||||
None => exprs.push(quote! {
|
||||
::core::default::Default::default()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
let output = quote! {
|
||||
impl #ipl ::core::default::Default for #struct_name #ty {
|
||||
fn default() -> Self {
|
||||
Self(#(#exprs),*)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
output
|
||||
},
|
||||
Fields::Unit => quote! {
|
||||
impl #ipl ::core::default::Default for #struct_name #ty {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::easy_default_core;
|
||||
|
||||
#[test]
|
||||
fn named_fields_struct() {
|
||||
let before = quote! {
|
||||
#[derive(EasyDefault)]
|
||||
struct Test {
|
||||
#[default("Test".to_string())]
|
||||
foo: String,
|
||||
bar: String,
|
||||
}
|
||||
};
|
||||
|
||||
let after = easy_default_core(before);
|
||||
assert_eq!(
|
||||
after.to_string(),
|
||||
quote! {
|
||||
impl ::core::default::Default for Test {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
foo: "Test".to_string(),
|
||||
bar: ::core::default::Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_struct() {
|
||||
let before = quote! {
|
||||
#[derive(EasyDefault)]
|
||||
struct Test(#[default(5)] u64);
|
||||
};
|
||||
|
||||
let after = easy_default_core(before);
|
||||
assert_eq!(
|
||||
after.to_string(),
|
||||
quote! {
|
||||
impl ::core::default::Default for Test {
|
||||
fn default() -> Self {
|
||||
Self(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_struct() {
|
||||
let before = quote! {
|
||||
#[derive(EasyDefault)]
|
||||
struct Test;
|
||||
};
|
||||
|
||||
let after = easy_default_core(before);
|
||||
assert_eq!(
|
||||
after.to_string(),
|
||||
quote! {
|
||||
impl ::core::default::Default for Test {
|
||||
fn default() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_output(input: TokenStream, expected: TokenStream) {
|
||||
let after = easy_default_core(input);
|
||||
assert_eq!(after.to_string(), expected.to_string(),)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other1() {
|
||||
assert_output(
|
||||
quote! {
|
||||
pub struct ObjectMeta {
|
||||
#[default(ObjectKind::Orphan)]
|
||||
pub kind: ObjectKind,
|
||||
#[default(OffsetDateTime::now_utc())]
|
||||
pub created_at: OffsetDateTime,
|
||||
#[default(OffsetDateTime::now_utc())]
|
||||
pub updated_at: OffsetDateTime,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl ::core::default::Default for ObjectMeta {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kind: ObjectKind::Orphan,
|
||||
created_at: OffsetDateTime::now_utc(),
|
||||
updated_at: OffsetDateTime::now_utc(),
|
||||
name: ::core::default::Default::default(),
|
||||
description: ::core::default::Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
9
proc-macros/easy-default/src/lib.rs
Normal file
9
proc-macros/easy-default/src/lib.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use e_easy_default_core::easy_default_core;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_derive(EasyDefault, attributes(default))]
|
||||
pub fn easy_default(input: TokenStream) -> TokenStream {
|
||||
easy_default_core(input.into()).into()
|
||||
}
|
||||
15
proc-macros/easy-from/Cargo.toml
Normal file
15
proc-macros/easy-from/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "e-easy-from"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
e-easy-from-core = { path = "./core" }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
10
proc-macros/easy-from/core/Cargo.toml
Normal file
10
proc-macros/easy-from/core/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "e-easy-from-core"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
214
proc-macros/easy-from/core/src/lib.rs
Normal file
214
proc-macros/easy-from/core/src/lib.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, TokenStreamExt};
|
||||
use syn::{spanned::Spanned, Fields, Ident, ItemEnum, ItemStruct, Meta};
|
||||
|
||||
fn handle_enum(e: ItemEnum) -> TokenStream {
|
||||
let name = &e.ident;
|
||||
|
||||
let mut stream: TokenStream = e
|
||||
.variants
|
||||
.iter()
|
||||
.filter_map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
let has_from_attr = variant.attrs.iter().any(|a| {
|
||||
let path = match &a.meta {
|
||||
Meta::Path(p) => p,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
path.get_ident().is_some_and(|i| *i == "from")
|
||||
});
|
||||
|
||||
if !has_from_attr {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Named(_n) => todo!(),
|
||||
Fields::Unnamed(u) => {
|
||||
match u.unnamed.iter().collect::<Vec<_>>().as_slice() {
|
||||
&[] => todo!(),
|
||||
&[field] => {
|
||||
let ty = &field.ty;
|
||||
Some(quote! {
|
||||
impl ::core::convert::From<#ty> for #name {
|
||||
fn from(value: #ty) -> Self {
|
||||
Self::#variant_name(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
_sl => todo!(),
|
||||
}
|
||||
},
|
||||
Fields::Unit => todo!(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let has_convert_infallible = e.attrs.iter().any(|a| match &a.meta {
|
||||
Meta::List(list) => {
|
||||
if list.path.get_ident().is_some_and(|i| *i == "from") {
|
||||
let ident: Ident = match syn::parse2(list.tokens.clone()) {
|
||||
Ok(i) => i,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
ident == "infallible"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if has_convert_infallible {
|
||||
stream.append_all(quote! {
|
||||
impl ::core::convert::From<::core::convert::Infallible> for #name {
|
||||
fn from(value: ::core::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stream
|
||||
}
|
||||
|
||||
fn handle_struct(s: ItemStruct) -> TokenStream {
|
||||
match &s.fields {
|
||||
Fields::Unnamed(u) => {
|
||||
if u.unnamed.len() != 1 {
|
||||
proc_macro_error::abort!(
|
||||
s.span(),
|
||||
"Tuple struct must have single field"
|
||||
);
|
||||
}
|
||||
|
||||
let field = u.unnamed.first().expect("Should be len 1");
|
||||
let ty = &field.ty;
|
||||
let name = &s.ident;
|
||||
let generics = &s.generics;
|
||||
|
||||
quote! {
|
||||
impl #generics ::core::convert::From<#ty> for #name #generics {
|
||||
fn from(value: #ty) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn easy_from_core(input: TokenStream) -> TokenStream {
|
||||
match syn::parse2(input.clone()) {
|
||||
Ok(e) => handle_enum(e),
|
||||
Err(_) => match syn::parse2(input) {
|
||||
Ok(s) => handle_struct(s),
|
||||
Err(e) => e.to_compile_error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use quote::quote;
|
||||
|
||||
use crate::easy_from_core;
|
||||
|
||||
#[test]
|
||||
fn test_enum_all_fields_covered() {
|
||||
let input = quote! {
|
||||
#[derive(EasyFrom)]
|
||||
enum Foo {
|
||||
#[from]
|
||||
Bar(u64),
|
||||
|
||||
#[from]
|
||||
Baz(String),
|
||||
}
|
||||
};
|
||||
let output = easy_from_core(input).to_string();
|
||||
let expected = quote! {
|
||||
impl ::core::convert::From<u64> for Foo {
|
||||
fn from(value: u64) -> Self {
|
||||
Self::Bar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::core::convert::From<String> for Foo {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Baz(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string();
|
||||
|
||||
assert_eq!(output, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_some_fields_covered() {
|
||||
let input = quote! {
|
||||
#[derive(EasyFrom)]
|
||||
enum Foo {
|
||||
#[from]
|
||||
Bar(u64),
|
||||
Baz(String),
|
||||
}
|
||||
};
|
||||
let output = easy_from_core(input).to_string();
|
||||
let expected = quote! {
|
||||
impl ::core::convert::From<u64> for Foo {
|
||||
fn from(value: u64) -> Self {
|
||||
Self::Bar(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string();
|
||||
|
||||
assert_eq!(output, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_enum_from_infallible() {
|
||||
let input = quote! {
|
||||
#[derive(EasyFrom)]
|
||||
#[from(infallible)]
|
||||
enum Never {}
|
||||
};
|
||||
let output = easy_from_core(input).to_string();
|
||||
let expected = quote! {
|
||||
impl ::core::convert::From<::core::convert::Infallible> for Never {
|
||||
fn from(value: ::core::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string();
|
||||
|
||||
assert_eq!(output, expected)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_newtype_struct() {
|
||||
let input = quote! {
|
||||
#[derive(EasyFrom)]
|
||||
struct NewType<T>(#[from] T);
|
||||
};
|
||||
let output = easy_from_core(input).to_string();
|
||||
let expected = quote! {
|
||||
impl<T> ::core::convert::From<T> for NewType<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string();
|
||||
|
||||
assert_eq!(output, expected)
|
||||
}
|
||||
}
|
||||
9
proc-macros/easy-from/src/lib.rs
Normal file
9
proc-macros/easy-from/src/lib.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use e_easy_from_core::easy_from_core;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_derive(EasyFrom, attributes(from))]
|
||||
pub fn easy_from(input: TokenStream) -> TokenStream {
|
||||
easy_from_core(input.into()).into()
|
||||
}
|
||||
15
proc-macros/easy-get/Cargo.toml
Normal file
15
proc-macros/easy-get/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "e-easy-get"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
e-easy-get-core = { path = "./core" }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
12
proc-macros/easy-get/core/Cargo.toml
Normal file
12
proc-macros/easy-get/core/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "e-easy-get-core"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
e-easy-default = { path = "../../easy-default" }
|
||||
280
proc-macros/easy-get/core/src/lib.rs
Normal file
280
proc-macros/easy-get/core/src/lib.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
Attribute, Field, Fields, FieldsNamed, Generics, Ident, ItemStruct, LitStr,
|
||||
Meta,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
enum Visibility {
|
||||
#[default]
|
||||
Inherited,
|
||||
Pub,
|
||||
}
|
||||
|
||||
impl ToTokens for Visibility {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Visibility::Pub => tokens.append_all(quote! { pub }),
|
||||
Visibility::Inherited => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct GetOptions {
|
||||
visibility: Visibility,
|
||||
rename: Option<String>,
|
||||
}
|
||||
impl GetOptions {
|
||||
fn from_attr(attr: &Attribute) -> syn::Result<Self> {
|
||||
let mut out = Self::default();
|
||||
attr.parse_nested_meta(|nm| {
|
||||
if nm.path.is_ident("pub") {
|
||||
out.visibility = Visibility::Pub;
|
||||
}
|
||||
|
||||
if nm.path.is_ident("rename") {
|
||||
let name = nm.value()?;
|
||||
let name: LitStr = name.parse()?;
|
||||
|
||||
out.rename = Some(name.value());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn named_fields(
|
||||
name: Ident,
|
||||
generics: Generics,
|
||||
n: FieldsNamed,
|
||||
) -> TokenStream {
|
||||
let (ipl, ty, _) = generics.split_for_impl();
|
||||
|
||||
n.named
|
||||
.into_iter()
|
||||
.flat_map(|field| {
|
||||
let field_name =
|
||||
field.ident.as_ref().expect("named should have ident");
|
||||
let field_ty = &field.ty;
|
||||
|
||||
fn options_for(
|
||||
attr_name: &str,
|
||||
field: &Field,
|
||||
) -> Option<syn::Result<GetOptions>> {
|
||||
field.attrs.iter().find_map(|a| match &a.meta {
|
||||
Meta::List(list) => list
|
||||
.path
|
||||
.get_ident()
|
||||
.filter(|i| *i == attr_name)
|
||||
.map(|_| GetOptions::from_attr(a)),
|
||||
Meta::Path(p) => p
|
||||
.get_ident()
|
||||
.filter(|i| *i == attr_name)
|
||||
.map(|_| Ok(GetOptions::default())),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
let get = options_for("get", &field);
|
||||
|
||||
fn output_for(
|
||||
options: Option<syn::Result<GetOptions>>,
|
||||
field_name: &Ident,
|
||||
f: impl FnOnce(Ident, Visibility) -> TokenStream,
|
||||
) -> Option<TokenStream> {
|
||||
match options {
|
||||
Some(Ok(get)) => {
|
||||
let GetOptions {
|
||||
visibility, rename, ..
|
||||
} = get;
|
||||
|
||||
let fn_name = if let Some(rename) = rename {
|
||||
format_ident!("{}", rename)
|
||||
} else {
|
||||
field_name.clone()
|
||||
};
|
||||
|
||||
Some(f(fn_name, visibility))
|
||||
},
|
||||
Some(Err(e)) => Some(e.into_compile_error()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
let get_out = output_for(get, field_name, |fn_name, vis| {
|
||||
quote! {
|
||||
impl #ipl #name #ty {
|
||||
#vis fn #fn_name(&self) -> #field_ty {
|
||||
self.#field_name
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let get_ref = options_for("get_ref", &field);
|
||||
|
||||
let get_ref_out =
|
||||
output_for(get_ref, field_name, |fn_name, vis| {
|
||||
quote! {
|
||||
impl #ipl #name #ty {
|
||||
#vis fn #fn_name(&self) -> &#field_ty {
|
||||
&self.#field_name
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let get_mut = options_for("get_mut", &field);
|
||||
let get_mut_renamed = get_mut
|
||||
.as_ref()
|
||||
.is_some_and(|r| r.as_ref().is_ok_and(|o| o.rename.is_some()));
|
||||
let get_mut_out =
|
||||
output_for(get_mut, field_name, |fn_name, vis| {
|
||||
let fn_name = if get_mut_renamed {
|
||||
fn_name
|
||||
} else {
|
||||
format_ident!("{}_mut", fn_name)
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #ipl #name #ty {
|
||||
#vis fn #fn_name(&mut self) -> &mut #field_ty {
|
||||
&mut self.#field_name
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
get_out.into_iter().chain(get_ref_out).chain(get_mut_out)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn e_easy_get_core(input: TokenStream) -> TokenStream {
|
||||
let item: ItemStruct = match syn::parse2(input) {
|
||||
Ok(i) => i,
|
||||
Err(e) => return e.into_compile_error(),
|
||||
};
|
||||
|
||||
match item.fields {
|
||||
Fields::Named(n) => named_fields(item.ident, item.generics, n),
|
||||
_ => todo!("aaaaaa"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::e_easy_get_core;
|
||||
|
||||
fn expect_output(input: TokenStream, output: TokenStream) {
|
||||
assert_eq!(e_easy_get_core(input).to_string(), output.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pub() {
|
||||
expect_output(
|
||||
quote! {
|
||||
#[derive(EasyGet)]
|
||||
pub struct Foo {
|
||||
#[get(pub)]
|
||||
bar: usize,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl Foo {
|
||||
pub fn bar(&self) -> usize {
|
||||
self.bar
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_generic() {
|
||||
expect_output(
|
||||
quote! {
|
||||
#[derive(EasyGet)]
|
||||
pub struct Foo<T> {
|
||||
#[get(pub)]
|
||||
bar: T,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl<T> Foo<T> {
|
||||
pub fn bar(&self) -> T {
|
||||
self.bar
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_ref_generic_default() {
|
||||
expect_output(
|
||||
quote! {
|
||||
#[derive(EasyGet)]
|
||||
pub struct Foo<T = usize> {
|
||||
#[get_ref(pub)]
|
||||
bar: T,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl<T> Foo<T> {
|
||||
pub fn bar(&self) -> &T {
|
||||
&self.bar
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename() {
|
||||
expect_output(
|
||||
quote! {
|
||||
#[derive(EasyGet)]
|
||||
pub struct Foo<T = usize> {
|
||||
#[get_ref(pub, rename = "baz")]
|
||||
bar: T,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl<T> Foo<T> {
|
||||
pub fn baz(&self) -> &T {
|
||||
&self.bar
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mut() {
|
||||
expect_output(
|
||||
quote! {
|
||||
#[derive(EasyGet)]
|
||||
pub struct Foo<T = usize> {
|
||||
#[get_mut(pub)]
|
||||
bar: T,
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
impl<T> Foo<T> {
|
||||
pub fn bar_mut(&mut self) -> &mut T {
|
||||
&mut self.bar
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
9
proc-macros/easy-get/src/lib.rs
Normal file
9
proc-macros/easy-get/src/lib.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use e_easy_get_core::e_easy_get_core;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_derive(EasyGet, attributes(get, get_ref, get_mut))]
|
||||
pub fn easy_get(input: TokenStream) -> TokenStream {
|
||||
e_easy_get_core(input.into()).into()
|
||||
}
|
||||
15
proc-macros/impl-for-refs/Cargo.toml
Normal file
15
proc-macros/impl-for-refs/Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "e-impl-for-refs"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
|
||||
e-impl-for-refs-core = { path = "./core" }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
10
proc-macros/impl-for-refs/core/Cargo.toml
Normal file
10
proc-macros/impl-for-refs/core/Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "e-impl-for-refs-core"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro-error = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
108
proc-macros/impl-for-refs/core/src/lib.rs
Normal file
108
proc-macros/impl-for-refs/core/src/lib.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{FnArg, ItemTrait, Pat};
|
||||
|
||||
pub fn impl_for_refs_core(
|
||||
_attrs: TokenStream,
|
||||
item: TokenStream,
|
||||
) -> TokenStream {
|
||||
let trt: ItemTrait = match syn::parse2(item.clone()) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.into_compile_error(),
|
||||
};
|
||||
|
||||
let trt_name = &trt.ident;
|
||||
|
||||
let fns = trt
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(|i| match i {
|
||||
syn::TraitItem::Fn(f) => {
|
||||
if f.default.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sig = &f.sig;
|
||||
let fn_name = &f.sig.ident;
|
||||
let args = sig.inputs.iter().map(|i| match i {
|
||||
FnArg::Receiver(_s) => {
|
||||
quote!((&**self))
|
||||
},
|
||||
FnArg::Typed(t) => match t.pat.as_ref() {
|
||||
Pat::Ident(e) => quote!(#e),
|
||||
_ => todo!(),
|
||||
},
|
||||
});
|
||||
|
||||
Some(quote! {
|
||||
#sig {
|
||||
#trt_name::#fn_name(#(#args),*)
|
||||
}
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.collect::<TokenStream>();
|
||||
|
||||
quote! {
|
||||
#item
|
||||
|
||||
impl<'a, T> #trt_name for &'a T where T: ?Sized + #trt_name {
|
||||
#fns
|
||||
}
|
||||
|
||||
impl<'a, T> #trt_name for &'a mut T where T: ?Sized + #trt_name {
|
||||
#fns
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use crate::impl_for_refs_core;
|
||||
|
||||
fn expect_output(
|
||||
attrs: TokenStream,
|
||||
input: TokenStream,
|
||||
output: TokenStream,
|
||||
) {
|
||||
assert_eq!(
|
||||
impl_for_refs_core(attrs, input).to_string(),
|
||||
output.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_works() {
|
||||
expect_output(
|
||||
quote!(),
|
||||
quote! {
|
||||
#[impl_for_refs]
|
||||
trait Test {
|
||||
fn test(&self) -> usize;
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
#[impl_for_refs]
|
||||
trait Test {
|
||||
fn test(&self) -> usize;
|
||||
}
|
||||
|
||||
impl<'a, T> Test for &'a T where T: ?Sized + Test {
|
||||
fn test(&self) -> usize {
|
||||
Test::test((&**self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Test for &'a mut T where T: ?Sized + Test {
|
||||
fn test(&self) -> usize {
|
||||
Test::test((&**self))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
9
proc-macros/impl-for-refs/src/lib.rs
Normal file
9
proc-macros/impl-for-refs/src/lib.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use e_impl_for_refs_core::impl_for_refs_core;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
|
||||
#[proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn impl_for_refs(attrs: TokenStream, item: TokenStream) -> TokenStream {
|
||||
impl_for_refs_core(attrs.into(), item.into()).into()
|
||||
}
|
||||
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
edition = "2021"
|
||||
hard_tabs = true
|
||||
match_block_trailing_comma = true
|
||||
max_width = 80
|
||||
empty_item_single_line = false
|
||||
244
src/lib.rs
Normal file
244
src/lib.rs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
#[cfg(derive)]
|
||||
pub mod derive {
|
||||
pub use e_easy_default::EasyDefault;
|
||||
pub use e_easy_from::EasyFrom;
|
||||
pub use e_easy_get::EasyGet;
|
||||
}
|
||||
|
||||
#[cfg(meta)]
|
||||
pub mod meta {
|
||||
pub use e_impl_for_refs::impl_for_refs;
|
||||
}
|
||||
|
||||
pub mod args {
|
||||
use std::error::Error;
|
||||
|
||||
use e_easy_get::EasyGet;
|
||||
|
||||
#[derive(Debug, EasyGet, Eq, PartialEq)]
|
||||
pub struct MissingParamFor<T = &'static str> {
|
||||
#[get_ref(pub)]
|
||||
arg_name: T,
|
||||
}
|
||||
impl<T: core::fmt::Debug> core::fmt::Display for MissingParamFor<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
impl<T: core::fmt::Debug> Error for MissingParamFor<T> {
|
||||
}
|
||||
|
||||
pub enum Arg<S> {
|
||||
/// An arg that starts with a single `-` character. If multiple
|
||||
/// characters follow the `-`, they will be returned as multiple,
|
||||
/// separate Shorts
|
||||
Short(char),
|
||||
/// An arg that starts with `--`
|
||||
Long(S),
|
||||
/// An arg that has no preceding dashes
|
||||
Param(S),
|
||||
}
|
||||
|
||||
enum ArgParserState {
|
||||
ParsingSingle(std::vec::IntoIter<char>),
|
||||
UseIterator,
|
||||
}
|
||||
|
||||
pub struct ArgParser<I, S> {
|
||||
iterator: I,
|
||||
state: ArgParserState,
|
||||
stored: Option<S>,
|
||||
}
|
||||
|
||||
impl<I, S> ArgParser<I, S>
|
||||
where
|
||||
I: Iterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
pub fn from_iterator(iterator: I) -> Self {
|
||||
Self {
|
||||
iterator,
|
||||
state: ArgParserState::UseIterator,
|
||||
stored: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn next(&mut self) -> Option<Arg<&str>> {
|
||||
loop {
|
||||
break match &mut self.state {
|
||||
ArgParserState::ParsingSingle(it) => {
|
||||
if let Some(c) = it.next() {
|
||||
Some(Arg::Short(c))
|
||||
} else {
|
||||
self.state = ArgParserState::UseIterator;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
ArgParserState::UseIterator => {
|
||||
self.stored = Some(self.iterator.next()?);
|
||||
|
||||
let stored = self.stored.as_ref().unwrap();
|
||||
let next = stored.as_ref();
|
||||
if let Some(long) = next.strip_prefix("--") {
|
||||
Some(Arg::Long(long))
|
||||
} else if let Some(short) = next.strip_prefix('-') {
|
||||
let mut chars = short.chars();
|
||||
let out = chars.next()?;
|
||||
let chars = chars.collect::<Vec<_>>().into_iter();
|
||||
self.state = ArgParserState::ParsingSingle(chars);
|
||||
|
||||
Some(Arg::Short(out))
|
||||
} else {
|
||||
Some(Arg::Param(next))
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_param_for<T>(
|
||||
&mut self,
|
||||
arg_name: T,
|
||||
) -> Result<&str, MissingParamFor<T>> {
|
||||
match self.next() {
|
||||
Some(Arg::Param(p)) => Ok(p),
|
||||
_ => Err(MissingParamFor { arg_name }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgParser<std::env::Args, String> {
|
||||
pub fn from_env() -> Self {
|
||||
let it = std::env::args();
|
||||
|
||||
Self::from_iterator(it)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use e_easy_default::EasyDefault;
|
||||
use e_easy_from::EasyFrom;
|
||||
|
||||
use crate::args::{Arg, ArgParser, MissingParamFor};
|
||||
|
||||
#[test]
|
||||
fn test_arg_parser() {
|
||||
#[derive(EasyDefault, Debug, PartialEq, Eq)]
|
||||
struct Args {
|
||||
#[default("Hello world".to_string())]
|
||||
string: String,
|
||||
#[default(false)]
|
||||
bool: bool,
|
||||
#[default(8)]
|
||||
u64: u64,
|
||||
}
|
||||
|
||||
#[derive(EasyFrom, Debug, PartialEq, Eq)]
|
||||
#[from(infallible)]
|
||||
enum Error {
|
||||
#[from]
|
||||
MissingParamFor(MissingParamFor<&'static str>),
|
||||
#[from]
|
||||
IntParseError(ParseIntError),
|
||||
}
|
||||
|
||||
fn parse_args(strs: &[&str]) -> Result<Args, Error> {
|
||||
let mut parser = ArgParser::from_iterator(strs.iter());
|
||||
let mut args = Args::default();
|
||||
|
||||
while let Some(arg) = parser.next() {
|
||||
match arg {
|
||||
Arg::Long("string") => {
|
||||
args.string =
|
||||
parser.next_param_for("string")?.to_string()
|
||||
},
|
||||
Arg::Long("bool") => {
|
||||
args.bool = true;
|
||||
},
|
||||
Arg::Long("u64") => {
|
||||
args.u64 = parser.next_param_for("u64")?.parse()?
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
parse_args(&["--string"]),
|
||||
Err(Error::MissingParamFor(MissingParamFor {
|
||||
arg_name: "string"
|
||||
}))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod str {
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(
|
||||
Default, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug,
|
||||
)]
|
||||
pub struct NonEmptyStr<'a>(&'a str);
|
||||
impl<'a> AsRef<str> for NonEmptyStr<'a> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
impl<'a> Display for NonEmptyStr<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StrExt {
|
||||
fn non_empty(&self) -> Option<NonEmptyStr>;
|
||||
}
|
||||
|
||||
impl<T> StrExt for T
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
fn non_empty(&self) -> Option<NonEmptyStr> {
|
||||
let s = self.as_ref();
|
||||
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(NonEmptyStr(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
use core::hash::{Hash, Hasher};
|
||||
|
||||
pub fn default<T: Default>() -> T {
|
||||
T::default()
|
||||
}
|
||||
|
||||
pub fn hash<T: Hasher + Default, U: Hash>(value: U) -> u64 {
|
||||
let mut hasher: T = default();
|
||||
value.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
pub struct DisplayFn<F: Fn(&mut core::fmt::Formatter) -> core::fmt::Result>(
|
||||
pub F,
|
||||
);
|
||||
impl<F> core::fmt::Display for DisplayFn<F>
|
||||
where
|
||||
F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.0(f)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue