Added -B and -A options to match grep

This commit is contained in:
Bruce Hill 2021-08-02 12:25:52 -07:00
parent db3c88d16b
commit 70f7f8c495
6 changed files with 85 additions and 55 deletions

View File

@ -22,7 +22,9 @@ It's written in pure C with no dependencies.
* `-p` `--pattern <pat>` provide a pattern (equivalent to `bp '\(<pat>)'`)
* `-r` `--replace <replacement>` replace the input pattern with the given replacement
* `-s` `--skip <skip pattern>` skip over the given pattern when looking for matches
* `-C` `--context <N>` change how many lines of context are printed (`0`: no context, `all`: the whole file, `<N>` matching lines and `<N-1>` lines before/after)
* `-B` `--context-before <N>` change how many lines of context are printed before each match
* `-B` `--context-after <N>` change how many lines of context are printed after each match
* `-C` `--context <N>` change how many lines of context are printed before and after each match
* `-g` `--grammar <grammar file>` 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)

23
bp.1
View File

@ -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.

18
bp.1.md
View File

@ -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*

59
bp.c
View File

@ -51,7 +51,9 @@ static const char *usage = (
" -w --word <string-pat> find words matching the given string pattern\n"
" -r --replace <replacement> replace the input pattern with the given replacement\n"
" -s --skip <skip-pattern> skip over the given pattern when looking for matches\n"
" -C --context <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 <n> set number of lines of context to print before the match\n"
" -B --context-after <n> set number of lines of context to print after the match\n"
" -C --context <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 <grammar-file> 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;

27
print.c
View File

@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -7,11 +7,16 @@
#include <stdbool.h>
#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