From 70f7f8c4958b6458708187278f9a8fbca34ef542 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 2 Aug 2021 12:25:52 -0700 Subject: [PATCH] Added -B and -A options to match grep --- README.md | 4 +++- bp.1 | 23 ++++++++++++++-------- bp.1.md | 18 +++++++++++------ bp.c | 59 ++++++++++++++++++++++++++++++++----------------------- print.c | 27 +++++++++++++------------ print.h | 9 +++++++-- 6 files changed, 85 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 8b52deb..402e36a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,9 @@ It's written in pure C with no dependencies. * `-p` `--pattern ` provide a pattern (equivalent to `bp '\()'`) * `-r` `--replace ` replace the input pattern with the given replacement * `-s` `--skip ` skip over the given pattern when looking for matches -* `-C` `--context ` change how many lines of context are printed (`0`: no context, `all`: the whole file, `` matching lines and `` lines before/after) +* `-B` `--context-before ` change how many lines of context are printed before each match +* `-B` `--context-after ` change how many lines of context are printed after each match +* `-C` `--context ` change how many lines of context are printed before and after each match * `-g` `--grammar ` use the specified file as a grammar * `-G` `--git` get filenames from git * `-f` `--format` `auto|plain|fancy` set the output format (`fancy` includes colors and line numbers) diff --git a/bp.1 b/bp.1 index 2d884d8..d1ffb7a 100644 --- a/bp.1 +++ b/bp.1 @@ -62,15 +62,22 @@ Use \f[B]git\f[R] to get a list of files. Remaining file arguments (if any) are passed to \f[B]git --ls-files\f[R] instead of treated as literal files. .TP +\f[B]-B\f[R], \f[B]--context-before\f[R] \f[I]N\f[R] +The number of lines of context to print before each match (default: 0). +See \f[B]--context\f[R] below for details on \f[B]none\f[R] or +\f[B]all\f[R]. +.TP +\f[B]-A\f[R], \f[B]--context-after\f[R] \f[I]N\f[R] +The number of lines of context to print after each match (default: 0). +See \f[B]--context\f[R] below for details on \f[B]none\f[R] or +\f[B]all\f[R]. +.TP \f[B]-C\f[R], \f[B]--context\f[R] \f[I]N\f[R] -The number of lines of context to print. -If \f[I]N\f[R] is 0, print only the exact text of the matches. -If \f[I]N\f[R] is \f[B]\[lq]all\[rq]\f[R], print the entire file. -Otherwise, if \f[I]N\f[R] is a positive integer, print the whole line on -which matches occur, as well as the \f[I]N-1\f[R] lines before and after -the match. -The default value for this argument is \f[B]1\f[R] (print whole lines -where matches occur). +The number of lines to print before and after each match (default: 0). +If \f[I]N\f[R] is \f[B]none\f[R], print only the exact text of the +matches. +If \f[I]N\f[R] is \f[B]\[lq]all\[rq]\f[R], print all text before and +after each match. .TP \f[B]-f\f[R], \f[B]--format\f[R] \f[I]auto\f[R]|\f[I]fancy\f[R]|\f[I]plain\f[R] Set the output format. diff --git a/bp.1.md b/bp.1.md index b5cc6d8..bf20d37 100644 --- a/bp.1.md +++ b/bp.1.md @@ -61,13 +61,19 @@ for more info. : Use `git` to get a list of files. Remaining file arguments (if any) are passed to `git --ls-files` instead of treated as literal files. +`-B`, `--context-before` *N* +: The number of lines of context to print before each match (default: 0). See +`--context` below for details on `none` or `all`. + +`-A`, `--context-after` *N* +: The number of lines of context to print after each match (default: 0). See +`--context` below for details on `none` or `all`. + + `-C`, `--context` *N* -: The number of lines of context to print. If *N* is 0, print only the -exact text of the matches. If *N* is **"all"**, print the entire file. -Otherwise, if *N* is a positive integer, print the whole line on which -matches occur, as well as the *N-1* lines before and after the match. The -default value for this argument is **1** (print whole lines where matches -occur). +: The number of lines to print before and after each match (default: 0). If *N* +is `none`, print only the exact text of the matches. If *N* is **"all"**, print +all text before and after each match. `-f`, `--format` *auto*\|*fancy*\|*plain* : Set the output format. *fancy* includes colors and line numbers, *plain* diff --git a/bp.c b/bp.c index c8cfdaa..73df4e6 100644 --- a/bp.c +++ b/bp.c @@ -51,7 +51,9 @@ static const char *usage = ( " -w --word find words matching the given string pattern\n" " -r --replace replace the input pattern with the given replacement\n" " -s --skip skip over the given pattern when looking for matches\n" - " -C --context set number of lines of context to print (all: the whole file, 0: only the match, 1: the line, N: N lines of context)\n" + " -B --context-before set number of lines of context to print before the match\n" + " -B --context-after set number of lines of context to print after the match\n" + " -C --context set number of lines of context to print before and after the match\n" " -f --format auto|fancy|plain set the output format\n" " -g --grammar use the specified file as a grammar"); @@ -60,17 +62,16 @@ static const char *usage = ( // Flag-configurable options: typedef enum { CONFIRM_ASK, CONFIRM_ALL, CONFIRM_NONE } confirm_t; -#define USE_DEFAULT_CONTEXT -2 -#define ALL_CONTEXT -1 static struct { - int context_lines; + int context_before, context_after; bool ignorecase, verbose, git_mode; confirm_t confirm; enum { MODE_NORMAL, MODE_LISTFILES, MODE_INPLACE, MODE_JSON, MODE_EXPLAIN } mode; enum { FORMAT_AUTO, FORMAT_FANCY, FORMAT_PLAIN } format; pat_t *skip; } options = { - .context_lines = USE_DEFAULT_CONTEXT, + .context_before = USE_DEFAULT_CONTEXT, + .context_after = USE_DEFAULT_CONTEXT, .ignorecase = false, .verbose = false, .confirm = CONFIRM_ALL, @@ -240,15 +241,17 @@ static void confirm_replacements(file_t *f, match_t *m, confirm_t *confirm) } { // Print the original - printer_t pr = {.file = f, .context_lines = options.context_lines, + printer_t pr = {.file = f, .context_before = options.context_before, + .context_after = options.context_after, .use_color = true, .print_line_numbers = true}; print_match(tty_out, &pr, m->children[0]); // Print trailing context lines: print_match(tty_out, &pr, NULL); } - if (options.context_lines > 1) fprintf(tty_out, "\n"); + if (options.context_before > 1 || options.context_after > 1) fprintf(tty_out, "\n"); { // Print the replacement - printer_t pr = {.file = f, .context_lines = options.context_lines, + printer_t pr = {.file = f, .context_before = options.context_before, + .context_after = options.context_after, .use_color = true, .print_line_numbers = true}; print_match(tty_out, &pr, m); // Print trailing context lines: @@ -295,7 +298,8 @@ static int inplace_modify_file(def_t *defs, file_t *f, pat_t *pattern) printer_t pr = { .file = f, - .context_lines = ALL_CONTEXT, + .context_before = ALL_CONTEXT, + .context_after = ALL_CONTEXT, .use_color = false, .print_line_numbers = false, }; @@ -306,8 +310,7 @@ static int inplace_modify_file(def_t *defs, file_t *f, pat_t *pattern) match_t *m = NULL; while ((m = next_match(defs, f, m, pattern, options.skip, options.ignorecase))) { ++matches; - printer_t err_pr = {.file = f, .context_lines = 1, .use_color = true, .print_line_numbers = true}; - if (print_errors(&err_pr, m) > 0) + if (print_errors(f, m) > 0) exit(EXIT_FAILURE); // Lazy-open file for writing upon first match: if (dest == NULL) { @@ -346,15 +349,15 @@ static int print_matches(def_t *defs, file_t *f, pat_t *pattern) int matches = 0; printer_t pr = { .file = f, - .context_lines = options.context_lines, + .context_before = options.context_before, + .context_after = options.context_after, .use_color = options.format == FORMAT_FANCY, .print_line_numbers = options.format == FORMAT_FANCY, }; match_t *m = NULL; while ((m = next_match(defs, f, m, pattern, options.skip, options.ignorecase))) { - printer_t err_pr = {.file = f, .context_lines = 1, .use_color = true, .print_line_numbers = true}; - if (print_errors(&err_pr, m) > 0) + if (print_errors(f, m) > 0) exit(EXIT_FAILURE); if (++matches == 1) { @@ -482,6 +485,16 @@ static int process_git_files(def_t *defs, pat_t *pattern, int argc, char *argv[] return found; } +// +// Convert a context string to an integer +// +static int context_from_flag(const char *flag) +{ + if (streq(flag, "all")) return ALL_CONTEXT; + if (streq(flag, "none")) return NO_CONTEXT; + return (int)strtol(flag, NULL, 10); +} + #define FLAG(f) (flag = get_flag(argv, f, &argv)) #define BOOLFLAG(f) get_boolflag(argv, f, &argv) @@ -567,15 +580,11 @@ int main(int argc, char *argv[]) } options.skip = either_pat(arg_file, options.skip, s); } else if (FLAG("-C") || FLAG("--context")) { - if (streq(flag, "all")) { - options.context_lines = ALL_CONTEXT; - } else if (streq(flag, "none")) { - options.context_lines = 0; - } else { - options.context_lines = (int)strtol(flag, &flag, 10); - if (flag && flag[0]) - errx(EXIT_FAILURE, "Unsupported flags after --context: %s", flag); - } + options.context_before = options.context_after = context_from_flag(flag); + } else if (FLAG("-B") || FLAG("--before-context")) { + options.context_before = context_from_flag(flag); + } else if (FLAG("-A") || FLAG("--after-context")) { + options.context_after = context_from_flag(flag); } else if (FLAG("-f") || FLAG("--format")) { options.format = streq(flag, "fancy") ? FORMAT_FANCY : streq(flag, "plain") ? FORMAT_PLAIN : FORMAT_AUTO; } else if (argv[0][0] == '-' && argv[0][1] && argv[0][1] != '-') { // single-char flags @@ -600,8 +609,8 @@ int main(int argc, char *argv[]) for (argc = 0; argv[argc]; ++argc) ; // update argc - if (options.context_lines == USE_DEFAULT_CONTEXT) options.context_lines = 1; - if (options.context_lines < 0 && options.context_lines != ALL_CONTEXT) options.context_lines = 0; + if (options.context_before == USE_DEFAULT_CONTEXT) options.context_before = 0; + if (options.context_after == USE_DEFAULT_CONTEXT) options.context_after = 0; if (options.format == FORMAT_AUTO) options.format = isatty(STDOUT_FILENO) ? FORMAT_FANCY : FORMAT_PLAIN; diff --git a/print.c b/print.c index 6cccba2..1a3f11b 100644 --- a/print.c +++ b/print.c @@ -7,6 +7,7 @@ #include #include +#include "files.h" #include "match.h" #include "print.h" #include "types.h" @@ -69,12 +70,12 @@ static void print_between(FILE *out, printer_t *pr, const char *start, const cha // static const char *context_before(printer_t *pr, const char *pos) { - if (pr->context_lines == -1) { + if (pr->context_before == ALL_CONTEXT) { return pr->pos; - } else if (pr->context_lines > 0) { + } else if (pr->context_before >= 0) { size_t n = get_line_number(pr->file, pos); - if (n >= (size_t)((pr->context_lines - 1) + 1)) - n -= (size_t)(pr->context_lines - 1); + if (n >= (size_t)(pr->context_before + 1)) + n -= (size_t)pr->context_before; else n = 1; const char *sol = get_line(pr->file, n); @@ -91,10 +92,10 @@ static const char *context_before(printer_t *pr, const char *pos) // static const char *context_after(printer_t *pr, const char *pos) { - if (pr->context_lines == -1) { + if (pr->context_after == ALL_CONTEXT) { return pr->file->end; - } else if (pr->context_lines > 0) { - size_t n = get_line_number(pr->file, pos) + (size_t)(pr->context_lines - 1); + } else if (pr->context_after >= 0) { + size_t n = get_line_number(pr->file, pos) + (size_t)pr->context_after; const char *eol = get_line(pr->file, n+1); return eol ? eol : pr->file->end; } else { @@ -221,7 +222,7 @@ void print_match(FILE *out, printer_t *pr, match_t *m) if (!first) { // When not printing context lines, print each match on its own // line instead of jamming them all together: - if (pr->context_lines == 0 && (!pr->needs_line_number || !pr->print_line_numbers)) { + if (pr->context_before == NO_CONTEXT && pr->context_after == NO_CONTEXT && (!pr->needs_line_number || !pr->print_line_numbers)) { fprintf(out, "\n"); pr->needs_line_number = 1; } @@ -233,7 +234,7 @@ void print_match(FILE *out, printer_t *pr, match_t *m) } else { // Non-overlapping ranges: print_between(out, pr, pr->pos, after_last, pr->use_color ? color_normal : NULL); - if (pr->context_lines > 1) + if (pr->context_before > 0 || pr->context_after > 0) fprintf(out, "\n"); // Gap between chunks } } @@ -253,19 +254,19 @@ void print_match(FILE *out, printer_t *pr, match_t *m) // Print any errors that are present in the given match object to stderr and // return the number of errors found. // -int print_errors(printer_t *pr, match_t *m) +int print_errors(file_t *f, match_t *m) { int ret = 0; if (m->pat->type == BP_ERROR) { fprintf(stderr, "\033[31;1m"); - printer_t tmp = {.file = pr->file}; // No bells and whistles + printer_t tmp = {.file = f, .context_before=NO_CONTEXT, .context_after=NO_CONTEXT}; // No bells and whistles print_match(stderr, &tmp, m); // Error message fprintf(stderr, "\033[0m\n"); - fprint_line(stderr, pr->file, m->start, m->end, " "); + fprint_line(stderr, f, m->start, m->end, " "); return 1; } for (int i = 0; m->children && m->children[i]; i++) - ret += print_errors(pr, m->children[i]); + ret += print_errors(f, m->children[i]); return ret; } diff --git a/print.h b/print.h index 5ad645c..2bc2948 100644 --- a/print.h +++ b/print.h @@ -7,11 +7,16 @@ #include #include "types.h" +#include "files.h" + +#define USE_DEFAULT_CONTEXT -3 +#define ALL_CONTEXT -2 +#define NO_CONTEXT -1 typedef struct { file_t *file; const char *pos; - int context_lines; + int context_before, context_after; bool needs_line_number:1; bool use_color:1; bool print_line_numbers:1; @@ -20,7 +25,7 @@ typedef struct { __attribute__((nonnull(1,2))) void print_match(FILE *out, printer_t *pr, match_t *m); __attribute__((nonnull)) -int print_errors(printer_t *pr, match_t *m); +int print_errors(file_t *f, match_t *m); #endif // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1