Skip to content

Commit 717663e

Browse files
committed
Expose methods as camelCase and add attributes for controlling renaming
Add an attribute to methods to control the name they're exported under and an attribute to php_impl to override automatic case conversion conventions.
1 parent 418265a commit 717663e

File tree

3 files changed

+117
-66
lines changed

3 files changed

+117
-66
lines changed

ext-php-rs-derive/src/impl_.rs

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{anyhow, bail, Result};
44
use darling::{FromMeta, ToTokens};
55
use proc_macro2::TokenStream;
66
use quote::quote;
7-
use syn::{Attribute, ItemImpl, Lit, Meta, NestedMeta};
7+
use syn::{Attribute, AttributeArgs, ItemImpl, Lit, Meta, NestedMeta};
88

99
use crate::{constant::Constant, method};
1010

@@ -15,14 +15,64 @@ pub enum Visibility {
1515
Private,
1616
}
1717

18+
#[derive(Debug, Copy, Clone, FromMeta)]
19+
pub enum RenameRule {
20+
#[darling(rename = "camelCase")]
21+
Camel,
22+
#[darling(rename = "snake_case")]
23+
Snake,
24+
}
25+
26+
impl RenameRule {
27+
/// Change case of an identifier.
28+
///
29+
/// Magic methods are handled specially to make sure they're always cased
30+
/// correctly.
31+
pub fn rename(&self, name: impl AsRef<str>) -> String {
32+
match name.as_ref() {
33+
"__construct" => "__construct".to_string(),
34+
"__destruct" => "__destruct".to_string(),
35+
"__call" => "__call".to_string(),
36+
"__call_static" => "__callStatic".to_string(),
37+
"__get" => "__get".to_string(),
38+
"__set" => "__set".to_string(),
39+
"__isset" => "__isset".to_string(),
40+
"__unset" => "__unset".to_string(),
41+
"__sleep" => "__sleep".to_string(),
42+
"__wakeup" => "__wakeup".to_string(),
43+
"__serialize" => "__serialize".to_string(),
44+
"__unserialize" => "__unserialize".to_string(),
45+
"__to_string" => "__toString".to_string(),
46+
"__invoke" => "__invoke".to_string(),
47+
"__set_state" => "__set_state".to_string(),
48+
"__clone" => "__clone".to_string(),
49+
"__debug_info" => "__debugInfo".to_string(),
50+
field => match self {
51+
Self::Camel => ident_case::RenameRule::CamelCase.apply_to_field(field),
52+
Self::Snake => ident_case::RenameRule::SnakeCase.apply_to_field(field),
53+
},
54+
}
55+
}
56+
}
57+
1858
#[derive(Debug)]
1959
pub enum ParsedAttribute {
2060
Default(HashMap<String, Lit>),
2161
Optional(String),
2262
Visibility(Visibility),
63+
Rename(String),
2364
}
2465

25-
pub fn parser(input: ItemImpl) -> Result<TokenStream> {
66+
#[derive(Default, Debug, FromMeta)]
67+
#[darling(default)]
68+
pub struct AttrArgs {
69+
rename_methods: Option<RenameRule>,
70+
}
71+
72+
pub fn parser(args: AttributeArgs, input: ItemImpl) -> Result<TokenStream> {
73+
let args = AttrArgs::from_list(&args)
74+
.map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))?;
75+
2676
let ItemImpl { self_ty, items, .. } = input;
2777
let class_name = self_ty.to_token_stream().to_string();
2878

@@ -61,7 +111,7 @@ pub fn parser(input: ItemImpl) -> Result<TokenStream> {
61111
}
62112
}
63113
syn::ImplItem::Method(mut method) => {
64-
let (sig, method) = method::parser(&mut method)?;
114+
let (sig, method) = method::parser(&mut method, args.rename_methods.unwrap_or(RenameRule::Camel))?;
65115
class.methods.push(method);
66116
sig
67117
}
@@ -107,6 +157,61 @@ pub fn parse_attribute(attr: &Attribute) -> Result<ParsedAttribute> {
107157
"public" => ParsedAttribute::Visibility(Visibility::Public),
108158
"protected" => ParsedAttribute::Visibility(Visibility::Protected),
109159
"private" => ParsedAttribute::Visibility(Visibility::Private),
160+
"rename" => {
161+
let ident = if let Meta::List(list) = meta {
162+
if let Some(NestedMeta::Lit(lit)) = list.nested.first() {
163+
String::from_value(lit).ok()
164+
} else {
165+
None
166+
}
167+
} else {
168+
None
169+
}
170+
.ok_or_else(|| anyhow!("Invalid argument given for `#[rename] macro."))?;
171+
172+
ParsedAttribute::Rename(ident)
173+
}
110174
attr => bail!("Invalid attribute `#[{}]`.", attr),
111175
})
112176
}
177+
178+
#[cfg(test)]
179+
mod tests {
180+
use super::RenameRule;
181+
182+
#[test]
183+
fn test_rename_magic() {
184+
for &(magic, expected) in &[
185+
("__construct", "__construct"),
186+
("__destruct", "__destruct"),
187+
("__call", "__call"),
188+
("__call_static", "__callStatic"),
189+
("__get", "__get"),
190+
("__set", "__set"),
191+
("__isset", "__isset"),
192+
("__unset", "__unset"),
193+
("__sleep", "__sleep"),
194+
("__wakeup", "__wakeup"),
195+
("__serialize", "__serialize"),
196+
("__unserialize", "__unserialize"),
197+
("__to_string", "__toString"),
198+
("__invoke", "__invoke"),
199+
("__set_state", "__set_state"),
200+
("__clone", "__clone"),
201+
("__debug_info", "__debugInfo"),
202+
] {
203+
assert_eq!(expected, RenameRule::Camel.rename(magic));
204+
assert_eq!(expected, RenameRule::Snake.rename(magic));
205+
}
206+
}
207+
208+
#[test]
209+
fn test_rename_php_methods() {
210+
for &(original, camel, snake) in &[
211+
("get_name", "getName", "get_name"),
212+
] {
213+
assert_eq!(camel, RenameRule::Camel.rename(original));
214+
assert_eq!(snake, RenameRule::Snake.rename(original));
215+
}
216+
}
217+
}

