1use proc_macro2::TokenStream;
2use quote::ToTokens;
3use 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
11pub 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)]
42pub 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)]
50pub struct LitStrValue<T>(pub T);
51
52impl<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
59impl<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)]
67pub struct NameLitStr(pub Ident);
68
69impl 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
80impl 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)]
88pub 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)]
101pub struct RenamingRuleLitStr {
102 pub lit: LitStr,
103 pub rule: RenamingRule,
104}
105
106impl 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
129impl 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)]
137pub enum TextSignatureAttributeValue {
138 Str(LitStr),
139 // `None` ident to disable automatic text signature generation
140 Disabled(Ident),
141}
142
143impl 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
161impl 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
170pub type ExtendsAttribute = KeywordAttribute<kw::extends, Path>;
171pub type FreelistAttribute = KeywordAttribute<kw::freelist, Box<Expr>>;
172pub type ModuleAttribute = KeywordAttribute<kw::module, LitStr>;
173pub type NameAttribute = KeywordAttribute<kw::name, NameLitStr>;
174pub type RenameAllAttribute = KeywordAttribute<kw::rename_all, RenamingRuleLitStr>;
175pub type TextSignatureAttribute = KeywordAttribute<kw::text_signature, TextSignatureAttributeValue>;
176
177impl<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
186impl<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
194pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>;
195
196/// For specifying the path to the pyo3 crate.
197pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
198
199pub 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.)
214pub 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
229pub 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