Skip to content

Commit eb369ed

Browse files
authored
Merge pull request #6 from yaahc/displaypath
feat: add Display support for Path and PathBuf
2 parents c3c1f41 + 6bdf067 commit eb369ed

File tree

7 files changed

+107
-10
lines changed

7 files changed

+107
-10
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ jobs:
3939
- uses: actions-rs/cargo@v1
4040
with:
4141
command: test
42+
- uses: actions-rs/cargo@v1
43+
with:
44+
command: test
45+
args: --no-default-features
4246

4347
fmt:
4448
name: Rustfmt

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "displaydoc"
3-
version = "0.1.4"
3+
version = "0.1.5"
44
authors = ["Jane Lusby <jlusby@yaah.dev>"]
55
edition = "2018"
66
license = "MIT OR Apache-2.0"
@@ -16,7 +16,9 @@ A derive macro for implementing the display Trait via a doc comment and string i
1616
[lib]
1717
proc-macro = true
1818

19-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
19+
[features]
20+
default = ["std"]
21+
std = []
2022

2123
[dependencies]
2224
syn = "1.0"
@@ -28,3 +30,4 @@ trybuild = "1.0"
2830
static_assertions = "0.3.4"
2931
libc = { version = "0.2", default-features = false }
3032
rustversion = "1.0.0"
33+
pretty_assertions = "0.6.1"

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This library provides a convenient derive macro for the standard library's
1111

1212
```toml
1313
[dependencies]
14-
displaydoc = "0.1.4"
14+
displaydoc = "0.1"
1515
```
1616

1717
*Compiler support: requires rustc 1.31+*
@@ -62,6 +62,9 @@ pub enum DataStoreError {
6262
1. **Is this crate `no_std` compatible?**
6363
* Yes! This crate implements the `core::fmt::Display` trait not the `std::fmt::Display` trait so it should work in `std` and `no_std` environments.
6464

65+
2. **Does this crate work with `Path` and `PathBuf` via the `Display` trait?**
66+
* Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl, it then specializes for `Path` and `PathBuf` and when either of these types are found it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the Display format specifier!
67+
6568
<br>
6669

6770
#### License

src/expand.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,42 @@ pub fn derive(input: &DeriveInput) -> Result<TokenStream> {
1111
}
1212
}
1313

14+
#[cfg(feature = "std")]
15+
fn specialization() -> TokenStream {
16+
quote! {
17+
trait DisplayToDisplayDoc {
18+
fn get_display(&self) -> Self;
19+
}
20+
21+
impl<T: core::fmt::Display> DisplayToDisplayDoc for &T {
22+
fn get_display(&self) -> Self {
23+
self
24+
}
25+
}
26+
27+
trait PathToDisplayDoc {
28+
fn get_display(&self) -> std::path::Display<'_>;
29+
}
30+
31+
impl PathToDisplayDoc for std::path::Path {
32+
fn get_display(&self) -> std::path::Display<'_> {
33+
self.display()
34+
}
35+
}
36+
37+
impl PathToDisplayDoc for std::path::PathBuf {
38+
fn get_display(&self) -> std::path::Display<'_> {
39+
self.display()
40+
}
41+
}
42+
}
43+
}
44+
45+
#[cfg(not(feature = "std"))]
46+
fn specialization() -> TokenStream {
47+
quote! {}
48+
}
49+
1450
fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
1551
let ty = &input.ident;
1652
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@@ -38,7 +74,11 @@ fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
3874
}
3975
});
4076

77+
let needed_traits = specialization();
78+
4179
Ok(quote! {
80+
#needed_traits
81+
4282
#display
4383
})
4484
}
@@ -89,7 +129,11 @@ fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> {
89129
return Err(Error::new_spanned(input, "Missing doc comments"));
90130
};
91131

132+
let needed_traits = specialization();
133+
92134
Ok(quote! {
135+
#needed_traits
136+
93137
#display
94138
})
95139
}

src/fmt.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ use proc_macro2::TokenStream;
33
use quote::quote_spanned;
44
use syn::{Ident, LitStr};
55

