1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module my.filter;
7 
8 import logger = std.experimental.logger;
9 
10 @safe:
11 
12 /** Filter strings by first cutting out a region (include) and then selectively
13  * remove (exclude) from that region.
14  *
15  * I often use this in my programs to allow a user to specify what files to
16  * process and the have some control over what to exclude.
17  *
18  * `--re-include` and `--re-exclude` is a suggestion for parameters to use with
19  * `getopt`.
20  */
21 struct ReFilter {
22     import std.regex : Regex, regex, matchFirst;
23 
24     Regex!char includeRe;
25     Regex!char[] excludeRe;
26 
27     /**
28      * The regular expressions are set to ignoring the case.
29      *
30      * Params:
31      *  include = regular expression.
32      *  exclude = regular expression.
33      */
34     this(string include, string[] exclude) {
35         includeRe = regex(include, "i");
36         foreach (r; exclude)
37             excludeRe ~= regex(r, "i");
38     }
39 
40     /**
41      * Returns: true if `s` matches `ìncludeRe` and NOT matches any of `excludeRe`.
42      */
43     bool match(string s) {
44         if (matchFirst(s, includeRe).empty)
45             return false;
46 
47         foreach (ref re; excludeRe) {
48             if (!matchFirst(s, re).empty)
49                 return false;
50         }
51 
52         return true;
53     }
54 }
55 
56 /// Example:
57 unittest {
58     import std.algorithm : filter;
59     import std.array : array;
60 
61     auto r = ReFilter("foo.*", [".*bar.*", ".*batman"]);
62 
63     assert(["foo", "foobar", "foo smurf batman", "batman", "fo",
64             "foo mother"].filter!(a => r.match(a)).array == [
65             "foo", "foo mother"
66             ]);
67 }
68 
69 /** Filter strings by first cutting out a region (include) and then selectively
70  * remove (exclude) from that region.
71  *
72  * I often use this in my programs to allow a user to specify what files to
73  * process and the have some control over what to exclude.
74  */
75 struct GlobFilter {
76     string[] include;
77     string[] exclude;
78 
79     /**
80      * The regular expressions are set to ignoring the case.
81      *
82      * Params:
83      *  include = glob string patter
84      *  exclude = glob string patterh
85      */
86     this(string[] include, string[] exclude) {
87         this.include = include;
88         this.exclude = exclude;
89     }
90 
91     /**
92      * Returns: true if `s` matches `ìncludeRe` and NOT matches any of `excludeRe`.
93      */
94     bool match(string s) {
95         import std.algorithm : canFind;
96         import std.path : globMatch;
97 
98         if (!canFind!((a, b) => globMatch(b, a))(include, s)) {
99             debug logger.tracef("%s did not match any of %s", s, include);
100             return false;
101         }
102 
103         if (canFind!((a, b) => globMatch(b, a))(exclude, s)) {
104             debug logger.tracef("%s did match one of %s", s, exclude);
105             return false;
106         }
107 
108         return true;
109     }
110 }
111 
112 /// Example:
113 unittest {
114     import std.algorithm : filter;
115     import std.array : array;
116 
117     auto r = GlobFilter(["foo*"], ["*bar*", "*batman"]);
118 
119     assert(["foo", "foobar", "foo smurf batman", "batman", "fo",
120             "foo mother"].filter!(a => r.match(a)).array == [
121             "foo", "foo mother"
122             ]);
123 }