https://github.com/cwfitzgerald/path-dsl-rs
A Rust utility DSL and macro to help construct and modify Paths.
https://github.com/cwfitzgerald/path-dsl-rs
dsl macros no-dependencies path paths rust rust-macro utility-library zero-cost-abstraction
Last synced: 9 months ago
JSON representation
A Rust utility DSL and macro to help construct and modify Paths.
- Host: GitHub
- URL: https://github.com/cwfitzgerald/path-dsl-rs
- Owner: cwfitzgerald
- License: mit
- Created: 2019-08-09T19:55:32.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2020-07-11T23:26:34.000Z (almost 6 years ago)
- Last Synced: 2025-09-14T15:10:45.252Z (9 months ago)
- Topics: dsl, macros, no-dependencies, path, paths, rust, rust-macro, utility-library, zero-cost-abstraction
- Language: Rust
- Homepage:
- Size: 55.7 KB
- Stars: 23
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# path-dsl
[](https://crates.io/crates/path-dsl)
[](https://docs.rs/path-dsl)

Utility DSL and macro to help deal with Paths.
PathDSL provides a simple and zero-overhead abstraction for creating
paths and appending to existing `Path`-like things.
## Overview
```rust
use path_dsl::path;
# use std::path::{PathBuf, Path};
// PathBuf::push() only called once with consecutive literals:
let literals: PathBuf = path!("dir1" | "dir2" | "dir3");
// Type annotation for illustration purposes; not needed
// Does not copy data if first path segment is a owning value:
let moving = path!(literals | "dir4");
// Mixing and matching is easy:
let start = path!("some" | "dir");
let end = path!("my_folder" | "my_file.txt");
// Can borrow as normal
let result = path!(start | "middle_folder" | &end);
// Works with PathBuf, Path, and String-likes
let file = Path::new("file.txt");
let folder = PathBuf::from("folder");
let middle: &str = "other_middle";
let combined = path!(folder | middle | "middle_folder" | file);
```
## PathDSL Macro and Type
PathDSL's [`path!`](https://docs.rs/path-dsl/*/path_dsl/macro.path.html) macro allows for the creation of a `PathBuf` in the most efficent way possible in the situation.
note the use of `|` instead of `/` due to rust's macro rules
```rust
use path_dsl::path;
// Type annotation for illustration only, not needed
let path: PathBuf = path!("dir1" | "dir2" | "dir3" | "file.txt");
```
#### PathDSL
You can also generate a PathDSL directly, though this is discouraged. PathDSL will pretend to be
a `PathBuf` as best it can, but it is almost always more efficent to use the `path!` macro to generate
a `PathBuf` directly.
```rust
use path_dsl::PathDSL;
let path = PathDSL::from("dir1") / "dir2" / "dir3" / "file.txt";
```
#### Adding Path-Like Structures
As well as using regular string literals, you can use anything that can be passed to `PathBuf::push`
as a part of the DSL.
Note the borrow on `other`: as these types are not `Copy`, they will be moved
into the path unless you borrow them. This matches behavior with `PathBuf::push`, but can be surprising
when used in a infix expression.
```rust
use path_dsl::{path, PathDSL};
let other = PathBuf::from("some_dir");
let filename: &str = "my_file.txt";
let mac: PathBuf = path!("dir1" | "dir2" | &other | filename); // Preferred
let path: PathDSL = PathDSL::from("dir1") / "dir2" / other / filename; // Also works
```
#### Moving vs Borrowing
Both the macro and the DSL type behave the same with regard to borrowing vs moving. If a
reference is provided, it will borrow the provided value. However, if a value is provided
**it will move it**, making the value unusable afterwards. While these are the normal rules
for rust, infix operators are normally used with `Copy` types, so this may be **surprising**.
Both mutable and immutable borrows are supported, though they will never actually mutate anything.
```rust,compile_fail
use path_dsl::path;
# use std::path::PathBuf;
let value = PathBuf::from("some_dir");
let borrow: &str = "my_file.txt";
let mac = path!(value | borrow);
let path = path!(value | borrow); // Will not compile because `value` was moved
```
You must manually borrow it:
```rust
let mac = path!(&value | borrow); // Borrow value so it can be used later
let path = PathDSL::new() / value / borrow; // Not used afterwards, so doesn't need a borrow
```
#### PathDSL <=> PathBuf
`PathDSL` is designed to be a drop-in replacement for `PathBuf`, including trivial conversions
between the two. In any situation where you would be able to use `PathBuf` you can use
`PathDSL`. `PathDSL` includes an implementation of `Deref` to a `PathBuf` (and by proxy `Path`) and re-implements all functions that take `self`, so is fully api compatable.
However there are some situations where you must have a `PathBuf`.
Obtaining a `&PathBuf` is trivial through dereferencing and obtaining a `PathBuf` is possible through the [`PathDSL::into_pathbuf`](https://docs.rs/path-dsl/*/path_dsl/struct.PathDSL.html#method.into_pathbuf) function.
PathDSL is `#[repr(transparent)]` over `PathBuf` and all functions are force-inlined so
conversions and operations should be cost-free compared to the equivalent `PathBuf` operation.
If they aren't, please file a bug.
Some known issues are:
**Equality**
```rust
use path_dsl::path;
let dsl = path!("file.txt");
let buf = PathBuf::from("file.txt");
assert!(dsl == buf);
// Must de-reference to PathBuf can't implement `Eq` for `PathBuf`
assert!(buf == *dsl);
```
**Function Calls**
```rust
use path_dsl::path;
fn func(p: PathBuf) {
}
let dsl = path!("file.txt");
let buf = PathBuf::from("file.txt");
func(buf);
// Must convert into `PathBuf`
// Dereferencing doesn't work because `func` moves.
func(dsl.to_path_buf());
func(dsl.into()) // also works
```
#### Macro Optimizations
As previously mentioned, the macro contains some optimizations over using raw `PathDSL` and should always
be used over manually using PathDSL. These optimizations happen at compile time, and are guaranteed.
Further details on these can be found on the [`path!`](https://docs.rs/path-dsl/*/path_dsl/macro.path.html) macro documentation.
**String Literal Concatenation:**
While it is ill-advised to use string literals with slashes in a `Path`, The [`path!`](https://docs.rs/path-dsl/*/path_dsl/macro.path.html) macro
takes slashes into account, and automatically constructs a single string literal from multiple
consecutive string literals. This can potentially save an allocation or two in the underlying
`OsString`.
```rust
use path_dsl::path;
let p = path!("this" | "is" | "combined");
if cfg!(windows) {
assert_eq!(p, PathBuf::from("this\\is\\combined"));
} else {
assert_eq!(p, PathBuf::from("this/is/combined"));
}
```
**First-Argument Optimization:**
When the very first argument of the [`path!`](https://docs.rs/path-dsl/*/path_dsl/macro.path.html) macro is a owning `PathBuf`, `OsString` or `PathDSL`
passed by value (moved), instead of copying everything into a new `PathDSL`, it will just steal the
buffer from that moved-in value. This allows you to use the [`path!`](https://docs.rs/path-dsl/*/path_dsl/macro.path.html) macro fearlessly when
appending to already existing variables.
```rust
use path_dsl::path;
let first = PathBuf::from("a_very_long_folder_name");
let p = path!(first); // Does not copy anything.
```
## Why Use A Crate?
You may be wondering why you should use a crate for this when you can easily wrap `PathBuf` and
add some `Div` implementations. This is basically what I thought as well until I actually went
to go implement this crate. There is a surprising amount of very tedious and particular code to try to emulate
`PathBuf` directly, as well as to test the functionality.
With this in mind, I have made `path_dsl` completely dependency free, choosing to lean on declarative
macros over proc macros as to not depend on things like `syn`. Additionally, everything is contained within
this one file, I have thorough tests, and I have added `#[deny(unsafe_code)]` for good measure.
Hopefully this makes this crate light enough and easily-auditable enough to be an acceptable dependency.
License: MIT