1 /*=============================================================================
2 Copyright (c) 2013 Daniel James
4 Use, modification and distribution is subject to the Boost Software
5 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
6 http://www.boost.org/LICENSE_1_0.txt)
7 =============================================================================*/
14 typedef string_iterator glob_iterator
;
16 void check_glob_range(glob_iterator
&, glob_iterator
);
17 void check_glob_escape(glob_iterator
&, glob_iterator
);
19 bool match_section(glob_iterator
& pattern_begin
, glob_iterator pattern_end
,
20 glob_iterator
& filename_begin
, glob_iterator
& filename_end
);
21 bool match_range(glob_iterator
& pattern_begin
, glob_iterator pattern_end
,
24 // Is pattern a glob or a plain file name?
25 // Throws glob_error if pattern is an invalid glob.
26 bool check_glob(quickbook::string_view pattern
)
31 glob_iterator begin
= pattern
.begin();
32 glob_iterator end
= pattern
.end();
34 while (begin
!= end
) {
35 if (*begin
< 32 || (*begin
& 0x80))
40 check_glob_escape(begin
, end
);
44 check_glob_range(begin
, end
);
49 throw glob_error("uneven square brackets");
60 if (begin
!= end
&& *begin
== '*') {
61 throw glob_error("'**' not supported");
70 if (is_glob
&& !is_ascii
)
71 throw glob_error("invalid character, globs are ascii only");
76 void check_glob_range(glob_iterator
& begin
, glob_iterator end
)
78 assert(begin
!= end
&& *begin
== '[');
82 throw glob_error("empty range");
84 while (begin
!= end
) {
90 throw glob_error("trailing escape");
92 else if (*begin
== '\\' || *begin
== '/') {
93 throw glob_error("contains escaped slash");
99 throw glob_error("nested square brackets");
104 throw glob_error("slash in square brackets");
110 throw glob_error("uneven square brackets");
113 void check_glob_escape(glob_iterator
& begin
, glob_iterator end
)
115 assert(begin
!= end
&& *begin
== '\\');
120 throw glob_error("trailing escape");
122 else if (*begin
== '\\' || *begin
== '/') {
123 throw glob_error("contains escaped slash");
129 // Does filename match pattern?
130 // Might throw glob_error if pattern is an invalid glob,
131 // but should call check_glob first to validate the glob.
132 bool glob(quickbook::string_view
const& pattern
,
133 quickbook::string_view
const& filename
)
135 // If there wasn't this special case then '*' would match an
137 if (filename
.empty()) return pattern
.empty();
139 glob_iterator pattern_it
= pattern
.begin();
140 glob_iterator pattern_end
= pattern
.end();
142 glob_iterator filename_it
= filename
.begin();
143 glob_iterator filename_end
= filename
.end();
145 if (!match_section(pattern_it
, pattern_end
, filename_it
, filename_end
))
148 while (pattern_it
!= pattern_end
) {
149 assert(*pattern_it
== '*');
152 if (pattern_it
== pattern_end
) return true;
154 if (*pattern_it
== '*') { throw glob_error("'**' not supported"); }
157 if (filename_it
== filename_end
) return false;
158 if (match_section(pattern_it
, pattern_end
, filename_it
, filename_end
))
164 return filename_it
== filename_end
;
167 bool match_section(glob_iterator
& pattern_begin
, glob_iterator pattern_end
,
168 glob_iterator
& filename_begin
, glob_iterator
& filename_end
)
170 glob_iterator pattern_it
= pattern_begin
;
171 glob_iterator filename_it
= filename_begin
;
173 while (pattern_it
!= pattern_end
&& *pattern_it
!= '*') {
174 if (filename_it
== filename_end
) return false;
176 switch(*pattern_it
) {
179 throw new glob_error("Internal error");
181 if (!match_range(pattern_it
, pattern_end
, *filename_it
))
186 throw glob_error("uneven square brackets");
193 if (pattern_it
== pattern_end
) {
194 throw glob_error("trailing escape");
195 } else if (*pattern_it
== '\\' || *pattern_it
== '/') {
196 throw glob_error("contains escaped slash");
200 if (*pattern_it
!= *filename_it
) return false;
206 if (pattern_it
== pattern_end
&& filename_it
!= filename_end
)
209 pattern_begin
= pattern_it
;
210 filename_begin
= filename_it
;
214 bool match_range(glob_iterator
& pattern_begin
, glob_iterator pattern_end
,
217 assert(pattern_begin
!= pattern_end
&& *pattern_begin
== '[');
219 if (pattern_begin
== pattern_end
) {
220 throw glob_error("uneven square brackets");
223 bool invert_match
= false;
224 bool matched
= false;
226 if (*pattern_begin
== '^') {
229 if (pattern_begin
== pattern_end
) {
230 throw glob_error("uneven square brackets");
232 } else if (*pattern_begin
== ']') {
233 throw glob_error("empty range");
236 // Search for a match
238 unsigned char first
= *pattern_begin
;
240 if (first
== ']') break;
242 throw glob_error("nested square brackets");
244 if (pattern_begin
== pattern_end
) {
245 throw glob_error("uneven square brackets");
249 first
= *pattern_begin
;
250 if (first
== '\\' || first
== '/') {
251 throw glob_error("contains escaped slash");
254 if (pattern_begin
== pattern_end
) {
255 throw glob_error("uneven square brackets");
257 } else if (first
== '/') {
258 throw glob_error("slash in square brackets");
261 if (*pattern_begin
!= '-') {
262 matched
= matched
|| (first
== x
);
266 if (pattern_begin
== pattern_end
) {
267 throw glob_error("uneven square brackets");
270 unsigned char second
= *pattern_begin
;
273 matched
= matched
|| (first
== x
) || (x
== '-');
276 if (pattern_begin
== pattern_end
) {
277 throw glob_error("uneven square brackets");
280 if (second
== '\\') {
281 second
= *pattern_begin
;
282 if (second
== '\\' || second
== '/') {
283 throw glob_error("contains escaped slash");
286 if (pattern_begin
== pattern_end
) {
287 throw glob_error("uneven square brackets");
289 } else if (second
== '/') {
290 throw glob_error("slash in square brackets");
293 matched
= matched
|| (first
<= x
&& x
<= second
);
297 return invert_match
!= matched
;
300 std::size_t find_glob_char(quickbook::string_view pattern
,
303 // Weird style is because quickbook::string_view's find_first_of
304 // doesn't take a position argument.
305 std::size_t removed
= 0;
308 pos
= pattern
.find_first_of("[]?*\\");
309 if (pos
== quickbook::string_view::npos
) return pos
;
310 if (pattern
[pos
] != '\\') return pos
+ removed
;
311 pattern
.remove_prefix(pos
+ 2);
316 std::string
glob_unescape(quickbook::string_view pattern
)
321 std::size_t pos
= pattern
.find("\\");
322 if (pos
== quickbook::string_view::npos
) {
323 result
.append(pattern
.data(), pattern
.size());
327 result
.append(pattern
.data(), pos
);
329 if (pos
< pattern
.size()) {
330 result
+= pattern
[pos
];
333 pattern
.remove_prefix(pos
);