mongodb_ext_derive/lib.rs
1//! This crate is the `proc_macro` library associated with the `mongodb_ext` crate.
2//!
3//! Since recent changes, this crate has an unfortunate name.
4//! "derive" is not quite correct, because this crate's purpose is to provide macros, not **derive** macros explicitly.
5//!
6//! This crate currently provides one macro: [`case!`].
7
8extern crate convert_case;
9extern crate proc_macro;
10extern crate proc_macro2;
11#[macro_use]
12extern crate syn;
13extern crate quote;
14
15use {
16 crate::{
17 convert_case::{Case, Casing},
18 proc_macro::TokenStream,
19 proc_macro2::Span,
20 quote::ToTokens,
21 syn::{
22 parse::{Error as SynError, Parse, ParseStream, Result as SynResult},
23 spanned::Spanned,
24 token::FatArrow,
25 LitStr, Path,
26 },
27 },
28 std::convert::From,
29};
30
31struct CaseInput(LitStr);
32impl Parse for CaseInput {
33 fn parse(input: ParseStream) -> SynResult<Self> {
34 // parse first path
35 let first_path: Path = input.parse::<Path>()?;
36 // get first path's span
37 let first_span: Span = first_path.span();
38 // convert first path to String
39 let first_string: String = if let Some(last_of_first) = first_path.segments.last() {
40 last_of_first.ident.to_string()
41 } else {
42 // throw error if there is no last element
43 return Err(SynError::new(first_span, "Cannot get last element of path"));
44 };
45
46 // parse `=>`
47 let _: FatArrow = input.parse::<FatArrow>()?;
48
49 // parse last (second) case
50 let last_path: Path = input.parse::<Path>()?;
51 // get last path's span
52 let last_span: Span = last_path.span();
53 // convert last path to String
54 let last_string: String = if let Some(last_of_last) = last_path.segments.last() {
55 last_of_last.ident.to_string()
56 } else {
57 // throw error if there is no last element
58 return Err(SynError::new(last_span, "Cannot get last element of path"));
59 };
60
61 // parse case as instance of `convert_case::Case`
62 let mut case: Option<Case> = None;
63 for c in Case::all_cases() {
64 if format!("{:?}", c).eq(&last_string) {
65 case = Some(c);
66 break;
67 }
68 }
69 if case.is_none() {
70 return Err(SynError::new(
71 last_span,
72 "Cannot parse case parameter as `Case`",
73 ));
74 }
75 let case = case.unwrap();
76
77 // change first path's case and return
78 let parsed_path: String = first_string.to_case(case);
79 Ok(Self(LitStr::new(&parsed_path, first_span)))
80 }
81}
82
83/// Small macro that converts the input path to a given case.
84///
85/// The general accepted format is: `case!(path::to::Type => Case)`
86///
87/// Hereby
88/// - `path::to::Type` can be any path. It does not need to exist.
89/// - `=>` is just a fat arrow that separates the two parameters.
90/// - `Case` is any path that points to any value of the [`convert_case`] crate's [`Case`] enum.
91///
92/// This macro always expands to a [`&str`] literal ([`LitStr`](struct@syn::LitStr)).
93///
94/// # Examples
95///
96/// The identifier given does not need to be an existing type:
97///
98/// ```rust
99/// use mongodb_ext_derive::case;
100///
101/// // `MyImaginaryType` is not imported, but works anyways
102/// // `Case` is not imported, but works anyways
103/// assert_eq!(
104/// case!(MyImaginaryType => Case::Camel),
105/// "myImaginaryType"
106/// );
107/// ```
108///
109/// If a path is given, only the last element will be parsed:
110///
111/// ```rust
112/// use mongodb_ext_derive::case;
113///
114/// assert_eq!(
115/// case!(std::collection::HashMap => Snake),
116/// "hash_map"
117/// );
118/// ```
119///
120/// ```rust
121/// use mongodb_ext_derive::case;
122///
123/// assert_eq!(
124/// case!(std::this::r#type::does::not::exist::THIS_CONSTANT_DOES_NOT_EXIST => Pascal),
125/// "ThisConstantDoesNotExist"
126/// );
127///
128/// assert_eq!(
129/// case!(std::this::r#type::does::not::exist::this_function_does_not_exist => Title),
130/// "This Function Does Not Exist"
131/// );
132///
133/// assert_eq!(
134/// case!(std::this::r#type::does::not::exist::ThisTypeDoesNotExist => Camel),
135/// "thisTypeDoesNotExist"
136/// );
137/// ```
138#[proc_macro]
139pub fn case(input: TokenStream) -> TokenStream {
140 parse_macro_input!(input as CaseInput)
141 .0
142 .to_token_stream()
143 .into()
144}