1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*!
This crate provides a `FromStr` derive for newtypes on iterable collections on single types, such `Vec<T>` or `HashSet<T>`. The element is parsed by splitting the input along a pattern that you specify though the `#[item_separator]` attribute. The individual items are then parsed using their respective `FromStr` implementations.

# Requirements
- The macro will take the first field in your struct that has a type with a path argument `<T>` and assume that it is the inner type. It will assume that `T` in this example is the item type.
- The item type `T` must not be an actual type (no trait type) that implements `FromStr`.
- As inner type you can use any type that implements `FromIterator<Item = T>`  (like `Vec<T>` or `HashSet<T>`).
- `From` needs to be implemented between your newtype and its inner type. Crates like `derive_more` take easy care of that.

# Example
```rust
use collections_fromstr::FromStr;
use derive_more::From;
use std::str::FromStr;

#[derive(From, FromStr, PartialEq)]
#[item_separator = ","]
struct NewVec(Vec<i32>);

static VALUES: &str = "1,2,3,-3,-2,-1";
assert!(NewVec::from_str(VALUES).unwrap() == NewVec(vec![1,2,3,-3,-2,-1]));
```

## Wait... you know that I could just use `Iterator::split` for this, right?

Okay, hear me out. Say you've got data like this:
```text
1-3:B,I,U//43-60:I//83-87:I,U//99-104: B,I// [etc.]
```
Let's say this data represents text markup: You've got character ranges on the left of the colon `:`, and highlighting information on the right side (`B` = bold, `I` = italics, `U` = underline, but you'll probably expand it later once you get that hedgefund money), of which there might be multiple, separated by commas `,`. Each markup is separated by double slashes `//`.

... Now look at the magic of `FromStr` doing its thing:
```rust
use std::collections::HashSet;
use derive_more::From;
use std::ops::Range;

#[derive(parse_display::FromStr)]
#[display("{0.start}-{0.end}")]
struct CharRange(#[from_str(default)] Range<u32>);

#[derive(parse_display::FromStr, Hash, PartialEq, Eq)]
#[non_exhaustive]
enum MarkupOperation {
    #[display("B")]
    Bold,
    #[display("I")]
    Italics, 
    #[display("U")]
    Underline,
}

#[derive(From, collections_fromstr::FromStr)]
#[item_separator=","]
struct Operations(HashSet<MarkupOperation>);

#[derive(parse_display::FromStr)]
#[display("{range}:{operations}")]
struct Markup{
    range: CharRange,
    operations: Operations,
}

#[derive(From, collections_fromstr::FromStr)]
#[item_separator="//"]
struct MarkupVec(Vec<Markup>);
```

Look at this code.
It's. So. Clean. 🥺 And you'll be less likely to produce bugs, like forgetting about the case of an empty input string.


# License

`collections-fromstr` is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See `LICENSE-APACHE` and `LICENSE-MIT` for details.
*/

#![deny(
    deprecated_in_future,
    exported_private_dependencies,
    future_incompatible,
    missing_copy_implementations,
    missing_crate_level_docs,
    missing_debug_implementations,
    missing_docs,
    private_in_public,
    rust_2018_compatibility,
    rust_2018_idioms,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unused_qualifications,
    trivial_casts,
    trivial_numeric_casts,
    unused_crate_dependencies,
    unused_lifetimes,
    variant_size_differences
)]
#![warn(clippy::pedantic)]

use proc_macro::TokenStream as TokenStream1;

mod derive_from_str;


#[proc_macro_derive(FromStr, attributes(item_separator))]
/// Derives `FromStr` for collections. The input will be split along a separator that is specified in a helper attribute like `#[item_separator=","]`. If the separator is a single character, it will be internally transformed into a `char`.
pub fn derive_from_str(input: TokenStream1) -> TokenStream1 {
    derive_from_str::derive_from_str_inner(input)
}


#[cfg(test)]
mod tests {
    use parse_display as _; // used in doctests

    #[test]
    fn compile_tests(){
        use derive_more as _;
        use assert2 as _;
        use maplit as _;

        let t = trybuild::TestCases::new();
        t.pass("trybuild_tests/01-newtype-vec.rs");
        t.compile_fail("trybuild_tests/02-from-trait-missing.rs");
        t.pass("trybuild_tests/03-derive-more.rs");
        t.pass("trybuild_tests/04-named-newtype.rs");
        t.pass("trybuild_tests/05-full-paths.rs");
        t.pass("trybuild_tests/06-hashset.rs");
        t.compile_fail("trybuild_tests/07-hashmap.rs");
        t.compile_fail("trybuild_tests/08-missing-separator.rs");
        t.compile_fail("trybuild_tests/09-empty-separator.rs");
        t.pass("trybuild_tests/10-parse-vec-i32.rs");
        t.pass("trybuild_tests/11-multibyte-separator.rs");
        t.pass("trybuild_tests/12-string-separator.rs");
        t.pass("trybuild_tests/13-parse-vec-string.rs");
        t.pass("trybuild_tests/14-parse-hashset.rs");
        t.pass("trybuild_tests/15-parse-single-value.rs");
        t.pass("trybuild_tests/16-parse-empty.rs");
    }
}