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
// License: see LICENSE file at root directory of main branch

//! # Glob set

use alloc::borrow::Cow;

#[cfg(not(feature="std"))]
use alloc::vec::Vec;

#[cfg(feature="std")]
use std::collections::HashSet;

use crate::Glob;

/// # Glob set
///
/// This struct contains a set of [`Glob`][::Glob]'s.
///
/// ## Notes
///
/// For `#![no_std]`: this struct uses a [vector][r:alloc:Vec] inside. Since at the time of implementation, [`HashSet`][r:std:HashSet] was not
/// available.
///
/// [::Glob]: struct.Glob.html
/// [r:alloc:Vec]: https://doc.rust-lang.org/alloc/vec/struct.Vec.html
/// [r:std:HashSet]: https://doc.rust-lang.org/std/collections/struct.HashSet.html
#[derive(Debug, Eq, PartialEq)]
pub struct GlobSet<'a> {

    // We can't use BTreeSet, because it requires Ord/PartialOrd. Implementing them for Glob makes no sense.
    #[cfg(not(feature="std"))]
    globs: Vec<Glob<'a>>,

    #[cfg(feature="std")]
    globs: HashSet<Glob<'a>>,

}

impl<'a> GlobSet<'a> {

    /// # Makes new instance from an iterator of [`Cow<'a, str>`][r::Cow]
    ///
    /// ## Notes
    ///
    /// - Empty strings will be ignored.
    /// - If the iterator contains no globs, `None` is returned.
    /// - For `#![no_std]` users: this function takes little effort to remove duplicates. You should help with that yourself (by not providing
    ///   duplicates).
    ///
    /// ## Examples
    ///
    /// ```
    /// use sub_strs::GlobSet;
    ///
    /// let glob_set = GlobSet::from_iter("*.rs|*.md".split('|').map(|s| s.into())).unwrap();
    /// assert!(glob_set.any("this.rs"));
    /// assert!(glob_set.any("that.md"));
    /// assert!(glob_set.any("not-this") == false);
    /// ```
    ///
    /// [r::Cow]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
    pub fn from_iter<I>(strs: I) -> Option<Self> where I: Iterator<Item=Cow<'a, str>> {
        #[cfg(feature="std")]
        let globs: HashSet<_> = strs.filter_map(|s| match s.is_empty() {
            true => None,
            false => Some(Glob::from(s)),
        }).collect();

        #[cfg(not(feature="std"))]
        let mut globs: Vec<_> = strs.filter_map(|s| match s.is_empty() {
            true => None,
            false => Some(Glob::from(s)),
        }).collect();
        #[cfg(not(feature="std"))]
        globs.dedup();

        match globs.is_empty() {
            true => None,
            false => Some(Self { globs })
        }
    }

    /// # Merges an iterator of [`GlobSet<'a>`][::GlobSet] into one
    ///
    /// ## Notes
    ///
    /// - If there are no inner [`Glob`][::Glob]'s, `None` is returned.
    /// - For `#![no_std]` users: this function takes little effort to remove duplicates. You should help with that yourself (by not providing
    ///   duplicates).
    ///
    /// ## Examples
    ///
    /// ```
    /// use sub_strs::GlobSet;
    ///
    /// let program_args = &["*.svg,*.OGG", "*.md"];
    /// let glob_set = GlobSet::merge(
    ///     program_args.iter().filter_map(|s|
    ///         GlobSet::from_iter(s.to_lowercase().split(',').map(|s| s.to_string().into()))
    ///     )
    /// );
    /// assert!(glob_set.map(|g| g.any("some.ogg")).unwrap_or(false));
    /// ```
    ///
    /// [::Glob]: struct.Glob.html
    /// [::GlobSet]: struct.GlobSet.html
    pub fn merge<I>(iter: I) -> Option<Self> where I: Iterator<Item=Self> {
        #[cfg(feature="std")]
        let result = iter.fold(Self { globs: HashSet::new() }, |mut result, item| {
            result.globs.extend(item.globs);
            result
        });

        #[cfg(not(feature="std"))]
        let mut result = iter.fold(Self { globs: Vec::new() }, |mut result, item| {
            result.globs.extend(item.globs);
            result
        });
        #[cfg(not(feature="std"))]
        result.globs.dedup();

        match result.globs.is_empty() {
            true => None,
            false => Some(result),
        }
    }

    /// # Checks if _any_ glob inside matches the input string
    pub fn any<S>(&self, s: S) -> bool where S: AsRef<str> {
        self.globs.iter().any(|g| g.matches(&s))
    }

}