Get macro functional with tests
This commit is contained in:
235
Cargo.lock
generated
235
Cargo.lock
generated
@@ -2,15 +2,135 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "comlexr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rstest",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[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-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[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 = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
@@ -29,6 +149,95 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89"
|
||||
dependencies = [
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
|
||||
|
||||
[[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 = "2.0.96"
|
||||
@@ -40,8 +249,34 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
23
Cargo.toml
23
Cargo.toml
@@ -3,7 +3,24 @@ name = "comlexr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.92"
|
||||
quote = "1.0.38"
|
||||
syn = { version = "2.0.96", features = ["full"] }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.24"
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[lints.clippy]
|
||||
correctness = "deny"
|
||||
suspicious = "deny"
|
||||
perf = "deny"
|
||||
style = "deny"
|
||||
nursery = "deny"
|
||||
pedantic = "deny"
|
||||
|
||||
83
bacon.toml
Normal file
83
bacon.toml
Normal file
@@ -0,0 +1,83 @@
|
||||
# This is a configuration file for the bacon tool
|
||||
#
|
||||
# Bacon repository: https://github.com/Canop/bacon
|
||||
# Complete help on configuration: https://dystroy.org/bacon/config/
|
||||
# You can also check bacon's own bacon.toml file
|
||||
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
|
||||
|
||||
default_job = "clippy-all"
|
||||
|
||||
[jobs.check]
|
||||
command = ["cargo", "check", "--color", "always"]
|
||||
need_stdout = false
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml"]
|
||||
|
||||
[jobs.check-all]
|
||||
command = ["cargo", "check", "--all-features", "--color", "always"]
|
||||
need_stdout = false
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml"]
|
||||
|
||||
[jobs.clippy]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml"]
|
||||
|
||||
[jobs.clippy-all]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--all-features",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml"]
|
||||
|
||||
[jobs.test]
|
||||
command = [
|
||||
"cargo", "+nightly", "test", "--color", "always", "--workspace",
|
||||
"--", "--color", "always",
|
||||
]
|
||||
env.RUSTFLAGS="-Zmacro-backtrace"
|
||||
need_stdout = true
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml", "tests"]
|
||||
|
||||
[jobs.test-all]
|
||||
command = [
|
||||
"cargo", "+nightly", "test", "--all-features", "--color", "always", "--workspace",
|
||||
"--", "--color", "always"
|
||||
]
|
||||
env.RUSTFLAGS="-Zmacro-backtrace"
|
||||
need_stdout = true
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml", "tests"]
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
||||
need_stdout = false
|
||||
default_watch = false
|
||||
watch = ["src", "Cargo.toml"]
|
||||
|
||||
# If the doc compiles, then it opens in your browser and bacon switches
|
||||
# to the previous job
|
||||
[jobs.doc-open]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
|
||||
need_stdout = false
|
||||
on_success = "back" # so that we don't open the browser at each change
|
||||
|
||||
# You may define here keybindings that would be specific to
|
||||
# a project, for example a shortcut to launch a specific job.
|
||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
||||
# should go in your personal global prefs.toml file instead.
|
||||
[keybindings]
|
||||
# alt-m = "job:my-job"
|
||||
c = "job:clippy"
|
||||
shift-c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
|
||||
t = "job:test"
|
||||
shift-t = "job:test-all"
|
||||
333
src/lib.rs
333
src/lib.rs
@@ -1,7 +1,8 @@
|
||||
use syn::{bracketed, parse::Parse, punctuated::Punctuated, token, Token};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{bracketed, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Token};
|
||||
|
||||
struct Command {
|
||||
program: Program,
|
||||
program: Value,
|
||||
args: Option<Punctuated<Arg, Token![,]>>,
|
||||
}
|
||||
|
||||
@@ -10,119 +11,257 @@ impl Parse for Command {
|
||||
let program = input.parse()?;
|
||||
|
||||
if input.is_empty() {
|
||||
Ok(Command {
|
||||
Ok(Self {
|
||||
program,
|
||||
args: None,
|
||||
})
|
||||
} else {
|
||||
_ = input.parse::<Token![,]>()?;
|
||||
Ok(Command {
|
||||
Ok(Self {
|
||||
program,
|
||||
args: Some(input.parse_terminated(Arg::parse, Token![,])?),
|
||||
args: Some(Punctuated::parse_terminated(input)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Program(syn::LitStr);
|
||||
impl ToTokens for Command {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let Self { program, args } = self;
|
||||
let program = quote! { ::std::process::Command::new(#program) };
|
||||
let args = args.as_ref().map(|args| args.iter());
|
||||
|
||||
impl Parse for Program {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.parse().map(Self)
|
||||
tokens.extend(args.map_or_else(
|
||||
|| quote! { #program },
|
||||
|args| {
|
||||
quote! {
|
||||
{
|
||||
let mut command = #program;
|
||||
#(#args)*
|
||||
command
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
enum Arg {
|
||||
Expression(syn::Expr),
|
||||
ForIter {
|
||||
for_key: Token![for],
|
||||
expr: syn::Expr,
|
||||
},
|
||||
ForInIterMulti {
|
||||
for_iter: ForIter,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
ForInIterSingle {
|
||||
for_iter: ForIter,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
IfLetMulti {
|
||||
if_let: IfLet,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
IfLetSingle {
|
||||
if_let: IfLet,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
IfMulti {
|
||||
expr: syn::Expr,
|
||||
arrow_token: Token![=>],
|
||||
args: MultiArg,
|
||||
},
|
||||
IfSingle {
|
||||
expr: syn::Expr,
|
||||
arrow_token: Token![=>],
|
||||
arg: SingleArg,
|
||||
},
|
||||
Expr(SingleArg),
|
||||
ForIter(ForIter),
|
||||
ForIn(ForIn),
|
||||
IfLet(IfLet),
|
||||
If(If),
|
||||
}
|
||||
|
||||
impl Parse for Arg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.parse()
|
||||
if input.peek(Token![for]) {
|
||||
if input.peek3(Token![in]) {
|
||||
input.parse().map(Self::ForIn)
|
||||
} else {
|
||||
input.parse().map(Self::ForIter)
|
||||
}
|
||||
} else if input.peek(Token![if]) {
|
||||
if input.peek2(Token![let]) {
|
||||
input.parse().map(Self::IfLet)
|
||||
} else {
|
||||
input.parse().map(Self::If)
|
||||
}
|
||||
} else {
|
||||
input.parse().map(Self::Expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Pattern(syn::Pat);
|
||||
|
||||
impl Parse for Pattern {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
input.call(syn::Pat::parse_single).map(Self)
|
||||
impl ToTokens for Arg {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
tokens.extend(match self {
|
||||
Self::Expr(expr) => quote! { #expr },
|
||||
Self::ForIter(for_iter) => quote! { #for_iter },
|
||||
Self::ForIn(for_in) => quote! { #for_in },
|
||||
Self::IfLet(if_let) => quote! { #if_let },
|
||||
Self::If(if_) => quote! { #if_ },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct ForIter {
|
||||
for_token: Token![for],
|
||||
pattern: Pattern,
|
||||
in_token: Token![in],
|
||||
iter: syn::Expr,
|
||||
expr: Value,
|
||||
}
|
||||
|
||||
impl Parse for ForIter {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(ForIter {
|
||||
for_token: input.parse()?,
|
||||
pattern: input.parse()?,
|
||||
in_token: input.parse()?,
|
||||
iter: input.parse()?,
|
||||
_ = input.parse::<Token![for]>()?;
|
||||
let expr = input.parse()?;
|
||||
|
||||
Ok(Self { expr })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ForIter {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let expr = &self.expr;
|
||||
|
||||
tokens.extend(quote! {
|
||||
for arg in #expr {
|
||||
command.arg(arg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct ForIn {
|
||||
pattern: syn::Pat,
|
||||
iter: Value,
|
||||
args: SingleMultiArg,
|
||||
}
|
||||
|
||||
impl Parse for ForIn {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
_ = input.parse::<Token![for]>()?;
|
||||
let pattern = input.call(syn::Pat::parse_single)?;
|
||||
_ = input.parse::<Token![in]>()?;
|
||||
let iter = input.parse()?;
|
||||
_ = input.parse::<Token![=>]>()?;
|
||||
let args = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
pattern,
|
||||
iter,
|
||||
args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ForIn {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let Self {
|
||||
pattern,
|
||||
iter,
|
||||
args,
|
||||
} = self;
|
||||
|
||||
tokens.extend(quote! {
|
||||
for #pattern in #iter {
|
||||
#args
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct IfLet {
|
||||
if_token: Token![if],
|
||||
let_token: Token![let],
|
||||
pattern: Pattern,
|
||||
eq_token: Token![=],
|
||||
expr: syn::Expr,
|
||||
pattern: syn::Pat,
|
||||
expr: Value,
|
||||
args: SingleMultiArg,
|
||||
}
|
||||
|
||||
impl Parse for IfLet {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(IfLet {
|
||||
if_token: input.parse()?,
|
||||
let_token: input.parse()?,
|
||||
pattern: input.parse()?,
|
||||
eq_token: input.parse()?,
|
||||
expr: input.parse()?,
|
||||
_ = input.parse::<Token![if]>()?;
|
||||
_ = input.parse::<Token![let]>()?;
|
||||
let pattern = input.call(syn::Pat::parse_single)?;
|
||||
_ = input.parse::<Token![=]>()?;
|
||||
let expr = input.parse()?;
|
||||
_ = input.parse::<Token![=>]>()?;
|
||||
let args = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
pattern,
|
||||
expr,
|
||||
args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleArg(syn::Expr);
|
||||
impl ToTokens for IfLet {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let Self {
|
||||
pattern,
|
||||
expr,
|
||||
args,
|
||||
} = &self;
|
||||
|
||||
tokens.extend(quote! {
|
||||
if let #pattern = #expr {
|
||||
#args
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct If {
|
||||
expr: Value,
|
||||
args: SingleMultiArg,
|
||||
}
|
||||
|
||||
impl Parse for If {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
_ = input.parse::<Token![if]>()?;
|
||||
let expr = input.parse()?;
|
||||
_ = input.parse::<Token![=>]>()?;
|
||||
let args = input.parse()?;
|
||||
|
||||
Ok(Self { expr, args })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for If {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let Self { expr, args } = self;
|
||||
|
||||
tokens.extend(quote! {
|
||||
if #expr {
|
||||
#args
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum SingleMultiArg {
|
||||
Single(SingleArg),
|
||||
Multi(MultiArg),
|
||||
}
|
||||
|
||||
impl Parse for SingleMultiArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(token::Bracket) {
|
||||
input.parse().map(Self::Multi)
|
||||
} else {
|
||||
input.parse().map(Self::Single)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for SingleMultiArg {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
tokens.extend(match self {
|
||||
Self::Single(arg) => quote! { #arg },
|
||||
Self::Multi(args) => quote! { #args },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiArg(Punctuated<SingleArg, Token![,]>);
|
||||
|
||||
impl Parse for MultiArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let args;
|
||||
bracketed!(args in input);
|
||||
|
||||
Punctuated::parse_terminated(&args).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for MultiArg {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let args = self.0.iter().collect::<Vec<_>>();
|
||||
|
||||
tokens.extend(quote! { #(#args)* });
|
||||
}
|
||||
}
|
||||
|
||||
struct SingleArg(Value);
|
||||
|
||||
impl Parse for SingleArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
@@ -130,17 +269,45 @@ impl Parse for SingleArg {
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiArg {
|
||||
bracket: token::Bracket,
|
||||
args: Punctuated<SingleArg, Token![,]>,
|
||||
}
|
||||
|
||||
impl Parse for MultiArg {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let args;
|
||||
Ok(MultiArg {
|
||||
bracket: bracketed!(args in input),
|
||||
args: input.parse_terminated(SingleArg::parse, Token![,])?,
|
||||
})
|
||||
impl ToTokens for SingleArg {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let arg = &self.0;
|
||||
tokens.extend(quote! {
|
||||
command.arg(#arg);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enum Value {
|
||||
Lit(syn::Lit),
|
||||
Ident(syn::Ident),
|
||||
Expr(syn::Expr),
|
||||
}
|
||||
|
||||
impl Parse for Value {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
if input.peek(syn::Lit) {
|
||||
input.parse().map(Self::Lit)
|
||||
} else if input.peek(syn::Ident) {
|
||||
input.parse().map(Self::Ident)
|
||||
} else {
|
||||
input.parse().map(Self::Expr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Value {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
tokens.extend(match self {
|
||||
Self::Lit(lit) => quote! { #lit },
|
||||
Self::Ident(ident) => quote! { #ident },
|
||||
Self::Expr(expr) => quote! { #expr },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn cmd(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let command = parse_macro_input!(input as Command);
|
||||
quote! { #command }.into()
|
||||
}
|
||||
|
||||
79
tests/cmd.rs
Normal file
79
tests/cmd.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use comlexr::cmd;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn expression() {
|
||||
let command = cmd!("echo", "test");
|
||||
|
||||
assert_eq!(format!("{command:?}"), r#""echo" "test""#.to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(false, false, r#""echo" "test""#)]
|
||||
#[case(true, false, r#""echo" "test" "single""#)]
|
||||
#[case(false, true, r#""echo" "test" "multi" "arg""#)]
|
||||
#[case(true, true, r#""echo" "test" "single" "multi" "arg""#)]
|
||||
fn if_statement(#[case] single: bool, #[case] multi: bool, #[case] expected: &str) {
|
||||
let command = cmd!(
|
||||
"echo",
|
||||
"test",
|
||||
if single => "single",
|
||||
if multi => [
|
||||
"multi",
|
||||
"arg",
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(format!("{command:?}"), expected.to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&[], r#""echo" "test""#)]
|
||||
#[case(&["1", "2"], r#""echo" "test" "1" "2""#)]
|
||||
fn for_iter(#[case] iter: &[&str], #[case] expected: &str) {
|
||||
let command = cmd!(
|
||||
"echo",
|
||||
"test",
|
||||
for iter,
|
||||
);
|
||||
|
||||
assert_eq!(format!("{command:?}"), expected.to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(&[], &[], r#""echo" "test""#)]
|
||||
#[case(&["1", "2"], &[], r#""echo" "test" "1" "2""#)]
|
||||
#[case(&[], &["3", "4"], r#""echo" "test" "multi" "3" "multi" "4""#)]
|
||||
#[case(&["1", "2"], &["3", "4"], r#""echo" "test" "1" "2" "multi" "3" "multi" "4""#)]
|
||||
fn for_in(#[case] single_iter: &[&str], #[case] multi_iter: &[&str], #[case] expected: &str) {
|
||||
let command = cmd!(
|
||||
"echo",
|
||||
"test",
|
||||
for arg in single_iter => arg,
|
||||
for arg in multi_iter => [
|
||||
"multi",
|
||||
arg,
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(format!("{command:?}"), expected.to_string());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(None, None, r#""echo" "test""#)]
|
||||
#[case(Some("arg"), None, r#""echo" "test" "arg""#)]
|
||||
#[case(None, Some("arg"), r#""echo" "test" "multi" "arg""#)]
|
||||
#[case(Some("1"), Some("2"), r#""echo" "test" "1" "multi" "2""#)]
|
||||
fn if_let(#[case] single: Option<&str>, #[case] multi: Option<&str>, #[case] expected: &str) {
|
||||
let command = cmd!(
|
||||
"echo",
|
||||
"test",
|
||||
if let Some(arg) = single => arg,
|
||||
if let Some(arg) = multi => [
|
||||
"multi",
|
||||
arg,
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(format!("{command:?}"), expected.to_string());
|
||||
}
|
||||
Reference in New Issue
Block a user