/[gentoo-projects]/portage-utils/qgrep.c
Gentoo

Contents of /portage-utils/qgrep.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.32 - (show annotations) (download) (as text)
Sun Oct 28 04:16:19 2012 UTC (23 months, 3 weeks ago) by vapier
Branch: MAIN
Changes since 1.31: +7 -7 lines
File MIME type: text/x-csrc
kill off more spurious/useless parens

1 /*
2 * Copyright 2005-2010 Gentoo Foundation
3 * Distributed under the terms of the GNU General Public License v2
4 * $Header: /var/cvsroot/gentoo-projects/portage-utils/qgrep.c,v 1.31 2012/08/13 22:23:35 robbat2 Exp $
5 *
6 * Copyright 2005-2010 Ned Ludd - <solar@gentoo.org>
7 * Copyright 2005-2010 Mike Frysinger - <vapier@gentoo.org>
8 * Copyright 2005 Petteri R├Ąty - <betelgeuse@gentoo.org>
9 */
10
11 #ifdef APPLET_qgrep
12
13 #define QGREP_FLAGS "IiHNclLexJEsS:B:A:" COMMON_FLAGS
14 static struct option const qgrep_long_opts[] = {
15 {"invert-match", no_argument, NULL, 'I'},
16 {"ignore-case", no_argument, NULL, 'i'},
17 {"with-filename", no_argument, NULL, 'H'},
18 {"with-name", no_argument, NULL, 'N'},
19 {"count", no_argument, NULL, 'c'},
20 {"list", no_argument, NULL, 'l'},
21 {"invert-list", no_argument, NULL, 'L'},
22 {"regexp", no_argument, NULL, 'e'},
23 {"extended", no_argument, NULL, 'x'},
24 {"installed", no_argument, NULL, 'J'},
25 {"eclass", no_argument, NULL, 'E'},
26 {"skip-comments", no_argument, NULL, 's'},
27 {"skip", a_argument, NULL, 'S'},
28 {"before", a_argument, NULL, 'B'},
29 {"after", a_argument, NULL, 'A'},
30 COMMON_LONG_OPTS
31 };
32 static const char * const qgrep_opts_help[] = {
33 "Select non-matching lines",
34 "Ignore case distinctions",
35 "Print the filename for each match",
36 "Print the package or eclass name for each match",
37 "Only print a count of matching lines per FILE",
38 "Only print FILE names containing matches",
39 "Only print FILE names containing no match",
40 "Use PATTERN as a regular expression",
41 "Use PATTERN as an extended regular expression",
42 "Search in installed ebuilds instead of the tree",
43 "Search in eclasses instead of ebuilds",
44 "Skip comments lines",
45 "Skip lines matching <arg>",
46 "Print <arg> lines of leading context",
47 "Print <arg> lines of trailing context",
48 COMMON_OPTS_HELP
49 };
50 static const char qgrep_rcsid[] = "$Id: qgrep.c,v 1.31 2012/08/13 22:23:35 robbat2 Exp $";
51 #define qgrep_usage(ret) usage(ret, QGREP_FLAGS, qgrep_long_opts, qgrep_opts_help, lookup_applet_idx("qgrep"))
52
53 char qgrep_name_match(const char*, const int, depend_atom**);
54 char qgrep_name_match(const char* name, const int argc, depend_atom** argv)
55 {
56 depend_atom* atom;
57 int i;
58
59 if ((atom = atom_explode(name)) == NULL)
60 return 0;
61
62 for (i = 0; i < argc; i++) {
63 if (argv[i] == NULL)
64 continue;
65 if (atom->CATEGORY && argv[i]->CATEGORY && *(argv[i]->CATEGORY)
66 && strcmp(atom->CATEGORY, argv[i]->CATEGORY))
67 continue;
68 if (atom->PN && argv[i]->PN && *(argv[i]->PN)
69 && strcmp(atom->PN, argv[i]->PN))
70 continue;
71 if (atom->PVR && argv[i]->PVR && *(argv[i]->PVR)
72 && strcmp(atom->PVR, argv[i]->PVR))
73 continue;
74 atom_implode(atom);
75 return 1;
76 }
77
78 atom_implode(atom);
79 return 0;
80 }
81
82 /* Circular list of line buffers for --before */
83 typedef struct qgrep_buf {
84 char valid;
85 /* 1 when the line should be included in
86 * a leading context, and 0 when it has already
87 * been displayed, or is from a previous file. */
88 char buf[BUFSIZ];
89 struct qgrep_buf *next;
90 } qgrep_buf_t;
91
92 /* Allocate <length> buffers in a circular list.
93 * <length> must be at least 1. */
94 qgrep_buf_t* qgrep_buf_list_alloc(const char);
95 qgrep_buf_t* qgrep_buf_list_alloc(const char length)
96 {
97 char i;
98 qgrep_buf_t *head, *current;
99 current = head = xmalloc(sizeof(qgrep_buf_t));
100 for (i = 1; i < length; i++) {
101 current->next = xmalloc(sizeof(qgrep_buf_t));
102 current = current->next;
103 }
104 current->next = head;
105 return head;
106 }
107
108 /* Free a circular buffers list. */
109 void qgrep_buf_list_free(qgrep_buf_t *);
110 void qgrep_buf_list_free(qgrep_buf_t *head)
111 {
112 qgrep_buf_t *current, *next;
113 next = head;
114 do {
115 current = next;
116 next = current->next;
117 free(current);
118 } while (next != head);
119 }
120
121 /* Set valid=0 in the whole list. */
122 void qgrep_buf_list_invalidate(qgrep_buf_t *);
123 void qgrep_buf_list_invalidate(qgrep_buf_t *head)
124 {
125 qgrep_buf_t *current;
126 current = head;
127 do {
128 current->valid = 0;
129 current = current->next;
130 } while (current != head);
131 }
132
133 /* Type for the str(case)str search functions */
134 typedef char *(*QGREP_STR_FUNC) (const char *, const char *);
135
136 /* Display a buffer, with an optionnal prefix. */
137 void qgrep_print_line(qgrep_buf_t *, const char *, const int, const char,
138 const regex_t*, const QGREP_STR_FUNC, const char*);
139 void qgrep_print_line(qgrep_buf_t *current, const char *label,
140 const int line_number, const char zig, const regex_t* preg,
141 const QGREP_STR_FUNC searchfunc, const char* searchstr)
142 {
143 char *p = current->buf;
144 /* Print line prefix, when in verbose mode */
145 if (label != NULL) {
146 printf("%s", label);
147 if (line_number > 0)
148 printf(":%d", line_number);
149 putchar(zig);
150 }
151 if (preg != NULL) {
152 /* Iteration over regexp matches, for color output.
153 * First regexec is a normal one, and then loop with
154 * REG_NOTBOL to not match "^pattern" anymore. */
155 regmatch_t match;
156 int regexec_flags = 0;
157 while ((*p != '\0') && !regexec(preg, p, 1, &match, regexec_flags)) {
158 if (match.rm_so > 0)
159 printf("%.*s", match.rm_so, p);
160 if (match.rm_eo > match.rm_so) {
161 printf("%s%.*s%s", RED, match.rm_eo - match.rm_so, p + match.rm_so, NORM);
162 p += match.rm_eo;
163 } else {
164 p += match.rm_eo;
165 putchar(*p++);
166 }
167 regexec_flags = REG_NOTBOL;
168 }
169 } else if (searchfunc != NULL && searchstr != NULL) {
170 /* Iteration over substring matches, for color output. */
171 char *q;
172 int searchlen = strlen(searchstr);
173 while (searchlen && ((q = searchfunc(p, searchstr)) != NULL)) {
174 if (p < q)
175 printf("%.*s", (int)(q - p), p);
176 printf("%s%.*s%s", RED, searchlen, q, NORM);
177 p = q + searchlen;
178 }
179 }
180 /* No color output (for context lines, or trailing portion
181 * of matching lines). */
182 printf("%s\n", p);
183 /* Once a line has been displayed, it is not valid anymore */
184 current->valid = 0;
185 }
186 #define qgrep_print_context_line(buf, label, lineno) \
187 qgrep_print_line(buf, label, lineno, '-', NULL, NULL, NULL)
188 #define qgrep_print_matching_line_nocolor(buf, label, lineno) \
189 qgrep_print_line(buf, label, lineno, ':', NULL, NULL, NULL)
190 #define qgrep_print_matching_line_regcolor(buf, label, lineno, preg) \
191 qgrep_print_line(buf, label, lineno, ':', preg, NULL, NULL)
192 #define qgrep_print_matching_line_strcolor(buf, label, lineno, searchfunc, searchstr) \
193 qgrep_print_line(buf, label, lineno, ':', NULL, searchfunc, searchstr)
194
195 /* Display a leading context (valid lines of the buffers list, but the matching one). */
196 void qgrep_print_before_context(qgrep_buf_t *, const char, const char *, const int);
197 void qgrep_print_before_context(qgrep_buf_t *current, const char num_lines_before,
198 const char *label, const int match_line_number)
199 {
200 int line_number;
201 line_number = match_line_number - num_lines_before;
202 while ((current = current->next)
203 && (line_number < match_line_number)) {
204 if (current->valid)
205 qgrep_print_context_line(current, label, line_number);
206 line_number++;
207 }
208 }
209
210 /* Yield the path of one of the installed ebuilds (from VDB). */
211 char *get_next_installed_ebuild(char *, DIR *, struct dirent **, DIR **);
212 char *get_next_installed_ebuild(char *ebuild_path, DIR *vdb_dir, struct dirent **cat_dirent_pt, DIR **cat_dir_pt)
213 {
214 struct dirent *pkg_dirent = NULL;
215 if (*cat_dirent_pt == NULL || *cat_dir_pt == NULL)
216 goto get_next_category;
217 get_next_ebuild_from_category:
218 if ((pkg_dirent = readdir(*cat_dir_pt)) == NULL)
219 goto get_next_category;
220 if (pkg_dirent->d_name[0] == '.')
221 goto get_next_ebuild_from_category;
222 snprintf(ebuild_path, _Q_PATH_MAX, "%s/%s/%s.ebuild",
223 (*cat_dirent_pt)->d_name, pkg_dirent->d_name, pkg_dirent->d_name);
224 return ebuild_path;
225 get_next_category:
226 if (*cat_dir_pt != NULL)
227 closedir(*cat_dir_pt);
228 *cat_dirent_pt = q_vdb_get_next_dir(vdb_dir);
229 if (*cat_dirent_pt == NULL)
230 return NULL;
231 if ((*cat_dir_pt = opendir((*cat_dirent_pt)->d_name)) == NULL)
232 goto get_next_category;
233 goto get_next_ebuild_from_category;
234 }
235
236 int qgrep_main(int argc, char **argv)
237 {
238 int i;
239 int count = 0;
240 char *p;
241 char do_count, do_regex, do_eclass, do_installed, do_list;
242 char show_filename, skip_comments, invert_list, show_name;
243 char per_file_output;
244 FILE *fp = NULL;
245 DIR *eclass_dir = NULL;
246 DIR *vdb_dir = NULL;
247 DIR *cat_dir = NULL;
248 struct dirent *dentry;
249 char ebuild[_Q_PATH_MAX];
250 char name[_Q_PATH_MAX];
251 char *label;
252 int reflags = 0;
253 char invert_match = 0;
254 regex_t preg, skip_preg;
255 char *skip_pattern = NULL;
256 depend_atom** include_atoms = NULL;
257 unsigned long int context_optarg;
258 char num_lines_before = 0;
259 char num_lines_after = 0;
260 qgrep_buf_t *buf_list;
261 int need_separator = 0;
262 char status = 1;
263
264 QGREP_STR_FUNC strfunc = (QGREP_STR_FUNC) strstr;
265
266 DBG("argc=%d argv[0]=%s argv[1]=%s",
267 argc, argv[0], argc > 1 ? argv[1] : "NULL?");
268
269 do_count = do_regex = do_eclass = do_installed = do_list = 0;
270 show_filename = skip_comments = invert_list = show_name = 0;
271
272 while ((i = GETOPT_LONG(QGREP, qgrep, "")) != -1) {
273 switch (i) {
274 case 'I': invert_match = 1; break;
275 case 'i':
276 strfunc = (QGREP_STR_FUNC) strcasestr;
277 reflags |= REG_ICASE;
278 break;
279 case 'c': do_count = 1; break;
280 case 'l': do_list = 1; break;
281 case 'L': do_list = invert_list = 1; break;
282 case 'e': do_regex = 1; break;
283 case 'x':
284 do_regex = 1;
285 reflags |= REG_EXTENDED;
286 break;
287 case 'J': do_installed = 1; break;
288 case 'E': do_eclass = 1; break;
289 case 'H': show_filename = 1; break;
290 case 'N': show_name = 1; break;
291 case 's': skip_comments = 1; break;
292 case 'S': skip_pattern = optarg; break;
293 case 'B':
294 case 'A':
295 errno = 0;
296 context_optarg = strtol(optarg, &p, 10);
297 if (errno != 0)
298 errp("%s: not a valid integer", optarg);
299 else if (p == optarg || *p != '\0')
300 err("%s: not a valid integer", optarg);
301 if (context_optarg > 254)
302 err("%s: silly value!", optarg);
303 if (i == 'B')
304 num_lines_before = context_optarg;
305 else
306 num_lines_after = context_optarg;
307 break;
308 COMMON_GETOPTS_CASES(qgrep)
309 }
310 }
311 if (argc == optind)
312 qgrep_usage(EXIT_FAILURE);
313
314 if (do_list && do_count) {
315 warn("%s and --count are incompatible options. The former wins.",
316 (invert_list ? "--invert-list" : "--list"));
317 do_count = 0;
318 }
319
320 if (show_name && show_filename) {
321 warn("--with-name and --with-filename are incompatible options. The former wins.");
322 show_filename = 0;
323 }
324
325 if (do_list && num_lines_before) {
326 warn("%s and --before are incompatible options. The former wins.",
327 (invert_list ? "--invert-list" : "--list"));
328 num_lines_before = 0;
329 }
330
331 if (do_list && num_lines_after) {
332 warn("%s and --after are incompatible options. The former wins.",
333 (invert_list ? "--invert-list" : "--list"));
334 num_lines_after = 0;
335 }
336
337 if (do_count && num_lines_before) {
338 warn("--count and --before are incompatible options. The former wins.");
339 num_lines_before = 0;
340 }
341
342 if (do_count && num_lines_after) {
343 warn("--count and --after are incompatible options. The former wins.");
344 num_lines_after = 0;
345 }
346
347 if (do_installed && do_eclass) {
348 warn("--installed and --eclass are incompatible options. The former wins.");
349 do_eclass = 0;
350 }
351
352 /* do we report results once per file or per line ? */
353 per_file_output = do_count || (do_list && (!verbose || invert_list));
354 /* label for prefixing matching lines or listing matching files */
355 label = (show_name ? name : ((verbose || show_filename || do_list) ? ebuild : NULL));
356
357 if (argc > (optind + 1)) {
358 include_atoms = xcalloc(sizeof(depend_atom*), (argc - optind - 1));
359 for (i = (optind + 1); i < argc; i++)
360 if ((include_atoms[i - optind - 1] = atom_explode(argv[i])) == NULL)
361 warn("%s: invalid atom, will be ignored", argv[i]);
362 }
363
364 /* pre-compile regexps once for all */
365 if (do_regex) {
366 if (invert_match || *RED == '\0')
367 reflags |= REG_NOSUB;
368 xregcomp(&preg, argv[optind], reflags);
369 reflags |= REG_NOSUB;
370 if (skip_pattern)
371 xregcomp(&skip_preg, skip_pattern, reflags);
372 }
373
374 /* go look either in ebuilds or eclasses or VDB */
375 if (!do_eclass && !do_installed) {
376 initialize_ebuild_flat(); /* sets our pwd to $PORTDIR */
377 if ((fp = fopen(CACHE_EBUILD_FILE, "r")) == NULL)
378 return 1;
379 } else if (do_eclass) {
380 xchdir(portdir);
381 if ((eclass_dir = opendir("eclass")) == NULL)
382 errp("opendir(\"%s/eclass\") failed", portdir);
383 } else { /* if (do_install) */
384 char buf[_Q_PATH_MAX];
385 snprintf(buf, sizeof(buf), "%s/%s", portroot, portvdb);
386 xchdir(buf);
387 if ((vdb_dir = opendir(".")) == NULL)
388 errp("could not opendir(%s/%s) for ROOT/VDB", portroot, portvdb);
389 }
390
391 /* allocate a circular buffers list for --before */
392 buf_list = qgrep_buf_list_alloc(num_lines_before + 1);
393
394 /* iteration is either over ebuilds or eclasses */
395 while (do_eclass
396 ? ((dentry = readdir(eclass_dir))
397 && snprintf(ebuild, sizeof(ebuild), "eclass/%s", dentry->d_name))
398 : (do_installed
399 ? (get_next_installed_ebuild(ebuild, vdb_dir, &dentry, &cat_dir) != NULL)
400 : (fgets(ebuild, sizeof(ebuild), fp) != NULL))) {
401 FILE *newfp;
402
403 /* filter badly named files, prepare eclass or package name, etc. */
404 if (do_eclass) {
405 if ((p = strrchr(ebuild, '.')) == NULL)
406 continue;
407 if (strcmp(p, ".eclass"))
408 continue;
409 if (show_name || (include_atoms != NULL)) {
410 /* cut ".eclass" */
411 *p = '\0';
412 /* and skip "eclass/" */
413 snprintf(name, sizeof(name), "%s", ebuild + 7);
414 /* restore the filepath */
415 *p = '.';
416 }
417 } else {
418 if ((p = strchr(ebuild, '\n')) != NULL)
419 *p = '\0';
420 if (show_name || (include_atoms != NULL)) {
421 /* cut ".ebuild" */
422 if (p == NULL)
423 p = ebuild + strlen(ebuild);
424 *(p-7) = '\0';
425 /* cut "/foo/" from "cat/foo/foo-x.y" */
426 if ((p = strchr(ebuild, '/')) == NULL)
427 continue;
428 *(p++) = '\0';
429 /* find head of the ebuild basename */
430 if ((p = strchr(p, '/')) == NULL)
431 continue;
432 /* find start of the pkg name */
433 snprintf(name, sizeof(name), "%s/%s", ebuild, (p+1));
434 /* restore the filepath */
435 *p = '/';
436 *(p + strlen(p)) = '.';
437 ebuild[strlen(ebuild)] = '/';
438 }
439 }
440
441 /* filter the files we grep when there are extra args */
442 if (include_atoms != NULL)
443 if (!qgrep_name_match(name, (argc - optind - 1), include_atoms))
444 continue;
445
446 if ((newfp = fopen(ebuild, "r")) != NULL) {
447 int lineno = 0;
448 char remaining_after_context = 0;
449 count = 0;
450 /* if there have been some matches already, then a separator will be needed */
451 need_separator = (!status) && (num_lines_before || num_lines_after);
452 /* whatever is in the circular buffers list is no more a valid context */
453 qgrep_buf_list_invalidate(buf_list);
454
455 /* reading a new line always happen in the next buffer of the list */
456 while ((buf_list = buf_list->next)
457 && (fgets(buf_list->buf, sizeof(buf_list->buf), newfp)) != NULL) {
458 lineno++;
459 buf_list->valid = 1;
460
461 /* cleanup EOL */
462 if ((p = strrchr(buf_list->buf, '\n')) != NULL)
463 *p = 0;
464 if ((p = strrchr(buf_list->buf, '\r')) != NULL)
465 *p = 0;
466
467 if (skip_comments) {
468 /* reject comments line ("^[ \t]*#") */
469 p = buf_list->buf;
470 while (*p == ' ' || *p == '\t') p++;
471 if (*p == '#')
472 goto print_after_context;
473 }
474
475 if (skip_pattern) {
476 /* reject some other lines which match an optional pattern */
477 if (!do_regex) {
478 if (strfunc(buf_list->buf, skip_pattern) != NULL)
479 goto print_after_context;
480 } else {
481 if (regexec(&skip_preg, buf_list->buf, 0, NULL, 0) == 0)
482 goto print_after_context;
483 }
484 }
485
486 /* four ways to match a line (with/without inversion and regexp) */
487 if (!invert_match) {
488 if (do_regex == 0) {
489 if (strfunc(buf_list->buf, argv[optind]) == NULL)
490 goto print_after_context;
491 } else {
492 if (regexec(&preg, buf_list->buf, 0, NULL, 0) != 0)
493 goto print_after_context;
494 }
495 } else {
496 if (do_regex == 0) {
497 if (strfunc(buf_list->buf, argv[optind]) != NULL)
498 goto print_after_context;
499 } else {
500 if (regexec(&preg, buf_list->buf, 0, NULL, 0) == 0)
501 goto print_after_context;
502 }
503 }
504
505 count++;
506 status = 0; /* got a match, exit status should be 0 */
507 if (per_file_output)
508 continue; /* matching files are listed out of this loop */
509
510 if ((need_separator > 0)
511 && (num_lines_before || num_lines_after))
512 printf("--\n");
513 /* "need_separator" is not a flag, but a counter, so that
514 * adjacent contextes are not separated */
515 need_separator = 0 - num_lines_before;
516 if (!do_list) {
517 /* print the leading context */
518 qgrep_print_before_context(buf_list, num_lines_before, label,
519 ((verbose > 1) ? lineno : -1));
520 /* print matching line */
521 if (invert_match || *RED == '\0')
522 qgrep_print_matching_line_nocolor(buf_list, label,
523 ((verbose > 1) ? lineno : -1));
524 else if (do_regex)
525 qgrep_print_matching_line_regcolor(buf_list, label,
526 ((verbose > 1) ? lineno : -1), &preg);
527 else
528 qgrep_print_matching_line_strcolor(buf_list, label,
529 ((verbose > 1) ? lineno : -1), strfunc, argv[optind]);
530 } else {
531 /* in verbose do_list mode, list the file once per match */
532 printf("%s", label);
533 if (verbose > 1)
534 printf(":%d", lineno);
535 putchar('\n');
536 }
537 /* init count down of trailing context lines */
538 remaining_after_context = num_lines_after;
539 continue;
540
541 print_after_context:
542 /* print some trailing context lines when needed */
543 if (!remaining_after_context) {
544 if (!status)
545 /* we're getting closer to the need of a separator between
546 * current match block and the next one */
547 ++need_separator;
548 } else {
549 qgrep_print_context_line(buf_list, label,
550 ((verbose > 1) ? lineno : -1));
551 --remaining_after_context;
552 }
553 }
554 fclose(newfp);
555 if (!per_file_output)
556 continue; /* matches were already displayed, line per line */
557 if (do_count && count) {
558 if (label != NULL)
559 /* -c without -v/-N/-H only outputs
560 * the matches count of the file */
561 printf("%s:", label);
562 printf("%d\n", count);
563 } else if ((count && !invert_list) || (!count && invert_list))
564 printf("%s\n", label); /* do_list == 1, or we wouldn't be here */
565 }
566 }
567 if (do_eclass)
568 closedir(eclass_dir);
569 else if (!do_installed)
570 fclose(fp);
571 if (do_regex)
572 regfree(&preg);
573 if (do_regex && skip_pattern)
574 regfree(&skip_preg);
575 if (include_atoms != NULL) {
576 for (i = 0; i < (argc - optind - 1); i++)
577 if (include_atoms[i] != NULL)
578 atom_implode(include_atoms[i]);
579 free(include_atoms);
580 }
581 qgrep_buf_list_free(buf_list);
582 return status;
583 }
584
585 #else
586 DEFINE_APPLET_STUB(qgrep)
587 #endif

  ViewVC Help
Powered by ViewVC 1.1.20