git_ref_format_macro/
lib.rs

1// Copyright © 2021 The Radicle Link Contributors
2//
3// This file is part of radicle-link, distributed under the GPLv3 with Radicle
4// Linking Exception. For full terms see the included LICENSE file.
5
6#[macro_use]
7extern crate proc_macro_error2;
8
9use std::convert::TryInto;
10
11use proc_macro::TokenStream;
12use proc_macro_error2::abort;
13use quote::quote;
14use syn::{parse_macro_input, LitStr};
15
16use git_ref_format_core::{refspec::PatternStr, Component, Error, Qualified, RefStr};
17
18/// Create a [`git_ref_format_core::RefString`] from a string literal.
19///
20/// The string is validated at compile time, and an unsafe conversion is
21/// emitted.
22#[proc_macro_error]
23#[proc_macro]
24pub fn refname(input: TokenStream) -> TokenStream {
25    let lit = parse_macro_input!(input as LitStr);
26    let val = lit.value();
27
28    let parsed: Result<&RefStr, Error> = val.as_str().try_into();
29    match parsed {
30        Ok(safe) => {
31            let safe: &str = safe.as_str();
32            let expand = quote! {
33                unsafe {
34                    use ::std::mem::transmute;
35                    use ::radicle_git_ext::ref_format::RefString;
36
37                    transmute::<_, RefString>(#safe.to_owned())
38                }
39            };
40            TokenStream::from(expand)
41        }
42
43        Err(e) => {
44            abort!(lit.span(), "invalid refname literal: {}", e);
45        }
46    }
47}
48
49/// Create a [`git_ref_format_core::Qualified`] from a string literal.
50///
51/// The string is validated at compile time, and an unsafe conversion is
52/// emitted.
53#[proc_macro_error]
54#[proc_macro]
55pub fn qualified(input: TokenStream) -> TokenStream {
56    let lit = parse_macro_input!(input as LitStr);
57    let val = lit.value();
58
59    let parsed: Result<&RefStr, Error> = val.as_str().try_into();
60    match parsed {
61        Ok(name) => {
62            let qualified: Option<Qualified> = Qualified::from_refstr(name);
63            match qualified {
64                Some(safe) => {
65                    let safe: &str = safe.as_str();
66                    let expand = quote! {
67                        unsafe {
68                            use ::std::{borrow::Cow, mem::transmute};
69                            use ::radicle_git_ext::ref_format::{Component, RefStr, RefString, Qualified};
70
71                            let inner: RefString = transmute(#safe.to_owned());
72                            let cow: Cow<'static, RefStr> = Cow::Owned(inner);
73                            transmute::<_, Qualified>(cow)
74                        }
75                    };
76
77                    TokenStream::from(expand)
78                }
79
80                None => {
81                    abort!(
82                        lit.span(),
83                        "refname is not of the form 'refs/<category>/<name>'"
84                    );
85                }
86            }
87        }
88
89        Err(e) => {
90            abort!(lit.span(), "invalid refname literal: {}", e);
91        }
92    }
93}
94
95/// Create a [`git_ref_format_core::Component`] from a string literal.
96///
97/// The string is validated at compile time, and an unsafe conversion is
98/// emitted.
99#[proc_macro_error]
100#[proc_macro]
101pub fn component(input: TokenStream) -> TokenStream {
102    let lit = parse_macro_input!(input as LitStr);
103    let val = lit.value();
104
105    let name: Result<&RefStr, Error> = val.as_str().try_into();
106    match name {
107        Ok(name) => {
108            let comp: Option<Component> = name.into();
109            match comp {
110                Some(safe) => {
111                    let safe: &str = safe.as_ref().as_str();
112                    let expand = quote! {
113                        unsafe {
114                            use ::std::{borrow::Cow, mem::transmute};
115                            use ::radicle_git_ext::ref_format::{Component, RefStr, RefString};
116
117                            let inner: RefString = transmute(#safe.to_owned());
118                            let cow: Cow<'static, RefStr> = Cow::Owned(inner);
119                            transmute::<_, Component>(cow)
120                        }
121                    };
122
123                    TokenStream::from(expand)
124                }
125
126                None => {
127                    abort!(lit.span(), "component contains a '/'");
128                }
129            }
130        }
131
132        Err(e) => {
133            abort!(lit.span(), "invalid refname literal: {}", e);
134        }
135    }
136}
137
138/// Create a [`git_ref_format_core::refspec::PatternString`] from a string
139/// literal.
140///
141/// The string is validated at compile time, and an unsafe conversion is
142/// emitted.
143#[proc_macro_error]
144#[proc_macro]
145pub fn pattern(input: TokenStream) -> TokenStream {
146    let lit = parse_macro_input!(input as LitStr);
147    let val = lit.value();
148
149    let parsed: Result<&PatternStr, Error> = val.as_str().try_into();
150    match parsed {
151        Ok(safe) => {
152            let safe: &str = safe.as_str();
153            let expand = quote! {
154                unsafe {
155                    use ::std::mem::transmute;
156                    use ::radicle_git_ext::ref_format::refspec::PatternString;
157
158                    transmute::<_, PatternString>(#safe.to_owned())
159                }
160            };
161            TokenStream::from(expand)
162        }
163
164        Err(e) => {
165            abort!(lit.span(), "invalid refspec pattern literal: {}", e);
166        }
167    }
168}
169
170/// Create a [`git_ref_format_core::refspec::QualifiedPattern`] from a string
171/// literal.
172///
173/// The string is validated at compile time, and an unsafe conversion is
174/// emitted.
175#[proc_macro_error]
176#[proc_macro]
177pub fn qualified_pattern(input: TokenStream) -> TokenStream {
178    let lit = parse_macro_input!(input as LitStr);
179    let val = lit.value();
180
181    let parsed: Result<&PatternStr, Error> = val.as_str().try_into();
182    match parsed {
183        Ok(safe) => {
184            let safe: &str = safe.as_str();
185            let expand = quote! {
186                unsafe {
187                    use ::std::mem::transmute;
188                    use ::radicle_git_ext::ref_format::refspec::QualifiedPattern;
189
190                    transmute::<_, QualifiedPattern>(#safe.to_owned())
191                }
192            };
193            TokenStream::from(expand)
194        }
195
196        Err(e) => {
197            abort!(lit.span(), "invalid refspec pattern literal: {}", e);
198        }
199    }
200}