|
1
|
/* |
|
2
|
** Copyright (c) 2011 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
|
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains code used to pattern matching using "glob" syntax. |
|
19
|
*/ |
|
20
|
#include "config.h" |
|
21
|
#include "glob.h" |
|
22
|
#include <assert.h> |
|
23
|
|
|
24
|
/* |
|
25
|
** Construct and return a string which is an SQL expression that will |
|
26
|
** be TRUE if value zVal matches any of the GLOB expressions in the list |
|
27
|
** zGlobList. For example: |
|
28
|
** |
|
29
|
** zVal: "x" |
|
30
|
** zGlobList: "*.o,*.obj" |
|
31
|
** |
|
32
|
** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" |
|
33
|
** |
|
34
|
** Commas and whitespace are considered to be element delimiters. Each |
|
35
|
** element of the GLOB list may optionally be enclosed in either '...' or |
|
36
|
** "...". This allows commas and/or whitespace to be used in the elements |
|
37
|
** themselves. |
|
38
|
** |
|
39
|
** The returned string is owned by the caller, who must fossil_free() |
|
40
|
** it. |
|
41
|
*/ |
|
42
|
char *glob_expr(const char *zVal, const char *zGlobList){ |
|
43
|
Blob expr; |
|
44
|
const char *zSep = "("; |
|
45
|
int nTerm = 0; |
|
46
|
int i; |
|
47
|
int cTerm; |
|
48
|
|
|
49
|
if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0"); |
|
50
|
blob_zero(&expr); |
|
51
|
while( zGlobList[0] ){ |
|
52
|
while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ |
|
53
|
zGlobList++; /* Skip leading commas, spaces, and newlines */ |
|
54
|
} |
|
55
|
if( zGlobList[0]==0 ) break; |
|
56
|
if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ |
|
57
|
cTerm = zGlobList[0]; |
|
58
|
zGlobList++; |
|
59
|
}else{ |
|
60
|
cTerm = ','; |
|
61
|
} |
|
62
|
/* Find the next delimiter (or the end of the string). */ |
|
63
|
for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){ |
|
64
|
if( cTerm!=',' ) continue; /* If quoted, keep going. */ |
|
65
|
if( fossil_isspace(zGlobList[i]) ) break; /* If space, stop. */ |
|
66
|
} |
|
67
|
blob_appendf(&expr, "%s%s GLOB '%#q'", zSep, zVal, i, zGlobList); |
|
68
|
zSep = " OR "; |
|
69
|
if( cTerm!=',' && zGlobList[i] ) i++; |
|
70
|
zGlobList += i; |
|
71
|
if( zGlobList[0] ) zGlobList++; |
|
72
|
nTerm++; |
|
73
|
} |
|
74
|
if( nTerm ){ |
|
75
|
blob_appendf(&expr, ")"); |
|
76
|
return blob_str(&expr); |
|
77
|
}else{ |
|
78
|
return fossil_strdup("0"); |
|
79
|
} |
|
80
|
} |
|
81
|
|
|
82
|
#if INTERFACE |
|
83
|
/* |
|
84
|
** A Glob object holds a set of patterns read to be matched against |
|
85
|
** a string. |
|
86
|
*/ |
|
87
|
struct Glob { |
|
88
|
int nPattern; /* Number of patterns */ |
|
89
|
char **azPattern; /* Array of pointers to patterns */ |
|
90
|
}; |
|
91
|
#endif /* INTERFACE */ |
|
92
|
|
|
93
|
/* |
|
94
|
** zPatternList is a comma- or whitespace-separated list of glob patterns. |
|
95
|
** Parse that list and use it to create a new Glob object. |
|
96
|
** |
|
97
|
** Elements of the glob list may be optionally enclosed in single- or |
|
98
|
** double-quotes. This allows commas and whitespace to be part of a |
|
99
|
** glob pattern. |
|
100
|
** |
|
101
|
** Leading and trailing spaces on glob patterns are ignored unless quoted. |
|
102
|
** |
|
103
|
** An empty or null pattern list results in a null glob, which will |
|
104
|
** match nothing. |
|
105
|
*/ |
|
106
|
Glob *glob_create(const char *zPatternList){ |
|
107
|
int nList; /* Size of zPatternList in bytes */ |
|
108
|
int i; /* Loop counters */ |
|
109
|
Glob *p; /* The glob being created */ |
|
110
|
char *z; /* Copy of the pattern list */ |
|
111
|
char delimiter; /* '\'' or '\"' or 0 */ |
|
112
|
|
|
113
|
if( zPatternList==0 || zPatternList[0]==0 ) return 0; |
|
114
|
nList = strlen(zPatternList); |
|
115
|
p = fossil_malloc( sizeof(*p) + nList+1 ); |
|
116
|
memset(p, 0, sizeof(*p)); |
|
117
|
z = (char*)&p[1]; |
|
118
|
memcpy(z, zPatternList, nList+1); |
|
119
|
while( z[0] ){ |
|
120
|
while( fossil_isspace(z[0]) || z[0]==',' ){ |
|
121
|
z++; /* Skip leading commas, spaces, and newlines */ |
|
122
|
} |
|
123
|
if( z[0]==0 ) break; |
|
124
|
if( z[0]=='\'' || z[0]=='"' ){ |
|
125
|
delimiter = z[0]; |
|
126
|
z++; |
|
127
|
}else{ |
|
128
|
delimiter = ','; |
|
129
|
} |
|
130
|
p->azPattern = fossil_realloc(p->azPattern, (p->nPattern+1)*sizeof(char*) ); |
|
131
|
p->azPattern[p->nPattern++] = z; |
|
132
|
/* Find the next delimiter (or the end of the string). */ |
|
133
|
for(i=0; z[i] && z[i]!=delimiter && |
|
134
|
!(delimiter==',' && fossil_isspace(z[i])); i++){ |
|
135
|
/* keep looking for the end of the glob pattern */ |
|
136
|
} |
|
137
|
if( z[i]==0 ) break; |
|
138
|
z[i] = 0; |
|
139
|
z += i+1; |
|
140
|
} |
|
141
|
return p; |
|
142
|
} |
|
143
|
|
|
144
|
/* |
|
145
|
** Return TRUE if zString matches any of the GLOB patterns in the |
|
146
|
** string zPatternList. |
|
147
|
** |
|
148
|
** This is a like calling glob_create(), glob_match(), and glob_free() |
|
149
|
** in sequence, without the overhead of creating the reusable Glob object. |
|
150
|
** Use this for one-time matches against a comma-separated GLOB list. |
|
151
|
*/ |
|
152
|
int glob_multi_match(const char *zPatternList, const char *zString){ |
|
153
|
int i; /* Loop counters */ |
|
154
|
int n = 0; /* Pattern counter */ |
|
155
|
const char *z; /* Current GLOB pattern */ |
|
156
|
char delimiter; /* '\'' or '\"' or 0 */ |
|
157
|
int rc; /* Result of comparison */ |
|
158
|
char zPat[100]; /* Copy of just the current pattern */ |
|
159
|
|
|
160
|
if( zPatternList==0 ) return 0; |
|
161
|
z = zPatternList; |
|
162
|
while( z[0] ){ |
|
163
|
while( fossil_isspace(z[0]) || z[0]==',' ){ |
|
164
|
z++; /* Skip leading commas, spaces, and newlines */ |
|
165
|
} |
|
166
|
if( z[0]==0 ) break; |
|
167
|
if( z[0]=='\'' || z[0]=='"' ){ |
|
168
|
delimiter = z[0]; |
|
169
|
z++; |
|
170
|
}else{ |
|
171
|
delimiter = ','; |
|
172
|
} |
|
173
|
/* Find the next delimiter (or the end of the string). */ |
|
174
|
for(i=0; z[i] && z[i]!=delimiter && |
|
175
|
!(delimiter==',' && fossil_isspace(z[i])); i++){ |
|
176
|
/* keep looking for the end of the glob pattern */ |
|
177
|
} |
|
178
|
n++; |
|
179
|
if( i>sizeof(zPat)-1 ){ |
|
180
|
char *zMPat = fossil_strndup(z, i); |
|
181
|
rc = sqlite3_strglob(zMPat, zString); |
|
182
|
fossil_free(zMPat); |
|
183
|
}else{ |
|
184
|
memcpy(zPat, z, i); |
|
185
|
zPat[i] = 0; |
|
186
|
rc = sqlite3_strglob(zPat, zString); |
|
187
|
} |
|
188
|
if( rc==0 ) return n; |
|
189
|
if( z[i]==0 ) break; |
|
190
|
z += i+1; |
|
191
|
} |
|
192
|
return 0; |
|
193
|
} |
|
194
|
|
|
195
|
/* |
|
196
|
** Return true (non-zero) if zString matches any of the patterns in |
|
197
|
** the Glob. The value returned is actually a 1-based index of the pattern |
|
198
|
** that matched. Return 0 if none of the patterns match zString. |
|
199
|
** |
|
200
|
** A NULL glob matches nothing. |
|
201
|
*/ |
|
202
|
int glob_match(Glob *pGlob, const char *zString){ |
|
203
|
int i; |
|
204
|
if( pGlob==0 ) return 0; |
|
205
|
for(i=0; i<pGlob->nPattern; i++){ |
|
206
|
if( sqlite3_strglob(pGlob->azPattern[i], zString)==0 ) return i+1; |
|
207
|
} |
|
208
|
return 0; |
|
209
|
} |
|
210
|
|
|
211
|
/* |
|
212
|
** Free all memory associated with the given Glob object |
|
213
|
*/ |
|
214
|
void glob_free(Glob *pGlob){ |
|
215
|
if( pGlob ){ |
|
216
|
fossil_free(pGlob->azPattern); |
|
217
|
fossil_free(pGlob); |
|
218
|
} |
|
219
|
} |
|
220
|
|
|
221
|
/* |
|
222
|
** Appends the given glob to the given buffer in the form of a |
|
223
|
** JS/JSON-compatible array. It requires that pDest have been |
|
224
|
** initialized. If pGlob is NULL or empty it emits [] (an empty |
|
225
|
** array). |
|
226
|
*/ |
|
227
|
void glob_render_json_to_blob(Glob *pGlob, Blob *pDest){ |
|
228
|
int i = 0; |
|
229
|
blob_append(pDest, "[", 1); |
|
230
|
for( ; pGlob && i < pGlob->nPattern; ++i ){ |
|
231
|
if(i){ |
|
232
|
blob_append(pDest, ",", 1); |
|
233
|
} |
|
234
|
blob_appendf(pDest, "%!j", pGlob->azPattern[i]); |
|
235
|
} |
|
236
|
blob_append(pDest, "]", 1); |
|
237
|
} |
|
238
|
/* |
|
239
|
** Functionally equivalent to glob_render_json_to_blob() |
|
240
|
** but outputs via cgi_print(). |
|
241
|
*/ |
|
242
|
void glob_render_json_to_cgi(Glob *pGlob){ |
|
243
|
int i = 0; |
|
244
|
CX("["); |
|
245
|
for( ; pGlob && i < pGlob->nPattern; ++i ){ |
|
246
|
if(i){ |
|
247
|
CX(","); |
|
248
|
} |
|
249
|
CX("%!j", pGlob->azPattern[i]); |
|
250
|
} |
|
251
|
CX("]"); |
|
252
|
} |
|
253
|
|
|
254
|
/* |
|
255
|
** COMMAND: test-glob |
|
256
|
** |
|
257
|
** Usage: %fossil test-glob PATTERN STRING... |
|
258
|
** |
|
259
|
** PATTERN is a comma- and whitespace-separated list of optionally |
|
260
|
** quoted glob patterns. Show which of the STRINGs that follow match |
|
261
|
** the PATTERN. |
|
262
|
** |
|
263
|
** If PATTERN begins with "@" the rest of the pattern is understood |
|
264
|
** to be a setting name (such as binary-glob, crln-glob, or encoding-glob) |
|
265
|
** and the value of that setting is used as the actually glob pattern. |
|
266
|
** |
|
267
|
** The output consists of two numbers and a STRING. The first number |
|
268
|
** is the result of glob_match() and the second is the result of |
|
269
|
** glob_multi_match(). |
|
270
|
*/ |
|
271
|
void glob_test_cmd(void){ |
|
272
|
Glob *pGlob; |
|
273
|
int i; |
|
274
|
char *zPattern; |
|
275
|
if( g.argc<4 ) usage("PATTERN STRING ..."); |
|
276
|
zPattern = g.argv[2]; |
|
277
|
if( zPattern[0]=='@' ){ |
|
278
|
db_find_and_open_repository(OPEN_ANY_SCHEMA,0); |
|
279
|
zPattern = db_get(zPattern+1, 0); |
|
280
|
if( zPattern==0 ) fossil_fatal("no such setting: %s", g.argv[2]+1); |
|
281
|
fossil_print("GLOB pattern: %s\n", zPattern); |
|
282
|
} |
|
283
|
fossil_print("SQL expression: %s\n", glob_expr("x", zPattern)); |
|
284
|
pGlob = glob_create(zPattern); |
|
285
|
for(i=0; i<pGlob->nPattern; i++){ |
|
286
|
fossil_print("pattern[%d] = [%s]\n", i, pGlob->azPattern[i]); |
|
287
|
} |
|
288
|
for(i=3; i<g.argc; i++){ |
|
289
|
fossil_print("%d %d %s\n", |
|
290
|
glob_match(pGlob, g.argv[i]), |
|
291
|
glob_multi_match(zPattern, g.argv[i]), |
|
292
|
g.argv[i]); |
|
293
|
} |
|
294
|
glob_free(pGlob); |
|
295
|
} |
|
296
|
|