Skip to main content

pistonite_cu/str/
path_macro.rs

1/// Efficient Path-join macro
2///
3/// **The macro rules above is only for illustration purpose, see source code for implementation**
4///
5/// ## Usage
6/// The macro efficiently creates joined paths from either a owned `PathBuf`
7/// or a borrowed Path reference (`impl AsRef<Path>`), and one or more path segments reference
8/// to join. The OS separator is used (i.e. `\` on Windows).
9///
10/// The format of the macro in pseudocode is:
11/// ```rust,ignore
12/// cu::path!( FIRST_SEG  $( / NEXT_SEG )* )
13/// ```
14///
15/// `FIRST_SEG` can be:
16/// - A owned `PathBuf` ident
17///   - e.g. `cu::path!(my_path_buf / ...)`
18/// - A borrowed `&Path` ident:
19///   - e.g. `cu::path!(&my_path / ...)`
20///   - Here `&` is the macro rule to indicate you don't want to borrow the path, so you need
21///     it even when `my_path` is already a borrowed path
22/// - A literal string, which you can use without `&`
23///   - e.g. `cu::path!("my_path" / ...)`
24/// - An expression that evaluates to a owned `PathBuf`
25///   - e.g. `cu::path!( (get_path()) / ...)`
26///   - Expression needs to be parenthesized because `/` cannot follow an expression in macro
27///     rules. `{ }` also works
28/// - An expression that evaludates to a borrowed `&Path`
29///   - e.g. `cu::path!( &(my.path) / ... )`
30///   - Expression needs to be parenthesized because `/` cannot follow an expression in macro
31///     rules. `{ }` also works
32///   - Here `&` is the macro rule to indicate you don't want to borrow the path, so you need
33///     it even when `my_path` is already a borrowed path
34///
35/// Each `NEXT_SEG` can be:
36/// - A literal string
37/// - An ident (the macro will not take ownership of the variable)
38/// - An expression wrapped with either `( )` or `{ }`. The last expression doesn't need to be
39///   wrapped
40///
41/// ## Examples
42/// ```rust
43/// # use pistonite_cu as cu;
44/// use std::path::{Path, PathBuf};
45///
46/// // From a literal string
47/// let p1 = cu::path!("home" / "user");
48/// let p2 = cu::path!("home" / "user" / "docs");
49/// assert_eq!(p1, PathBuf::from("home").join("user"));
50/// assert_eq!(p2, PathBuf::from("home").join("user").join("docs"));
51///
52/// // From an owned PathBuf ident (base is moved)
53/// let base = PathBuf::from("usr").join("local");
54/// let p = cu::path!(base / "bin" / "tool");
55/// assert_eq!(p, PathBuf::from("usr").join("local").join("bin").join("tool"));
56///
57/// // From a borrowed &Path ident (use `&` even if already a reference)
58/// let base = PathBuf::from("etc");
59/// let base_ref: &Path = base.as_path();
60/// let p = cu::path!(&base_ref / "nginx" / "nginx.conf");
61/// assert_eq!(p, PathBuf::from("etc").join("nginx").join("nginx.conf"));
62///
63/// // From an expression returning PathBuf (must be parenthesized)
64/// let p = cu::path!((PathBuf::from("usr").join("local")) / "bin");
65/// assert_eq!(p, PathBuf::from("usr").join("local").join("bin"));
66///
67/// // From an expression returning &Path (must be parenthesized, and needs `&`)
68/// let owned = PathBuf::from("var");
69/// let p = cu::path!(&(owned.as_path()) / "log");
70/// assert_eq!(p, PathBuf::from("var").join("log"));
71///
72/// // NEXT_SEG can be an ident — not moved, still usable after
73/// let dir = "subdir";
74/// let file = "file.txt";
75/// let p = cu::path!("root" / dir / file);
76/// assert_eq!(p, PathBuf::from("root").join(dir).join(file));
77/// let _ = (dir, file); // still accessible
78///
79/// // NEXT_SEG can be an expression (must be parenthesized)
80/// let sub = String::from("sub");
81/// let p = cu::path!("root" / (sub.as_str()) / "output.log");
82/// assert_eq!(p, PathBuf::from("root").join("sub").join("output.log"));
83/// ```
84///
85/// ## Implementation
86/// Currently this uses the same implementation as the standard library (as of 1.95.0)
87/// that does not do any probing to pre-allocate the path based on the input iterator.
88/// Each segment is `.push()`-ed onto the initial buffer in a loop.
89///
90#[cfg(doc)]
91#[macro_export]
92macro_rules! path {
93    ($(&)? ident / $(ident_or_literal_or_expr) / * ) => {};
94    (literal / $(ident_or_literal_or_expr) / * ) => {};
95    ($(&)? ( path_expression ) / $(ident_or_literal_or_expr) / *) => {};
96}
97
98#[cfg(not(doc))]
99#[macro_export]
100macro_rules! path {
101    ($first:ident / $($rest_segs:tt)* ) => {{
102        let mut x = $first;
103        $crate::__path_internal!(x / $($rest_segs)*)
104    }};
105    ( & $first:ident / $($rest_segs:tt)* ) => {{
106        let mut x = std::path::PathBuf::from($first.to_owned());
107        $crate::__path_internal!(x / $($rest_segs)*)
108    }};
109    ($first:literal / $($rest_segs:tt)* ) => {{
110        let mut x = std::path::PathBuf::from($first);
111        $crate::__path_internal!(x / $($rest_segs)*)
112    }};
113    (( $first:expr ) / $($rest_segs:tt)* ) => {{
114        let mut x: ::std::path::PathBuf = { $first };
115        $crate::__path_internal!(x / $($rest_segs)*)
116    }};
117    ( & ( $first:expr ) / $($rest_segs:tt)* ) => {{
118        let x: &::std::path::Path = {$first}.as_ref();
119        let mut x = x.to_path_buf();
120        $crate::__path_internal!(x / $($rest_segs)*)
121    }};
122    ( $first:block / $($rest_segs:tt)* ) => {{
123        let mut x: ::std::path::PathBuf = $first;
124        $crate::__path_internal!(x / $($rest_segs)*)
125    }};
126    ( & $first:block / $($rest_segs:tt)* ) => {{
127        let x: &::std::path::Path = $first.as_ref();
128        let mut x = x.to_path_buf();
129        $crate::__path_internal!(x / $($rest_segs)*)
130    }};
131}
132
133#[macro_export]
134#[doc(hidden)]
135macro_rules! __path_internal {
136    // Non-expression terminals (1 or 2 remaining)
137    ($first:ident / $second:literal) => {{
138        $first.push($second); $first
139    }};
140    ($first:ident / $second:ident) => {{
141        let x: &::std::path::Path = $second.as_ref();
142        $first.push(x); $first
143    }};
144    ($first:ident / $second:literal / $third:literal) => {{
145        $first.push($second); $first.push($third); $first
146    }};
147    ($first:ident / $second:literal / $third:ident) => {{
148        $first.push($second);
149        let x: &::std::path::Path = $third.as_ref();
150        $first.push(x); $first
151    }};
152    ($first:ident / $second:ident / $third:literal) => {{
153        let x: &::std::path::Path = $second.as_ref();
154        $first.push(x);
155        $first.push($third); $first
156    }};
157    ($first:ident / $second:ident / $third:ident) => {{
158        let x: &::std::path::Path = $second.as_ref();
159        $first.push(x);
160        let x: &::std::path::Path = $third.as_ref();
161        $first.push(x); $first
162    }};
163
164    // non-terminal muchering (2 at a time)
165    ($first:ident / $second:literal / $third:literal / $($rest_segs:tt)* ) => {{
166        $first.push($second); $first.push($third);
167        $crate::__path_internal!($first / $($rest_segs)* )
168    }};
169    ($first:ident / $second:literal / $third:ident / $($rest_segs:tt)* ) => {{
170        $first.push($second);
171        let x: &::std::path::Path = $third.as_ref();
172        $first.push(x);
173        $crate::__path_internal!($first / $($rest_segs)* )
174    }};
175    ($first:ident / $second:literal / ( $third:expr ) / $($rest_segs:tt)* ) => {{
176        $first.push($second);
177        let x: &::std::path::Path = {$third}.as_ref();
178        $first.push(x);
179        $crate::__path_internal!($first / $($rest_segs)* )
180    }};
181    ($first:ident / $second:literal / $third:block / $($rest_segs:tt)* ) => {{
182        $first.push($second);
183        let x: &::std::path::Path = $third.as_ref();
184        $first.push(x);
185        $crate::__path_internal!($first / $($rest_segs)* )
186    }};
187    ($first:ident / $second:ident / $third:literal / $($rest_segs:tt)* ) => {{
188        let x: &::std::path::Path = $second.as_ref();
189        $first.push(x);
190        $first.push($third);
191        $crate::__path_internal!($first / $($rest_segs)* )
192    }};
193    ($first:ident / $second:ident / $third:ident / $($rest_segs:tt)* ) => {{
194        let x: &::std::path::Path = $second.as_ref();
195        $first.push(x);
196        let x: &::std::path::Path = $third.as_ref();
197        $first.push(x);
198        $crate::__path_internal!($first / $($rest_segs)* )
199    }};
200    ($first:ident / $second:ident / ( $third:expr ) / $($rest_segs:tt)* ) => {{
201        let x: &::std::path::Path = $second.as_ref();
202        $first.push(x);
203        let x: &::std::path::Path = {$third}.as_ref();
204        $first.push(x);
205        $crate::__path_internal!($first / $($rest_segs)* )
206    }};
207    ($first:ident / $second:ident / $third:block / $($rest_segs:tt)* ) => {{
208        let x: &::std::path::Path = $second.as_ref();
209        $first.push(x);
210        let x: &::std::path::Path = $third.as_ref();
211        $first.push(x);
212        $crate::__path_internal!($first / $($rest_segs)* )
213    }};
214    // expression muchering (1 at a time)
215    ($first:ident / ( $second:expr ) / $($rest_segs:tt)* ) => {{
216        let x: &::std::path::Path = {$second}.as_ref();
217        $first.push(x);
218        $crate::__path_internal!($first / $($rest_segs)* )
219    }};
220    ($first:ident / $second:block / $($rest_segs:tt)* ) => {{
221        let x: &::std::path::Path = $second.as_ref();
222        $first.push(x);
223        $crate::__path_internal!($first / $($rest_segs)* )
224    }};
225
226    // expression muchering, must be after the non-expression rules
227    // so the `/` doesn't get interpreted as an operator
228
229    ($first:ident / $second:literal / $third:expr) => {{
230        $first.push($second);
231        let x: &::std::path::Path = {$third}.as_ref();
232        $first.push(x); $first
233    }};
234    ($first:ident / $second:ident / $third:expr) => {{
235        let x: &::std::path::Path = $second.as_ref();
236        $first.push(x);
237        let x: &::std::path::Path = {$third}.as_ref();
238        $first.push(x); $first
239    }};
240    ($first:ident / $second:expr) => {{
241        let x: &::std::path::Path = {$second}.as_ref();
242        $first.push(x); $first
243    }};
244
245}
246
247#[cfg(test)]
248mod tests {
249    use std::path::{Path, PathBuf};
250
251    fn long_expected() -> PathBuf {
252        PathBuf::from("a")
253            .join("b")
254            .join("c")
255            .join("d")
256            .join("e")
257            .join("f")
258            .join("g")
259            .join("h")
260            .join("i")
261            .join("j")
262    }
263
264    // literal first, mix of literal / ident / expr segments
265    #[test]
266    fn long_path_from_literal() {
267        let c = "c";
268        let e = String::from("e");
269        let h = "h";
270        let p = crate::path!("a" / "b" / (c) / "d" / (e.as_str()) / "f" / "g" / h / "i" / "j");
271        assert_eq!(p, long_expected());
272        let _ = (c, h); // idents still accessible
273    }
274
275    // owned PathBuf first, mix of ident / expr / literal segments
276    #[test]
277    fn long_path_from_owned() {
278        let base = PathBuf::from("a");
279        let b = "b";
280        let d = String::from("d");
281        let g = "g";
282        let p = crate::path!(base / (b) / "c" / (d.as_str()) / "e" / "f" / g / "h" / "i" / "j");
283        assert_eq!(p, long_expected());
284        let _ = (b, g);
285    }
286
287    // borrowed &Path first, mix of expr / literal / ident segments
288    #[test]
289    fn long_path_from_borrowed() {
290        let base = PathBuf::from("a");
291        let base_ref: &Path = base.as_path();
292        let c = String::from("c");
293        let f = "f";
294        let i = "i";
295        let p = crate::path!(&base_ref / "b" / (c.as_str()) / "d" / "e" / f / "g" / "h" / i / "j");
296        assert_eq!(p, long_expected());
297        let _ = (f, i);
298    }
299
300    // expr-owned first, mix of literal / ident / expr segments
301    #[test]
302    fn long_path_from_expr_owned() {
303        let d = "d";
304        let e = String::from("e");
305        let f = String::from("f");
306        let p = crate::path!(
307            (PathBuf::from("a")) / "b" / "c" / d / (&e) / (f.as_str()) / "g" / "h" / "i" / "j"
308        );
309        assert_eq!(p, long_expected());
310        let _ = d;
311    }
312
313    // expr-borrowed first, mix of ident / expr / literal segments
314    #[test]
315    fn long_path_from_expr_borrowed() {
316        let base = PathBuf::from("a");
317        let b = "b";
318        let e = String::from("e");
319        let j = "j";
320        let p = crate::path!(
321            &(base.as_path()) / b / "c" / "d" / (e.as_str()) / "f" / "g" / "h" / "i" / j
322        );
323        assert_eq!(p, long_expected());
324        let _ = (b, j);
325    }
326
327    // no two consecutive segments share a type: cycles ident / expr / literal throughout
328    // 10 segments (even) — exercises even-count terminal arm
329    #[test]
330    fn long_path_no_consecutive_same_type_even() {
331        let b = "b";
332        let c = String::from("c");
333        let e = "e";
334        let f = String::from("f");
335        let h = "h";
336        let i = String::from("i");
337        let p = crate::path!(
338            "a" / b / (c.as_str()) / "d" / e / (f.as_str()) / "g" / h / (i.as_str()) / "j"
339        );
340        assert_eq!(p, long_expected());
341        let _ = (b, e, h);
342    }
343
344    fn nine_expected() -> PathBuf {
345        PathBuf::from("a")
346            .join("b")
347            .join("c")
348            .join("d")
349            .join("e")
350            .join("f")
351            .join("g")
352            .join("h")
353            .join("i")
354    }
355
356    // no two consecutive segments share a type: cycles literal / ident / expr throughout
357    // 9 segments (odd) — exercises odd-count terminal arm
358    #[test]
359    fn long_path_no_consecutive_same_type_odd() {
360        let b = "b";
361        let c = String::from("c");
362        let e = "e";
363        let f = String::from("f");
364        let h = "h";
365        let i = String::from("i");
366        let p =
367            crate::path!("a" / b / (c.as_str()) / "d" / e / (f.as_str()) / "g" / h / i.as_str());
368        assert_eq!(p, nine_expected());
369        let _ = (b, e, h);
370    }
371}