6+
#[cfg(feature = "std")]
7+
const IS_STD: bool = true;
8+
#[cfg(not(feature = "std"))]
9+
const IS_STD: bool = false;
10+
11+
macro_rules! peek_next {
12+
($read:ident) => {
13+
match $read.chars().next() {
14+
Some(next) => next,
15+
None => return,
16+
}
17+
};
18+
}
19+
620
impl Display {
721
// Transform `"error {var}"` to `"error {}", var`.
822
pub fn expand_shorthand(&mut self) {
@@ -15,22 +29,33 @@ impl Display {
1529
while let Some(brace) = read.find('{') {
1630
out += &read[..=brace];
1731
read = &read[brace + 1..];
32+
33+
// skip cases where we find a {{
1834
if read.starts_with('{') {
1935
out.push('{');
2036
read = &read[1..];
2137
continue;
2238
}
23-
let next = match read.chars().next() {
24-
Some(next) => next,
25-
None => return,
26-
};
39+
40+
let next = peek_next!(read);
41+
2742
let var = match next {
2843
'0'..='9' => take_int(&mut read),
2944
'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read),
3045
_ => return,
3146
};
47+
3248
let ident = Ident::new(&var, span);
33-
args.extend(quote_spanned!(span=> , #ident));
49+
50+
let next = peek_next!(read);
51+
52+
let arg = if IS_STD && next == '}' {
53+
quote_spanned!(span=> , (&#ident).get_display())
54+
} else {
55+
quote_spanned!(span=> , #ident)
56+
};
57+
58+
args.extend(arg);
3459
}
3560

3661
out += read;
@@ -71,6 +96,7 @@ fn take_ident(read: &mut &str) -> String {
7196
#[cfg(test)]
7297
mod tests {
7398
use super::*;
99+
use pretty_assertions::assert_eq;
74100
use proc_macro2::Span;
75101

76102
fn assert(input: &str, fmt: &str, args: &str) {
@@ -85,12 +111,20 @@ mod tests {
85111

86112
#[test]
87113
fn test_expand() {
88-
assert("error {var}", "error {}", ", var");
89114
assert("fn main() {{ }}", "fn main() {{ }}", "");
115+
}
116+
117+
#[test]
118+
#[cfg_attr(not(feature = "std"), ignore)]
119+
fn test_std_expand() {
90120
assert(
91121
"{v} {v:?} {0} {0:?}",
92122
"{} {:?} {} {:?}",
93-
", v , v , _0 , _0",
123+
", ( & v ) . get_display ( ) , v , ( & _0 ) . get_display ( ) , _0",
94124
);
125+
assert("error {var}", "error {}", ", ( & var ) . get_display ( )");
126+
127+
// assert("The path {0.display()}", "The path {}", "0.display()");
128+
// assert("The path {0.display():?}", "The path {:?}", "0.display()");
95129
}
96130
}

tests/compile_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
#[allow(unused_attributes)]
12
#[rustversion::attr(not(nightly), ignore)]
3+
#[cfg_attr(feature = "std", ignore)]
24
#[test]
35
fn no_std() {
46
let t = trybuild::TestCases::new();

tests/happy.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use displaydoc::Display;
2+
// use std::path::PathBuf;
23

34
#[derive(Display)]
45
enum Happy {
@@ -17,6 +18,8 @@ enum Happy {
1718
/// Variant5 just has {0} many problems
1819
/// but multi line comments aren't one of them
1920
Variant5(u32),
21+
// /// The path {0.display()}
22+
// Variant6(PathBuf),
2023
}
2124

2225
fn assert_display<T: std::fmt::Display>(input: T, expected: &'static str) {
@@ -34,4 +37,8 @@ fn does_it_print() {
3437
"Variant4 wants to have a lot of lines\n\n Lets see how this works out for it",
3538
);
3639
assert_display(Happy::Variant5(2), "Variant5 just has 2 many problems");
40+
// assert_display(
41+
// Happy::Variant6(PathBuf::from("/var/log/happy")),
42+
// "The path /var/log/happy",
43+
// );
3744
}

0 commit comments

Comments
 (0)