ext-php-rs-derive/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ pub fn php_startup(_: TokenStream, input: TokenStream) -> TokenStream {
9393
}
9494

9595
#[proc_macro_attribute]
96-
pub fn php_impl(_: TokenStream, input: TokenStream) -> TokenStream {
96+
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
97+
let args = parse_macro_input!(args as AttributeArgs);
9798
let input = parse_macro_input!(input as ItemImpl);
9899

99-
match impl_::parser(input) {
100+
match impl_::parser(args, input) {
100101
Ok(parsed) => parsed,
101102
Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(),
102103
}

ext-php-rs-derive/src/method.rs

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
use anyhow::{anyhow, bail, Result};
2-
use ident_case::RenameRule;
32
use quote::ToTokens;
43
use std::collections::HashMap;
54

6-
use crate::{
7-
function,
8-
impl_::{parse_attribute, ParsedAttribute, Visibility},
9-
};
5+
use crate::{function, impl_::{ParsedAttribute, RenameRule, Visibility, parse_attribute}};
106
use proc_macro2::{Ident, Span, TokenStream};
117
use quote::quote;
128
use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Signature, Token, Type};
@@ -35,39 +31,18 @@ pub struct Method {
3531
pub visibility: Visibility,
3632
}
3733

38-
fn camel_case_identifier(field: &str) -> String {
39-
match field {
40-
"__construct" => "__construct".to_string(),
41-
"__destruct" => "__destruct".to_string(),
42-
"__call" => "__call".to_string(),
43-
"__call_static" => "__callStatic".to_string(),
44-
"__get" => "__get".to_string(),
45-
"__set" => "__set".to_string(),
46-
"__isset" => "__isset".to_string(),
47-
"__unset" => "__unset".to_string(),
48-
"__sleep" => "__sleep".to_string(),
49-
"__wakeup" => "__wakeup".to_string(),
50-
"__serialize" => "__serialize".to_string(),
51-
"__unserialize" => "__unserialize".to_string(),
52-
"__to_string" => "__toString".to_string(),
53-
"__invoke" => "__invoke".to_string(),
54-
"__set_state" => "__set_state".to_string(),
55-
"__clone" => "__clone".to_string(),
56-
"__debug_info" => "__debugInfo".to_string(),
57-
field => RenameRule::CamelCase.apply_to_field(field),
58-
}
59-
}
60-
61-
pub fn parser(input: &mut ImplItemMethod) -> Result<(TokenStream, Method)> {
34+
pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result<(TokenStream, Method)> {
6235
let mut defaults = HashMap::new();
6336
let mut optional = None;
6437
let mut visibility = Visibility::Public;
38+
let mut identifier = None;
6539

6640
for attr in input.attrs.iter() {
6741
match parse_attribute(attr)? {
6842
ParsedAttribute::Default(list) => defaults = list,
6943
ParsedAttribute::Optional(name) => optional = Some(name),
7044
ParsedAttribute::Visibility(vis) => visibility = vis,
45+
ParsedAttribute::Rename(ident) => identifier = Some(ident),
7146
}
7247
}
7348

@@ -116,8 +91,9 @@ pub fn parser(input: &mut ImplItemMethod) -> Result<(TokenStream, Method)> {
11691
}
11792
};
11893

94+
let name = identifier.unwrap_or_else(|| rename_rule.rename(ident.to_string()));
11995
let method = Method {
120-
name: camel_case_identifier(&ident.to_string()),
96+
name,
12197
ident: internal_ident.to_string(),
12298
args,
12399
optional,
@@ -271,34 +247,3 @@ impl Method {
271247
.to_token_stream()
272248
}
273249
}
274-
275-
#[cfg(test)]
276-
mod tests {
277-
use super::camel_case_identifier;
278-
279-
#[test]
280-
fn test_rename_php_methods() {
281-
for &(original, expected) in &[
282-
("__construct", "__construct"),
283-
("__destruct", "__destruct"),
284-
("__call", "__call"),
285-
("__call_static", "__callStatic"),
286-
("__get", "__get"),
287-
("__set", "__set"),
288-
("__isset", "__isset"),
289-
("__unset", "__unset"),
290-
("__sleep", "__sleep"),
291-
("__wakeup", "__wakeup"),
292-
("__serialize", "__serialize"),
293-
("__unserialize", "__unserialize"),
294-
("__to_string", "__toString"),
295-
("__invoke", "__invoke"),
296-
("__set_state", "__set_state"),
297-
("__clone", "__clone"),
298-
("__debug_info", "__debugInfo"),
299-
("get_name", "getName"),
300-
] {
301-
assert_eq!(camel_case_identifier(original), expected);
302-
}
303-
}
304-
}

0 commit comments

Comments
 (0)