2 * Copyright (C) 2017 - Philippe Proulx <pproulx@efficios.com>
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 #include "string-utils.h"
31 enum star_glob_pattern_type_flags
{
32 STAR_GLOB_PATTERN_TYPE_FLAG_NONE
= 0,
33 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN
= 1,
34 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY
= 2,
38 enum star_glob_pattern_type_flags
strutils_test_glob_pattern(const char *pattern
)
40 enum star_glob_pattern_type_flags ret
=
41 STAR_GLOB_PATTERN_TYPE_FLAG_NONE
;
46 for (p
= pattern
; *p
!= '\0'; p
++) {
49 ret
= STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN
;
52 ret
|= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY
;
73 * Returns true if `pattern` is a star-only globbing pattern, that is,
74 * it contains at least one non-escaped `*`.
76 bool strutils_is_star_glob_pattern(const char *pattern
)
78 return strutils_test_glob_pattern(pattern
) &
79 STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN
;
83 * Returns true if `pattern` is a globbing pattern with a globbing,
84 * non-escaped star only at its very end.
86 bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern
)
88 return strutils_test_glob_pattern(pattern
) &
89 STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY
;
93 bool at_end_of_pattern(const char *p
, const char *pattern
, size_t pattern_len
)
95 return (p
- pattern
) == pattern_len
|| *p
== '\0';
99 * Globbing matching function with the star feature only (`?` and
100 * character sets are not supported). This matches `candidate` (plain
101 * string) against `pattern`. A literal star can be escaped with `\` in
104 * `pattern_len` or `candidate_len` can be greater than the actual
105 * string length of `pattern` or `candidate` if the string is
108 bool strutils_star_glob_match(const char *pattern
, size_t pattern_len
,
109 const char *candidate
, size_t candidate_len
) {
110 const char *retry_c
= candidate
, *retry_p
= pattern
, *c
, *p
;
111 bool got_a_star
= false;
118 * The concept here is to retry a match in the specific case
119 * where we already got a star. The retry position for the
120 * pattern is just after the most recent star, and the retry
121 * position for the candidate is the character following the
122 * last try's first character.
126 * candidate: hi ev every onyx one
128 * pattern: hi*every*one
131 * candidate: hi ev every onyx one
133 * pattern: hi*every*one
136 * candidate: hi ev every onyx one
138 * pattern: hi*every*one
141 * candidate: hi ev every onyx one
143 * pattern: hi*every*one
146 * candidate: hi ev every onyx one
148 * pattern: hi*every*one
151 * candidate: hi ev every onyx one
153 * pattern: hi*every*one
156 * candidate: hi ev every onyx one
158 * pattern: hi*every*one
161 * candidate: hi ev every onyx one
163 * pattern: hi*every*one
166 * candidate: hi ev every onyx one
168 * pattern: hi*every*one
171 * candidate: hi ev every onyx one
173 * pattern: hi*every*one
176 * candidate: hi ev every onyx one
178 * pattern: hi*every*one
181 * candidate: hi ev every onyx one
183 * pattern: hi*every*one
186 * candidate: hi ev every onyx one
188 * pattern: hi*every*one
191 * candidate: hi ev every onyx one
193 * pattern: hi*every*one
196 * candidate: hi ev every onyx one
198 * pattern: hi*every*one
201 * candidate: hi ev every onyx one
203 * pattern: hi*every*one
206 * candidate: hi ev every onyx one
208 * pattern: hi*every*one
211 * candidate: hi ev every onyx one
213 * pattern: hi*every*one
216 * candidate: hi ev every onyx one
218 * pattern: hi*every*one
221 * candidate: hi ev every onyx one
223 * pattern: hi*every*one
226 * candidate: hi ev every onyx one
228 * pattern: hi*every*one
231 * candidate: hi ev every onyx one
233 * pattern: hi*every*one
236 * candidate: hi ev every onyx one
238 * pattern: hi*every*one
241 * candidate: hi ev every onyx one
243 * pattern: hi*every*one
246 * candidate: hi ev every onyx one
248 * pattern: hi*every*one
251 * candidate: hi ev every onyx one
253 * pattern: hi*every*one
256 * candidate: hi ev every onyx one
258 * pattern: hi*every*one
261 while ((c
- candidate
) < candidate_len
&& *c
!= '\0') {
264 if (at_end_of_pattern(p
, pattern
, pattern_len
)) {
273 * Our first try starts at the current candidate
274 * character and after the star in the pattern.
279 if (at_end_of_pattern(retry_p
, pattern
, pattern_len
)) {
281 * Star at the end of the pattern at
282 * this point: automatic match.
289 /* Go to escaped character. */
293 * Fall through the default case which will
294 * compare the escaped character now.
297 if (at_end_of_pattern(p
, pattern
, pattern_len
) ||
300 /* Character mismatch OR end of pattern. */
303 * We didn't get any star yet,
304 * so this first mismatch
305 * automatically makes the whole
312 * Next try: next candidate character,
313 * original pattern character (following
314 * the most recent star).
322 /* Next pattern and candidate characters. */
328 * We checked every candidate character and we're still in a
329 * success state: the only pattern character allowed to remain
332 if (at_end_of_pattern(p
, pattern
, pattern_len
)) {
337 return p
[-1] == '*' && at_end_of_pattern(p
, pattern
, pattern_len
);