1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
extern crate proc_macro;

use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
#[macro_use]
extern crate quote;
use std::iter;

fn pascal_case(dat: &str) -> String {
    let mut ret = String::new();
    let mut uppercase = true;
    for c in dat.chars() {
        if c == ' ' {
            uppercase = true;
            continue;
        }
        if !c.is_ascii_alphabetic() {
            continue;
        }
        if uppercase {
            ret.push(c.to_ascii_uppercase());
        } else {
            ret.push(c.to_ascii_lowercase());
        }
        uppercase = false;
    }
    ret
}

fn extend<T: Into<TokenTree>>(ts: &mut TokenStream, tt: T) {
    ts.extend(iter::once(tt.into()));
}

#[proc_macro]
pub fn status_codes(toks: TokenStream) -> TokenStream {
    let mut ret = TokenStream::new();
    let mut enum_stream = TokenStream::new();
    let mut is_string = false;
    let mut prev_num: i32 = -1;
    let mut entries: Vec<(i32, String, String)> = Vec::new();
    for tt in toks {
        match tt {
            TokenTree::Literal(lit) => {
                let repr = lit.to_string();
                if !is_string {
                    let val: i32 = repr
                        .parse()
                        .expect("Expected number but did not receive one.");
                    prev_num = val;
                } else {
                    let name: &str = &repr[1..repr.len() - 1];
                    let cased = pascal_case(name);
                    entries.push((prev_num, name.to_string(), cased.clone()));
                    extend(&mut enum_stream, Ident::new(&cased, Span::call_site()));
                    extend(&mut enum_stream, Punct::new(',', Spacing::Alone));
                }
                is_string = !is_string;
            }
            _ => continue,
        }
    }
    enum_stream.extend::<TokenStream>(
        quote! {
            Other(i32, &'static str),
        }
        .into(),
    );
    let enum_group = Group::new(Delimiter::Brace, enum_stream);
    let mut enum_tot = TokenStream::new();
    enum_tot.extend::<TokenStream>(
        quote! {
            #[derive(Debug, Clone, Copy, PartialEq, Eq)]
            pub enum StatusCode
        }
        .into(),
    );
    extend(&mut enum_tot, enum_group);
    ret.extend(enum_tot);
    let mut match_stream = TokenStream::new();
    for (code, name, cased) in entries {
        match_stream.extend::<TokenStream>(
            quote! {
                StatusCode::
            }
            .into(),
        );
        extend(&mut match_stream, Ident::new(&cased, Span::call_site()));
        extend(&mut match_stream, Punct::new('=', Spacing::Joint));
        extend(&mut match_stream, Punct::new('>', Spacing::Alone));
        match_stream.extend::<TokenStream>(
            quote! {
                (#code, #name),
            }
            .into(),
        );
    }
    match_stream.extend::<TokenStream>(
        quote! {
            StatusCode::Other(n, s) => (*n, s)
        }
        .into(),
    );
    let mut function_body = TokenStream::new();
    function_body.extend::<TokenStream>(
        quote! {
            match self
        }
        .into(),
    );
    extend(
        &mut function_body,
        Group::new(Delimiter::Brace, match_stream),
    );
    let mut impl_body = TokenStream::new();
    impl_body.extend::<TokenStream>(
        quote! {
            pub fn fetch(&self) -> (i32, &'static str)
        }
        .into(),
    );
    extend(&mut impl_body, Group::new(Delimiter::Brace, function_body));
    let mut full_impl = TokenStream::new();
    full_impl.extend::<TokenStream>(
        quote! {
            impl StatusCode
        }
        .into(),
    );
    extend(&mut full_impl, Group::new(Delimiter::Brace, impl_body));
    ret.extend(full_impl);
    ret
}