1use proc_macro::{TokenStream, TokenTree, Literal, Spacing, Punct};
12use syn::{LitStr, LitByteStr, parse_macro_input};
13use syn::parse::{self, Parse, ParseStream};
14use sha1::{Digest, Sha1};
15
16enum Input {
17 String(LitStr),
18 Bytes(LitByteStr)
19}
20
21impl Input {
22 pub fn to_bytes(&self) -> Vec<u8> {
23 match self {
24 Self::String(x) => x.value().into_bytes(),
25 Self::Bytes(x) => x.value()
26 }
27 }
28}
29
30impl Parse for Input {
31 fn parse(input: ParseStream) -> parse::Result<Self> {
32 if input.peek(LitStr) {
33 Ok(Input::String(input.parse()?))
34 } else if input.peek(LitByteStr) {
35 Ok(Input::Bytes(input.parse()?))
36 } else {
37 Err(input.error("expected a string or byte literal"))
38 }
39 }
40}
41
42#[proc_macro]
50pub fn sha1_hex(tokens: TokenStream) -> TokenStream {
51 sha1_impl(tokens, |hash| {
52 let hash = hex::encode(hash);
53 TokenTree::Literal(Literal::string(hash.as_ref())).into()
54 })
55}
56
57#[proc_macro]
65pub fn sha1_base64(tokens: TokenStream) -> TokenStream {
66 use base64::engine::general_purpose::STANDARD_NO_PAD;
67 use base64::Engine;
68
69 sha1_impl(tokens, |hash| {
70 let hash = STANDARD_NO_PAD.encode(hash);
71 TokenTree::Literal(Literal::string(hash.as_ref())).into()
72 })
73}
74
75#[proc_macro]
84pub fn sha1_bytes(tokens: TokenStream) -> TokenStream {
85 sha1_impl(tokens, |hash| {
86 TokenStream::from_iter([TokenTree::Punct(Punct::new('*', Spacing::Joint)), Literal::byte_string(hash).into()])
87 })
88}
89
90
91fn sha1_impl(tokens: TokenStream, f: impl FnOnce(&[u8]) -> TokenStream) -> TokenStream {
92 let input = parse_macro_input!(tokens as Input);
93 let bytes = input.to_bytes();
94
95 let mut hasher = Sha1::new();
96 hasher.update(bytes.as_slice());
97
98 let hash = hasher.finalize();
99 f(hash.as_ref())
100}