Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle-git-ref-format src lib.rs
#![no_std]

//! [`git_ref_format`]: https://crates.io/crates/git-ref-format
//! [`radicle-git-ext`]: https://crates.io/crates/radicle-git-ext
//!
//! This crate depends on and re-exports from [`git_ref_format_core`].
//!
//! ## Macros
//!
//! Instead of providing procedural macros, like [`git_ref_format`]
//! it just provides much simpler declarative macros, guarded by the feature
//! flag `macro`.
//!
//! ### Benefits
//!
//! - Does not depend on [`radicle-git-ext`].
//! - Does not pull in procedural macro dependencies.
//! - Has much smaller compile-time overhead than [`git_ref_format`].
//!
//! ### Drawback
//!
//! The main drawback is that the macros in this crate cannot provide compile
//! time validation of the argument. Thus, these macros must be used in
//! conjunction with testing: If all generated objects are used in tests, and
//! these tests are run, then the guarantees are equally strong. Consumers that
//! do not or cannot test their code should not use the macros then.

pub use git_ref_format_core::*;

/// Create a [`git_ref_format_core::RefString`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! refname {
    ($arg:literal) => {{
        use $crate::RefString;

        #[cfg(debug_assertions)]
        {
            RefString::try_from($arg).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid reference name"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use alloc::string::String;

            let s: String = $arg.to_owned();
            unsafe { core::mem::transmute::<_, RefString>(s) }
        }
    }};
}

/// Create a [`git_ref_format_core::Qualified`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! qualified {
    ($arg:literal) => {{
        use $crate::Qualified;

        #[cfg(debug_assertions)]
        {
            Qualified::from_refstr($crate::refname!($arg)).expect(core::concat!(
                "literal `",
                $arg,
                "` must be of the form 'refs/<category>/<name>'"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::{RefStr, RefString};

            let s: String = $arg.to_owned();
            let refstring: RefString = unsafe { transmute(s) };
            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
            let qualified: Qualified = unsafe { transmute(cow) };

            qualified
        }
    }};
}

/// Create a [`git_ref_format_core::Component`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! component {
    ($arg:literal) => {{
        use $crate::Component;

        #[cfg(debug_assertions)]
        {
            Component::from_refstr($crate::refname!($arg)).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid component (cannot contain '/')"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::{RefStr, RefString};

            let s: String = $arg.to_owned();
            let refstring: RefString = unsafe { transmute(s) };
            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
            let component: Component = unsafe { transmute(cow) };

            component
        }
    }};
}

/// Create a [`git_ref_format_core::refspec::PatternString`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! pattern {
    ($arg:literal) => {{
        use $crate::refspec::PatternString;

        #[cfg(debug_assertions)]
        {
            PatternString::try_from($arg).expect(core::concat!(
                "literal `",
                $arg,
                "` must be a valid refspec pattern"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use alloc::string::String;

            let s: String = $arg.to_owned();
            unsafe { core::mem::transmute::<_, PatternString>(s) }
        }
    }};
}

/// Create a [`git_ref_format_core::refspec::QualifiedPattern`] from a string literal.
///
/// Similar to [`core::debug_assert`], an optimized build will not validate
/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
/// passed to the compiler.
#[cfg(any(feature = "macro", test))]
#[macro_export]
macro_rules! qualified_pattern {
    ($arg:literal) => {{
        use $crate::refspec::QualifiedPattern;

        #[cfg(debug_assertions)]
        {
            use core::concat;

            use $crate::refspec::PatternStr;

            let pattern = PatternStr::try_from_str($arg).expect(concat!(
                "literal `",
                $arg,
                "` must be a valid refspec pattern"
            ));

            QualifiedPattern::from_patternstr(pattern).expect(concat!(
                "literal `",
                $arg,
                "` must be a valid qualified refspec pattern"
            ))
        }

        #[cfg(not(debug_assertions))]
        {
            extern crate alloc;

            use core::mem::transmute;

            use alloc::borrow::Cow;
            use alloc::string::String;

            use $crate::refspec::{PatternStr, PatternString};

            let s: String = $arg.to_owned();
            let pattern: PatternString = unsafe { transmute(s) };
            let cow: Cow<'_, PatternStr> = Cow::Owned(pattern);
            let qualified: QualifiedPattern = unsafe { transmute(cow) };

            qualified
        }
    }};
}

#[cfg(test)]
mod test {
    #[test]
    fn refname() {
        let _ = crate::refname!("refs/heads/main");
        let _ = crate::refname!("refs/tags/v1.0.0");
        let _ = crate::refname!("refs/remotes/origin/main");
        let _ = crate::refname!("a");
    }

    #[test]
    #[should_panic]
    fn refname_invalid() {
        let _ = crate::refname!("a~b");
    }

    #[test]
    fn qualified() {
        let _ = crate::qualified!("refs/heads/main");
        let _ = crate::qualified!("refs/tags/v1.0.0");
        let _ = crate::qualified!("refs/remotes/origin/main");
    }

    #[test]
    #[should_panic]
    fn qualified_invalid() {
        let _ = crate::qualified!("a");
    }

    #[test]
    fn component() {
        let _ = crate::component!("a");
    }

    #[test]
    #[should_panic]
    fn component_invalid() {
        let _ = crate::component!("a/b");
    }

    #[test]
    fn pattern() {
        let _ = crate::pattern!("refs/heads/main");
        let _ = crate::pattern!("refs/tags/v1.0.0");
        let _ = crate::pattern!("refs/remotes/origin/main");

        let _ = crate::pattern!("a");
        let _ = crate::pattern!("a/*");
        let _ = crate::pattern!("*");
        let _ = crate::pattern!("a/b*");
        let _ = crate::pattern!("a/b*/c");
        let _ = crate::pattern!("a/*/c");
    }

    #[test]
    fn qualified_pattern() {
        let _ = crate::qualified_pattern!("refs/heads/main");
        let _ = crate::qualified_pattern!("refs/tags/v1.0.0");
        let _ = crate::qualified_pattern!("refs/remotes/origin/main");

        let _ = crate::qualified_pattern!("refs/heads/main/*");
        let _ = crate::qualified_pattern!("refs/tags/v*");
        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
        let _ = crate::qualified_pattern!("refs/remotes/origin/department/*/person");
    }

    #[test]
    #[should_panic]
    fn qualified_pattern_invalid() {
        let _ = crate::qualified_pattern!("a/*/b");
    }
}