Skip to main content

radicle_git_ref_format/
lib.rs

1#![no_std]
2
3//! [`git_ref_format`]: https://crates.io/crates/git-ref-format
4//! [`radicle-git-ext`]: https://crates.io/crates/radicle-git-ext
5//!
6//! This crate depends on and re-exports from [`git_ref_format_core`].
7//!
8//! ## Macros
9//!
10//! Instead of providing procedural macros, like [`git_ref_format`]
11//! it just provides much simpler declarative macros, guarded by the feature
12//! flag `macro`.
13//!
14//! ### Benefits
15//!
16//! - Does not depend on [`radicle-git-ext`].
17//! - Does not pull in procedural macro dependencies.
18//! - Has much smaller compile-time overhead than [`git_ref_format`].
19//!
20//! ### Drawback
21//!
22//! The main drawback is that the macros in this crate cannot provide compile
23//! time validation of the argument. Thus, these macros must be used in
24//! conjunction with testing: If all generated objects are used in tests, and
25//! these tests are run, then the guarantees are equally strong. Consumers that
26//! do not or cannot test their code should not use the macros then.
27
28pub use git_ref_format_core::*;
29
30/// Create a [`git_ref_format_core::RefString`] from a string literal.
31///
32/// Similar to [`core::debug_assert`], an optimized build will not validate
33/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
34/// passed to the compiler.
35#[cfg(any(feature = "macro", test))]
36#[macro_export]
37macro_rules! refname {
38    ($arg:literal) => {{
39        use $crate::RefString;
40
41        #[cfg(debug_assertions)]
42        {
43            RefString::try_from($arg).expect(core::concat!(
44                "literal `",
45                $arg,
46                "` must be a valid reference name"
47            ))
48        }
49
50        #[cfg(not(debug_assertions))]
51        {
52            extern crate alloc;
53
54            use alloc::string::String;
55
56            let s: String = $arg.to_owned();
57            unsafe { core::mem::transmute::<_, RefString>(s) }
58        }
59    }};
60}
61
62/// Create a [`git_ref_format_core::Qualified`] from a string literal.
63///
64/// Similar to [`core::debug_assert`], an optimized build will not validate
65/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
66/// passed to the compiler.
67#[cfg(any(feature = "macro", test))]
68#[macro_export]
69macro_rules! qualified {
70    ($arg:literal) => {{
71        use $crate::Qualified;
72
73        #[cfg(debug_assertions)]
74        {
75            Qualified::from_refstr($crate::refname!($arg)).expect(core::concat!(
76                "literal `",
77                $arg,
78                "` must be of the form 'refs/<category>/<name>'"
79            ))
80        }
81
82        #[cfg(not(debug_assertions))]
83        {
84            extern crate alloc;
85
86            use core::mem::transmute;
87
88            use alloc::borrow::Cow;
89            use alloc::string::String;
90
91            use $crate::{RefStr, RefString};
92
93            let s: String = $arg.to_owned();
94            let refstring: RefString = unsafe { transmute(s) };
95            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
96            let qualified: Qualified = unsafe { transmute(cow) };
97
98            qualified
99        }
100    }};
101}
102
103/// Create a [`git_ref_format_core::Component`] from a string literal.
104///
105/// Similar to [`core::debug_assert`], an optimized build will not validate
106/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
107/// passed to the compiler.
108#[cfg(any(feature = "macro", test))]
109#[macro_export]
110macro_rules! component {
111    ($arg:literal) => {{
112        use $crate::Component;
113
114        #[cfg(debug_assertions)]
115        {
116            Component::from_refstr($crate::refname!($arg)).expect(core::concat!(
117                "literal `",
118                $arg,
119                "` must be a valid component (cannot contain '/')"
120            ))
121        }
122
123        #[cfg(not(debug_assertions))]
124        {
125            extern crate alloc;
126
127            use core::mem::transmute;
128
129            use alloc::borrow::Cow;
130            use alloc::string::String;
131
132            use $crate::{RefStr, RefString};
133
134            let s: String = $arg.to_owned();
135            let refstring: RefString = unsafe { transmute(s) };
136            let cow: Cow<'_, RefStr> = Cow::Owned(refstring);
137            let component: Component = unsafe { transmute(cow) };
138
139            component
140        }
141    }};
142}
143
144/// Create a [`git_ref_format_core::refspec::PatternString`] from a string literal.
145///
146/// Similar to [`core::debug_assert`], an optimized build will not validate
147/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
148/// passed to the compiler.
149#[cfg(any(feature = "macro", test))]
150#[macro_export]
151macro_rules! pattern {
152    ($arg:literal) => {{
153        use $crate::refspec::PatternString;
154
155        #[cfg(debug_assertions)]
156        {
157            PatternString::try_from($arg).expect(core::concat!(
158                "literal `",
159                $arg,
160                "` must be a valid refspec pattern"
161            ))
162        }
163
164        #[cfg(not(debug_assertions))]
165        {
166            extern crate alloc;
167
168            use alloc::string::String;
169
170            let s: String = $arg.to_owned();
171            unsafe { core::mem::transmute::<_, PatternString>(s) }
172        }
173    }};
174}
175
176/// Create a [`git_ref_format_core::refspec::QualifiedPattern`] from a string literal.
177///
178/// Similar to [`core::debug_assert`], an optimized build will not validate
179/// (but rather perform an unsafe conversion) unless `-C debug-assertions` is
180/// passed to the compiler.
181#[cfg(any(feature = "macro", test))]
182#[macro_export]
183macro_rules! qualified_pattern {
184    ($arg:literal) => {{
185        use $crate::refspec::QualifiedPattern;
186
187        #[cfg(debug_assertions)]
188        {
189            use core::concat;
190
191            use $crate::refspec::PatternStr;
192
193            let pattern = PatternStr::try_from_str($arg).expect(concat!(
194                "literal `",
195                $arg,
196                "` must be a valid refspec pattern"
197            ));
198
199            QualifiedPattern::from_patternstr(pattern).expect(concat!(
200                "literal `",
201                $arg,
202                "` must be a valid qualified refspec pattern"
203            ))
204        }
205
206        #[cfg(not(debug_assertions))]
207        {
208            extern crate alloc;
209
210            use core::mem::transmute;
211
212            use alloc::borrow::Cow;
213            use alloc::string::String;
214
215            use $crate::refspec::{PatternStr, PatternString};
216
217            let s: String = $arg.to_owned();
218            let pattern: PatternString = unsafe { transmute(s) };
219            let cow: Cow<'_, PatternStr> = Cow::Owned(pattern);
220            let qualified: QualifiedPattern = unsafe { transmute(cow) };
221
222            qualified
223        }
224    }};
225}
226
227#[cfg(test)]
228mod test {
229    #[test]
230    fn refname() {
231        let _ = crate::refname!("refs/heads/main");
232        let _ = crate::refname!("refs/tags/v1.0.0");
233        let _ = crate::refname!("refs/remotes/origin/main");
234        let _ = crate::refname!("a");
235    }
236
237    #[test]
238    #[should_panic]
239    fn refname_invalid() {
240        let _ = crate::refname!("a~b");
241    }
242
243    #[test]
244    fn qualified() {
245        let _ = crate::qualified!("refs/heads/main");
246        let _ = crate::qualified!("refs/tags/v1.0.0");
247        let _ = crate::qualified!("refs/remotes/origin/main");
248    }
249
250    #[test]
251    #[should_panic]
252    fn qualified_invalid() {
253        let _ = crate::qualified!("a");
254    }
255
256    #[test]
257    fn component() {
258        let _ = crate::component!("a");
259    }
260
261    #[test]
262    #[should_panic]
263    fn component_invalid() {
264        let _ = crate::component!("a/b");
265    }
266
267    #[test]
268    fn pattern() {
269        let _ = crate::pattern!("refs/heads/main");
270        let _ = crate::pattern!("refs/tags/v1.0.0");
271        let _ = crate::pattern!("refs/remotes/origin/main");
272
273        let _ = crate::pattern!("a");
274        let _ = crate::pattern!("a/*");
275        let _ = crate::pattern!("*");
276        let _ = crate::pattern!("a/b*");
277        let _ = crate::pattern!("a/b*/c");
278        let _ = crate::pattern!("a/*/c");
279    }
280
281    #[test]
282    fn qualified_pattern() {
283        let _ = crate::qualified_pattern!("refs/heads/main");
284        let _ = crate::qualified_pattern!("refs/tags/v1.0.0");
285        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
286
287        let _ = crate::qualified_pattern!("refs/heads/main/*");
288        let _ = crate::qualified_pattern!("refs/tags/v*");
289        let _ = crate::qualified_pattern!("refs/remotes/origin/main");
290        let _ = crate::qualified_pattern!("refs/remotes/origin/department/*/person");
291    }
292
293    #[test]
294    #[should_panic]
295    fn qualified_pattern_invalid() {
296        let _ = crate::qualified_pattern!("a/*/b");
297    }
298}