chiark / gitweb /
This commit was manufactured by cvs2svn to create branch 'straylight'.
[ssr] / StraySrc / Utilities / c / glob
1 /*
2  * glob.c
3  *
4  * Full file wildcard matching
5  *
6  * © 1998 Straylight/Edgeware
7  */
8
9 /*----- Licensing note ----------------------------------------------------*
10  *
11  * This file is part of Straylight's core utilities (coreutils).
12  *
13  * Coreutils is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2, or (at your option)
16  * any later version.
17  *
18  * Coreutils is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with coreutils.  If not, write to the Free Software Foundation,
25  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include <ctype.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include "swis.h"
36 #include "swiv.h"
37
38 #include "alloc.h"
39 #include "gf.h"
40 #include "glob.h"
41
42 /*----- Type definitions --------------------------------------------------*/
43
44 typedef struct glob_ctx {
45   char buf[1024];                       /* Pointer to output buffer */
46   void (*proc)(const char *, void *);   /* User procedure */
47   void *ctx;                            /* Context to pass the procedure */
48   int done;                             /* Number of matches found */
49 } glob_ctx;
50
51 /*----- Main code ---------------------------------------------------------*/
52
53 /* --- Wildcard syntax --- *
54  *
55  * The wildcards `*' and `#' do what they normally do: match zero-or-more
56  * and any-one characters respectively.  Additionally, a `.' where a
57  * filename is expected will search recursively down the directory
58  * structure.  Only globby matches with really existing things are counted;
59  * if a name contains nothing globbable then it matches nothing.  This is
60  * done for speed: I expect the client to use a name literally if it fails
61  * to match anything.  The syntax is a sort of blend between traditional
62  * RISC OS and kpathsea.
63  */
64
65 /* --- Hack note --- *
66  *
67  * This will fail gloriously given something like `adfs::ak*ha.$.foo.*'.
68  * Do I look like I care?  This program has already taken five times longer
69  * than it should have done because I decided I wanted to do wildcarding.
70  */
71
72 /* --- Forward reference --- */
73
74 static void glob_do(glob_ctx *g, char *p, char *in);
75
76 /* --- @glob_got@ --- *
77  *
78  * Arguments:   @glob_ctx *g@ = pointer to my context
79  *              @char *p@ = pointer to current position in buffer
80  *              @char *fn@ = filename to add
81  *              @char *in@ = rest of wildcard pattern to match
82  *
83  * Returns:     ---
84  *
85  * Use:         Handles a matched filename in the glob matcher.
86  */
87
88 static void glob_got(glob_ctx *g, char *p, char *fn, char *in)
89 {
90   size_t sz;
91
92   /* --- Build the filename --- */
93
94   if (p != g->buf)
95     *p++ = '.';
96   sz = strlen(fn);
97   memcpy(p, fn, sz + 1);
98   p += sz;
99
100   /* --- See if this is the final element --- */
101
102   if (!in) {
103     int ty;
104
105     if (_swix(OS_File, _inr(0, 1) | _out(0), 17, g->buf, &ty) || !ty)
106       return;
107     g->done++;
108     g->proc(g->buf, g->ctx);
109   } else
110     glob_do(g, p, in);
111 }
112
113 /* --- @glob_match@ --- *
114  *
115  * Arguments:   @const char *pat@ = pointer to pattern string
116  *              @const char *s@ = pointer to candidate string
117  *
118  * Returns:     Nonzero if pattern matches candidate.
119  *
120  * Use:         Tries to match globbily.  This is very simple stuff.
121  *              I may add character classes later if I feel really eager.
122  */
123
124 static int glob_match(const char *pat, const char *s)
125 {
126   for (;;) {
127     if (!*pat && !*s)
128       return (1);
129     else if (!*pat)
130       return (0);
131     else if (*pat == '*') {
132       do pat++; while (*pat == '*');
133       do {
134         if (glob_match(pat, s))
135           return (1);
136       } while (*s++);
137       return (0);
138     } else if (!*s)
139       return (0);
140     else if (*pat != '#' && tolower(*pat) != tolower(*s))
141       return (0);
142     else
143       pat++, s++;
144   }
145 }
146
147 /* --- @glob_do@ --- *
148  *
149  * Arguments:   @glob_ctx *g@ = pointer to my context
150  *              @char *p@ = pointer to current position in buffer
151  *              @char *in@ = rest of wildcard pattern to match
152  *
153  * Returns:     ---
154  *
155  * Use:         Main recursive glob matcher.
156  */
157
158 static void glob_do(glob_ctx *g, char *p, char *in)
159 {
160   char *q;
161   char *rec = 0;
162
163   /* --- Pick out the next component of the pathname --- */
164
165   if (*in == '.') {
166     do in++; while (*in == '.');
167     rec = in - 1;
168     if (!*in) in = "*";
169   }
170
171   q = strchr(in, '.');
172   if (q)
173     *q++ = 0;
174   else
175     q = 0;
176
177   /* --- See if this contains any wildcards --- */
178
179   if (rec) {
180     gf_ctx gx;
181     char *n;
182     char *qq = q ? q - 1 : 0;
183     for (gf_init(&gx, "*", g->buf); *p = 0, (n = gf_next(&gx)) != 0; ) {
184       if (qq) *qq = 0;
185       if (glob_match(in, n))
186         glob_got(g, p, n, q);
187       if (qq) *qq = '.';
188       glob_got(g, p, n, rec);
189     }
190   } else if (strpbrk(in, "*#")) {
191     gf_ctx gx;
192     char *n;
193     for (gf_init(&gx, in, g->buf); *p = 0, (n = gf_next(&gx)) != 0; )
194       glob_got(g, p, n, q);
195   } else
196     glob_got(g, p, in, q);
197 }
198
199 /* --- @glob@ --- *
200  *
201  * Arguments:   @const char *pat@ = pointer to pattern string to match
202  *              @void (*proc)(const char *, void *)@ = client function
203  *              @void *ctx@ = context pointer to pass function
204  *
205  * Returns:     Number of filenames matched.
206  *
207  * Use:         Does filename globbing.
208  */
209
210 int glob(const char *pat, void (*proc)(const char *, void *), void *ctx)
211 {
212   glob_ctx gx;
213   char *in;
214
215   if (!strpbrk(pat, "*#") && !strstr(pat, ".."))
216     return (0);
217
218   gx.proc = proc;
219   gx.ctx = ctx;
220   gx.done = 0;
221   in = xstrdup(pat);
222   glob_do(&gx, gx.buf, in);
223   free(in);
224   return (gx.done);
225 }
226
227 /*----- That's all, folks -------------------------------------------------*/