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 std.algorithm : filter; 9 import std.array : array, empty; 10 11 import logger = std.experimental.logger; 12 13 @safe: 14 15 /** Filter strings by first cutting out regions (include) and then selectively 16 * remove (exclude) from region. 17 * 18 * It assumes that if `include` is empty everything should match. 19 * 20 * I often use this in my programs to allow a user to specify what files to 21 * process and have some control over what to exclude. 22 * 23 * `--re-include` and `--re-exclude` is a suggestion for parameters to use with 24 * `getopt`. 25 */ 26 struct ReFilter { 27 import std.regex : Regex, regex, matchFirst; 28 29 Regex!char[] includeRe; 30 Regex!char[] excludeRe; 31 32 /** 33 * The regular expressions are set to ignoring the case. 34 * 35 * Params: 36 * include = regular expression. 37 * exclude = regular expression. 38 */ 39 this(string[] include, string[] exclude) { 40 foreach (r; include) 41 includeRe ~= regex(r, "i"); 42 foreach (r; exclude) 43 excludeRe ~= regex(r, "i"); 44 } 45 46 /** 47 * Returns: true if `s` matches `ìncludeRe` and NOT matches any of `excludeRe`. 48 */ 49 bool match(string s, void delegate(string s, string type) @safe logFailed = null) { 50 const inclPassed = () { 51 if (includeRe.empty) 52 return true; 53 foreach (ref re; includeRe) { 54 if (!matchFirst(s, re).empty) 55 return true; 56 } 57 return false; 58 }(); 59 if (!inclPassed) { 60 if (logFailed !is null) 61 logFailed(s, "include"); 62 return false; 63 } 64 65 foreach (ref re; excludeRe) { 66 if (!matchFirst(s, re).empty) { 67 if (logFailed !is null) 68 logFailed(s, "exclude"); 69 return false; 70 } 71 } 72 73 return true; 74 } 75 } 76 77 /// Example: 78 unittest { 79 auto r = ReFilter(["foo.*"], [".*bar.*", ".*batman"]); 80 assert(["foo", "foobar", "foo smurf batman", "batman", "fo", 81 "foo mother"].filter!(a => r.match(a)).array == [ 82 "foo", "foo mother" 83 ]); 84 } 85 86 @("shall match everything by default") 87 unittest { 88 ReFilter r; 89 assert(["foo", "foobar"].filter!(a => r.match(a)).array == ["foo", "foobar"]); 90 } 91 92 @("shall exclude the specified items") 93 unittest { 94 auto r = ReFilter(null, [".*bar.*", ".*batman"]); 95 assert(["foo", "foobar", "foo smurf batman", "batman", "fo", 96 "foo mother"].filter!(a => r.match(a)).array == [ 97 "foo", "fo", "foo mother" 98 ]); 99 } 100 101 /** Filter strings by first cutting out a region (include) and then selectively 102 * remove (exclude) from that region. 103 * 104 * I often use this in my programs to allow a user to specify what files to 105 * process and the have some control over what to exclude. 106 */ 107 struct GlobFilter { 108 string[] include; 109 string[] exclude; 110 111 /** 112 * The regular expressions are set to ignoring the case. 113 * 114 * Params: 115 * include = glob string patter 116 * exclude = glob string patterh 117 */ 118 this(string[] include, string[] exclude) { 119 this.include = include; 120 this.exclude = exclude; 121 } 122 123 /** 124 * Params: 125 * logFailed = called when `s` fails matching. 126 * 127 * Returns: true if `s` matches `ìncludeRe` and NOT matches any of `excludeRe`. 128 */ 129 bool match(string s, void delegate(string s, string[] filters) @safe logFailed = null) { 130 import std.algorithm : canFind; 131 import std.path : globMatch; 132 133 if (!include.empty && !canFind!((a, b) => globMatch(b, a))(include, s)) { 134 if (logFailed !is null) 135 logFailed(s, include); 136 return false; 137 } 138 139 if (canFind!((a, b) => globMatch(b, a))(exclude, s)) { 140 if (logFailed !is null) 141 logFailed(s, exclude); 142 return false; 143 } 144 145 return true; 146 } 147 } 148 149 /// Example: 150 unittest { 151 import std.algorithm : filter; 152 import std.array : array; 153 154 auto r = GlobFilter(["foo*"], ["*bar*", "*batman"]); 155 156 assert(["foo", "foobar", "foo smurf batman", "batman", "fo", 157 "foo mother"].filter!(a => r.match(a)).array == [ 158 "foo", "foo mother" 159 ]); 160 }