1 | use proc_macro2::TokenStream; |
2 | use quote::ToTokens; |
3 | use syn::{ |
4 | parse::{Parse, ParseStream}, |
5 | punctuated::Punctuated, |
6 | spanned::Spanned, |
7 | token::Comma, |
8 | Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token, |
9 | }; |
10 | |
11 | pub mod kw { |
12 | syn::custom_keyword!(args); |
13 | syn::custom_keyword!(annotation); |
14 | syn::custom_keyword!(attribute); |
15 | syn::custom_keyword!(dict); |
16 | syn::custom_keyword!(extends); |
17 | syn::custom_keyword!(freelist); |
18 | syn::custom_keyword!(from_py_with); |
19 | syn::custom_keyword!(frozen); |
20 | syn::custom_keyword!(gc); |
21 | syn::custom_keyword!(get); |
22 | syn::custom_keyword!(get_all); |
23 | syn::custom_keyword!(item); |
24 | syn::custom_keyword!(from_item_all); |
25 | syn::custom_keyword!(mapping); |
26 | syn::custom_keyword!(module); |
27 | syn::custom_keyword!(name); |
28 | syn::custom_keyword!(pass_module); |
29 | syn::custom_keyword!(rename_all); |
30 | syn::custom_keyword!(sequence); |
31 | syn::custom_keyword!(set); |
32 | syn::custom_keyword!(set_all); |
33 | syn::custom_keyword!(signature); |
34 | syn::custom_keyword!(subclass); |
35 | syn::custom_keyword!(text_signature); |
36 | syn::custom_keyword!(transparent); |
37 | syn::custom_keyword!(unsendable); |
38 | syn::custom_keyword!(weakref); |
39 | } |
40 | |
41 | #[derive (Clone, Debug)] |
42 | pub struct KeywordAttribute<K, V> { |
43 | pub kw: K, |
44 | pub value: V, |
45 | } |
46 | |
47 | /// A helper type which parses the inner type via a literal string |
48 | /// e.g. `LitStrValue<Path>` -> parses "some::path" in quotes. |
49 | #[derive (Clone, Debug, PartialEq, Eq)] |
50 | pub struct LitStrValue<T>(pub T); |
51 | |
52 | impl<T: Parse> Parse for LitStrValue<T> { |
53 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
54 | let lit_str: LitStr = input.parse()?; |
55 | lit_str.parse().map(op:LitStrValue) |
56 | } |
57 | } |
58 | |
59 | impl<T: ToTokens> ToTokens for LitStrValue<T> { |
60 | fn to_tokens(&self, tokens: &mut TokenStream) { |
61 | self.0.to_tokens(tokens) |
62 | } |
63 | } |
64 | |
65 | /// A helper type which parses a name via a literal string |
66 | #[derive (Clone, Debug, PartialEq, Eq)] |
67 | pub struct NameLitStr(pub Ident); |
68 | |
69 | impl Parse for NameLitStr { |
70 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
71 | let string_literal: LitStr = input.parse()?; |
72 | if let Ok(ident: Ident) = string_literal.parse() { |
73 | Ok(NameLitStr(ident)) |
74 | } else { |
75 | bail_spanned!(string_literal.span() => "expected a single identifier in double quotes" ) |
76 | } |
77 | } |
78 | } |
79 | |
80 | impl ToTokens for NameLitStr { |
81 | fn to_tokens(&self, tokens: &mut TokenStream) { |
82 | self.0.to_tokens(tokens) |
83 | } |
84 | } |
85 | |
86 | /// Available renaming rules |
87 | #[derive (Clone, Copy, Debug, PartialEq, Eq)] |
88 | pub enum RenamingRule { |
89 | CamelCase, |
90 | KebabCase, |
91 | Lowercase, |
92 | PascalCase, |
93 | ScreamingKebabCase, |
94 | ScreamingSnakeCase, |
95 | SnakeCase, |
96 | Uppercase, |
97 | } |
98 | |
99 | /// A helper type which parses a renaming rule via a literal string |
100 | #[derive (Clone, Debug, PartialEq, Eq)] |
101 | pub struct RenamingRuleLitStr { |
102 | pub lit: LitStr, |
103 | pub rule: RenamingRule, |
104 | } |
105 | |
106 | impl Parse for RenamingRuleLitStr { |
107 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
108 | let string_literal: LitStr = input.parse()?; |
109 | let rule: RenamingRule = match string_literal.value().as_ref() { |
110 | "camelCase" => RenamingRule::CamelCase, |
111 | "kebab-case" => RenamingRule::KebabCase, |
112 | "lowercase" => RenamingRule::Lowercase, |
113 | "PascalCase" => RenamingRule::PascalCase, |
114 | "SCREAMING-KEBAB-CASE" => RenamingRule::ScreamingKebabCase, |
115 | "SCREAMING_SNAKE_CASE" => RenamingRule::ScreamingSnakeCase, |
116 | "snake_case" => RenamingRule::SnakeCase, |
117 | "UPPERCASE" => RenamingRule::Uppercase, |
118 | _ => { |
119 | bail_spanned!(string_literal.span() => "expected a valid renaming rule, possible values are: \"camelCase \", \"kebab-case \", \"lowercase \", \"PascalCase \", \"SCREAMING-KEBAB-CASE \", \"SCREAMING_SNAKE_CASE \", \"snake_case \", \"UPPERCASE \"" ) |
120 | } |
121 | }; |
122 | Ok(Self { |
123 | lit: string_literal, |
124 | rule, |
125 | }) |
126 | } |
127 | } |
128 | |
129 | impl ToTokens for RenamingRuleLitStr { |
130 | fn to_tokens(&self, tokens: &mut TokenStream) { |
131 | self.lit.to_tokens(tokens) |
132 | } |
133 | } |
134 | |
135 | /// Text signatue can be either a literal string or opt-in/out |
136 | #[derive (Clone, Debug, PartialEq, Eq)] |
137 | pub enum TextSignatureAttributeValue { |
138 | Str(LitStr), |
139 | // `None` ident to disable automatic text signature generation |
140 | Disabled(Ident), |
141 | } |
142 | |
143 | impl Parse for TextSignatureAttributeValue { |
144 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
145 | if let Ok(lit_str: LitStr) = input.parse::<LitStr>() { |
146 | return Ok(TextSignatureAttributeValue::Str(lit_str)); |
147 | } |
148 | |
149 | let err_span: Span = match input.parse::<Ident>() { |
150 | Ok(ident: Ident) if ident == "None" => { |
151 | return Ok(TextSignatureAttributeValue::Disabled(ident)); |
152 | } |
153 | Ok(other_ident: Ident) => other_ident.span(), |
154 | Err(e: Error) => e.span(), |
155 | }; |
156 | |
157 | Err(err_spanned!(err_span => "expected a string literal or `None`" )) |
158 | } |
159 | } |
160 | |
161 | impl ToTokens for TextSignatureAttributeValue { |
162 | fn to_tokens(&self, tokens: &mut TokenStream) { |
163 | match self { |
164 | TextSignatureAttributeValue::Str(s: &LitStr) => s.to_tokens(tokens), |
165 | TextSignatureAttributeValue::Disabled(b: &Ident) => b.to_tokens(tokens), |
166 | } |
167 | } |
168 | } |
169 | |
170 | pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>; |
171 | pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>; |
172 | pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>; |
173 | pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>; |
174 | pub type RenameAllAttribute = KeywordAttribute<kw::rename_all, RenamingRuleLitStr>; |
175 | pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, TextSignatureAttributeValue>; |
176 | |
177 | impl<K: Parse + std::fmt::Debug, V: Parse> Parse for KeywordAttribute<K, V> { |
178 | fn parse(input: ParseStream<'_>) -> Result<Self> { |
179 | let kw: K = input.parse()?; |
180 | let _: Token![=] = input.parse()?; |
181 | let value: V = input.parse()?; |
182 | Ok(KeywordAttribute { kw, value }) |
183 | } |
184 | } |
185 | |
186 | impl<K: ToTokens, V: ToTokens> ToTokens for KeywordAttribute<K, V> { |
187 | fn to_tokens(&self, tokens: &mut TokenStream) { |
188 | self.kw.to_tokens(tokens); |
189 | Token![=](self.kw.span()).to_tokens(tokens); |
190 | self.value.to_tokens(tokens); |
191 | } |
192 | } |
193 | |
194 | pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>; |
195 | |
196 | /// For specifying the path to the pyo3 crate. |
197 | pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>; |
198 | |
199 | pub fn get_pyo3_options<T: Parse>(attr: &syn::Attribute) -> Result<Option<Punctuated<T, Comma>>> { |
200 | if attr.path().is_ident("pyo3" ) { |
201 | attr.parse_args_with(Punctuated::parse_terminated).map(op:Some) |
202 | } else { |
203 | Ok(None) |
204 | } |
205 | } |
206 | |
207 | /// Takes attributes from an attribute vector. |
208 | /// |
209 | /// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then |
210 | /// the attribute will be removed from the vector. |
211 | /// |
212 | /// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed. |
213 | /// (In `retain`, returning `true` keeps the element, here it removes it.) |
214 | pub fn take_attributes( |
215 | attrs: &mut Vec<Attribute>, |
216 | mut extractor: impl FnMut(&Attribute) -> Result<bool>, |
217 | ) -> Result<()> { |
218 | *attrs = attrsimpl Iterator- >
|
219 | .drain(..) |
220 | .filter_map(|attr: Attribute| { |
221 | extractorResult(&attr) |
222 | .map(op:move |attribute_handled: bool| if attribute_handled { None } else { Some(attr) }) |
223 | .transpose() |
224 | }) |
225 | .collect::<Result<_>>()?; |
226 | Ok(()) |
227 | } |
228 | |
229 | pub fn take_pyo3_options<T: Parse>(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<T>> { |
230 | let mut out: Vec = Vec::new(); |
231 | take_attributes(attrs, |attr: &Attribute| { |
232 | if let Some(options: Punctuated) = get_pyo3_options(attr)? { |
233 | out.extend(iter:options); |
234 | Ok(true) |
235 | } else { |
236 | Ok(false) |
237 | } |
238 | })?; |
239 | Ok(out) |
240 | } |
241 | |