extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, Data, DeriveInput, Fields, Ident, Lit, Meta};
pub fn impl_qller(ast: &DeriveInput) -> TokenStream {
let base_url = extract_base_url(ast);
let name = &ast.ident;
let qoute_fields = sort_fielnds(get_fields(ast)).into_iter().map(|(t, v)| {
quote! {
params.push((#t, self.#v.to_string()));
}
});
let gen = quote! {
impl MakeLink for #name {
fn url_generate(&self) -> url::Url {
let mut params = Vec::new();
#(#qoute_fields)*
url::Url::parse_with_params(#base_url, ¶ms).expect("Failed to parse URL with params")
}
}
};
gen.into()
}
fn extract_base_url(ast: &DeriveInput) -> String {
ast.attrs
.iter()
.find_map(|attr| {
if attr.path.is_ident("url") {
if let Ok(Meta::NameValue(meta)) = attr.parse_meta() {
if let syn::Lit::Str(lit) = meta.lit {
return Some(lit.value());
}
}
}
None
})
.expect("No URL attribute found")
}
fn get_fields(ast: &DeriveInput) -> Vec<((usize, Option<usize>), String, Ident)> {
let collected = match &ast.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => {
fields
.named
.iter()
.enumerate()
.map(|(position, item)| {
let ident = item.ident.as_ref().expect("Parsing ident not found");
let mut ident_name = ident.to_string();
let mut position_perspective: (usize, Option<usize>) = (0, None);
item.attrs.iter().for_each(|x| {
if let Ok(Meta::NameValue(meta)) = x.parse_meta() {
if meta.path.is_ident("name") {
if let Lit::Str(lit) = &meta.lit {
let lit = lit.value();
if !lit.is_empty() && ident_name != lit {
ident_name = lit
}
}
}
if meta.path.is_ident("pos") {
if let Lit::Int(lit) = &meta.lit {
let lit = lit.base10_parse::<usize>().ok();
position_perspective = (position, lit)
}
}
}
});
((position_perspective), ident_name, ident.clone())
})
.collect::<Vec<((usize, Option<usize>), String, Ident)>>()
}
_ => panic!("Only for named data"),
},
_ => panic!("It can be use for structures"),
};
collected
}
fn sort_fielnds(fields: Vec<((usize, Option<usize>), String, Ident)>) -> Vec<(String, Ident)> {
let length = fields.len();
let mut result = vec![None; length];
let (mut with_custom_pos, without_custom_pos): (Vec<_>, Vec<_>) = fields
.into_iter()
.partition(|((_, custom_pos), _, _)| custom_pos.is_some());
with_custom_pos.sort_by_key(|((_, custom_pos), _, _)| custom_pos.unwrap());
for ((_, custom_pos), name, ident) in with_custom_pos {
if let Some(pos) = custom_pos {
let mut current_pos = pos;
while current_pos < length && result[current_pos].is_some() {
current_pos += 1;
}
if current_pos < length {
result[current_pos] = Some((name, ident));
}
}
}
for ((original_pos, _), name, ident) in without_custom_pos {
let mut current_pos = original_pos;
while current_pos < length && result[current_pos].is_some() {
current_pos += 1;
}
if current_pos < length {
result[current_pos] = Some((name, ident));
}
}
result.into_iter().flatten().collect()
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::Span;
use syn::Ident;
#[test]
fn test_sort_elements() {
let elements = vec![
(
(0, None),
"a".to_string(),
Ident::new("a", Span::call_site()),
),
(
(1, None),
"b".to_string(),
Ident::new("b", Span::call_site()),
),
(
(2, None),
"c".to_string(),
Ident::new("c", Span::call_site()),
),
(
(3, Some(2)),
"d".to_string(),
Ident::new("d", Span::call_site()),
),
(
(4, None),
"e".to_string(),
Ident::new("e", Span::call_site()),
),
(
(5, Some(4)),
"f".to_string(),
Ident::new("f", Span::call_site()),
),
];
let sorted_elements = sort_fielnds(elements);
let expected = vec![
("a".to_string(), Ident::new("a", Span::call_site())),
("b".to_string(), Ident::new("b", Span::call_site())),
("d".to_string(), Ident::new("d", Span::call_site())),
("c".to_string(), Ident::new("c", Span::call_site())),
("f".to_string(), Ident::new("f", Span::call_site())),
("e".to_string(), Ident::new("e", Span::call_site())),
];
assert_eq!(sorted_elements, expected);
}
}