git-ref-format-macro 0.6.0

Macros for the git-ref-format crate
Documentation
#[macro_use]
extern crate proc_macro_error2;

use std::convert::TryInto;

use proc_macro::TokenStream;
use proc_macro_error2::abort;
use quote::quote;
use syn::{parse_macro_input, LitStr};

use git_ref_format_core::{refspec::PatternStr, Component, Error, Qualified, RefStr};

/// Create a [`git_ref_format_core::RefString`] from a string literal.
///
/// The string is validated at compile time, and an unsafe conversion is
/// emitted.
#[proc_macro_error]
#[proc_macro]
pub fn refname(input: TokenStream) -> TokenStream {
    let lit = parse_macro_input!(input as LitStr);
    let val = lit.value();

    let parsed: Result<&RefStr, Error> = val.as_str().try_into();
    match parsed {
        Ok(safe) => {
            let safe: &str = safe.as_str();
            let expand = quote! {
                unsafe {
                    use ::std::mem::transmute;
                    use ::radicle_git_ext::ref_format::RefString;

                    transmute::<_, RefString>(#safe.to_owned())
                }
            };
            TokenStream::from(expand)
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
        }
    }
}

/// Create a [`git_ref_format_core::Qualified`] from a string literal.
///
/// The string is validated at compile time, and an unsafe conversion is
/// emitted.
#[proc_macro_error]
#[proc_macro]
pub fn qualified(input: TokenStream) -> TokenStream {
    let lit = parse_macro_input!(input as LitStr);
    let val = lit.value();

    let parsed: Result<&RefStr, Error> = val.as_str().try_into();
    match parsed {
        Ok(name) => {
            let qualified: Option<Qualified> = Qualified::from_refstr(name);
            match qualified {
                Some(safe) => {
                    let safe: &str = safe.as_str();
                    let expand = quote! {
                        unsafe {
                            use ::std::{borrow::Cow, mem::transmute};
                            use ::radicle_git_ext::ref_format::{Component, RefStr, RefString, Qualified};

                            let inner: RefString = transmute(#safe.to_owned());
                            let cow: Cow<'static, RefStr> = Cow::Owned(inner);
                            transmute::<_, Qualified>(cow)
                        }
                    };

                    TokenStream::from(expand)
                }

                None => {
                    abort!(
                        lit.span(),
                        "refname is not of the form 'refs/<category>/<name>'"
                    );
                }
            }
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
        }
    }
}

/// Create a [`git_ref_format_core::Component`] from a string literal.
///
/// The string is validated at compile time, and an unsafe conversion is
/// emitted.
#[proc_macro_error]
#[proc_macro]
pub fn component(input: TokenStream) -> TokenStream {
    let lit = parse_macro_input!(input as LitStr);
    let val = lit.value();

    let name: Result<&RefStr, Error> = val.as_str().try_into();
    match name {
        Ok(name) => {
            let comp: Option<Component> = name.into();
            match comp {
                Some(safe) => {
                    let safe: &str = safe.as_ref().as_str();
                    let expand = quote! {
                        unsafe {
                            use ::std::{borrow::Cow, mem::transmute};
                            use ::radicle_git_ext::ref_format::{Component, RefStr, RefString};

                            let inner: RefString = transmute(#safe.to_owned());
                            let cow: Cow<'static, RefStr> = Cow::Owned(inner);
                            transmute::<_, Component>(cow)
                        }
                    };

                    TokenStream::from(expand)
                }

                None => {
                    abort!(lit.span(), "component contains a '/'");
                }
            }
        }

        Err(e) => {
            abort!(lit.span(), "invalid refname literal: {}", e);
        }
    }
}

/// Create a [`git_ref_format_core::refspec::PatternString`] from a string
/// literal.
///
/// The string is validated at compile time, and an unsafe conversion is
/// emitted.
#[proc_macro_error]
#[proc_macro]
pub fn pattern(input: TokenStream) -> TokenStream {
    let lit = parse_macro_input!(input as LitStr);
    let val = lit.value();

    let parsed: Result<&PatternStr, Error> = val.as_str().try_into();
    match parsed {
        Ok(safe) => {
            let safe: &str = safe.as_str();
            let expand = quote! {
                unsafe {
                    use ::std::mem::transmute;
                    use ::radicle_git_ext::ref_format::refspec::PatternString;

                    transmute::<_, PatternString>(#safe.to_owned())
                }
            };
            TokenStream::from(expand)
        }

        Err(e) => {
            abort!(lit.span(), "invalid refspec pattern literal: {}", e);
        }
    }
}

/// Create a [`git_ref_format_core::refspec::QualifiedPattern`] from a string
/// literal.
///
/// The string is validated at compile time, and an unsafe conversion is
/// emitted.
#[proc_macro_error]
#[proc_macro]
pub fn qualified_pattern(input: TokenStream) -> TokenStream {
    let lit = parse_macro_input!(input as LitStr);
    let val = lit.value();

    let parsed: Result<&PatternStr, Error> = val.as_str().try_into();
    match parsed {
        Ok(safe) => {
            let safe: &str = safe.as_str();
            let expand = quote! {
                unsafe {
                    use ::std::mem::transmute;
                    use ::radicle_git_ext::ref_format::refspec::QualifiedPattern;

                    transmute::<_, QualifiedPattern>(#safe.to_owned())
                }
            };
            TokenStream::from(expand)
        }

        Err(e) => {
            abort!(lit.span(), "invalid refspec pattern literal: {}", e);
        }
    }
}