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        fn cast(x: &::std::path::Path) -> &::std::path::Path { x }
232        $first.push(cast($third.as_ref()));
233        $first
234    }};
235    ($first:ident / $second:ident / $third:expr) => {{
236        let x: &::std::path::Path = $second.as_ref();
237        $first.push(x);
238        fn cast(x: &::std::path::Path) -> &::std::path::Path { x }
239        $first.push(cast($third.as_ref())); $first
240    }};
241    ($first:ident / $second:expr) => {{
242        fn cast(x: &::std::path::Path) -> &::std::path::Path { x }
243        $first.push(cast($second.as_ref())); $first
244    }};
245
246}
247
248#[cfg(test)]
249mod tests {
250    use std::path::{Path, PathBuf};
251
252    fn long_expected() -> PathBuf {
253        PathBuf::from("a")
254            .join("b")
255            .join("c")
256            .join("d")
257            .join("e")
258            .join("f")
259            .join("g")
260            .join("h")
261            .join("i")
262            .join("j")
263    }
264
265    // literal first, mix of literal / ident / expr segments
266    #[test]
267    fn long_path_from_literal() {
268        let c = "c";
269        let e = String::from("e");
270        let h = "h";
271        let p = crate::path!("a" / "b" / (c) / "d" / (e.as_str()) / "f" / "g" / h / "i" / "j");
272        assert_eq!(p, long_expected());
273        let _ = (c, h); // idents still accessible
274    }
275
276    // owned PathBuf first, mix of ident / expr / literal segments
277    #[test]
278    fn long_path_from_owned() {
279        let base = PathBuf::from("a");
280        let b = "b";
281        let d = String::from("d");
282        let g = "g";
283        let p = crate::path!(base / (b) / "c" / (d.as_str()) / "e" / "f" / g / "h" / "i" / "j");
284        assert_eq!(p, long_expected());
285        let _ = (b, g);
286    }
287
288    // borrowed &Path first, mix of expr / literal / ident segments
289    #[test]
290    fn long_path_from_borrowed() {
291        let base = PathBuf::from("a");
292        let base_ref: &Path = base.as_path();
293        let c = String::from("c");
294        let f = "f";
295        let i = "i";
296        let p = crate::path!(&base_ref / "b" / (c.as_str()) / "d" / "e" / f / "g" / "h" / i / "j");
297        assert_eq!(p, long_expected());
298        let _ = (f, i);
299    }
300
301    // expr-owned first, mix of literal / ident / expr segments
302    #[test]
303    fn long_path_from_expr_owned() {
304        let d = "d";
305        let e = String::from("e");
306        let f = String::from("f");
307        let p = crate::path!(
308            (PathBuf::from("a")) / "b" / "c" / d / (&e) / (f.as_str()) / "g" / "h" / "i" / "j"
309        );
310        assert_eq!(p, long_expected());
311        let _ = d;
312    }
313
314    // expr-borrowed first, mix of ident / expr / literal segments
315    #[test]
316    fn long_path_from_expr_borrowed() {
317        let base = PathBuf::from("a");
318        let b = "b";
319        let e = String::from("e");
320        let j = "j";
321        let p = crate::path!(
322            &(base.as_path()) / b / "c" / "d" / (e.as_str()) / "f" / "g" / "h" / "i" / j
323        );
324        assert_eq!(p, long_expected());
325        let _ = (b, j);
326    }
327
328    // no two consecutive segments share a type: cycles ident / expr / literal throughout
329    // 10 segments (even) — exercises even-count terminal arm
330    #[test]
331    fn long_path_no_consecutive_same_type_even() {
332        let b = "b";
333        let c = String::from("c");
334        let e = "e";
335        let f = String::from("f");
336        let h = "h";
337        let i = String::from("i");
338        let p = crate::path!(
339            "a" / b / (c.as_str()) / "d" / e / (f.as_str()) / "g" / h / (i.as_str()) / "j"
340        );
341        assert_eq!(p, long_expected());
342        let _ = (b, e, h);
343    }
344
345    fn nine_expected() -> PathBuf {
346        PathBuf::from("a")
347            .join("b")
348            .join("c")
349            .join("d")
350            .join("e")
351            .join("f")
352            .join("g")
353            .join("h")
354            .join("i")
355    }
356
357    // no two consecutive segments share a type: cycles literal / ident / expr throughout
358    // 9 segments (odd) — exercises odd-count terminal arm
359    #[test]
360    fn long_path_no_consecutive_same_type_odd() {
361        let b = "b";
362        let c = String::from("c");
363        let e = "e";
364        let f = String::from("f");
365        let h = "h";
366        let i = String::from("i");
367        let p =
368            crate::path!("a" / b / (c.as_str()) / "d" / e / (f.as_str()) / "g" / h / i.as_str());
369        assert_eq!(p, nine_expected());
370        let _ = (b, e, h);
371    }
372
373    #[test]
374    fn no_temporary() {
375        let aaa = String::from("aaa");
376        let bbb = String::from("bbb");
377        struct Foo {
378            bar: String,
379        }
380        let foo = Foo {
381            bar: String::from("bar"),
382        };
383        let p = crate::path!(&aaa / "k" / bbb / foo.bar);
384        let expected = PathBuf::from("aaa").join("k").join("bbb").join("bar");
385        assert_eq!(p, expected);
386        let p = crate::path!((aaa.clone().into()) / "k" / bbb / foo.bar);
387        assert_eq!(p, expected);
388        let p = crate::path!((aaa.into()) / "k" / bbb / format!("biz{}biz", foo.bar));
389        let expected = PathBuf::from("aaa").join("k").join("bbb").join("bizbarbiz");
390        assert_eq!(p, expected);
391    }
392}