2021-01-12 21:04:43 -08:00
|
|
|
//
|
|
|
|
// bp.c - Source code for the bp parser
|
|
|
|
//
|
|
|
|
// See `man ./bp.1` for more details
|
|
|
|
//
|
2020-09-11 01:28:06 -07:00
|
|
|
#include <fcntl.h>
|
2020-09-13 15:55:09 -07:00
|
|
|
#include <glob.h>
|
2020-09-12 15:49:51 -07:00
|
|
|
#include <limits.h>
|
2021-01-15 18:23:18 -08:00
|
|
|
#include <signal.h>
|
2020-09-11 01:28:06 -07:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2021-01-17 09:21:58 -08:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
2020-09-11 01:28:06 -07:00
|
|
|
#include <unistd.h>
|
|
|
|
|
2021-01-15 19:35:39 -08:00
|
|
|
#include "definitions.h"
|
2021-01-15 19:44:16 -08:00
|
|
|
#include "files.h"
|
2020-12-14 22:20:16 -08:00
|
|
|
#include "json.h"
|
2021-01-15 19:44:16 -08:00
|
|
|
#include "match.h"
|
|
|
|
#include "pattern.h"
|
2021-01-15 19:27:25 -08:00
|
|
|
#include "print.h"
|
2020-09-11 01:28:06 -07:00
|
|
|
#include "utils.h"
|
|
|
|
|
2021-01-15 18:32:56 -08:00
|
|
|
#ifndef BP_NAME
|
|
|
|
#define BP_NAME "bp"
|
|
|
|
#endif
|
|
|
|
|
2020-09-11 01:28:06 -07:00
|
|
|
static const char *usage = (
|
2021-01-15 18:32:56 -08:00
|
|
|
BP_NAME" - a Parsing Expression Grammar command line tool\n\n"
|
2020-09-11 01:28:06 -07:00
|
|
|
"Usage:\n"
|
2021-01-15 18:32:56 -08:00
|
|
|
" "BP_NAME" [flags] <pattern> [<input files>...]\n\n"
|
2020-09-11 01:28:06 -07:00
|
|
|
"Flags:\n"
|
2020-09-14 12:39:31 -07:00
|
|
|
" -h --help print the usage and quit\n"
|
|
|
|
" -v --verbose print verbose debugging info\n"
|
2020-12-12 16:31:53 -08:00
|
|
|
" -e --explain explain the matches\n"
|
2020-12-14 22:32:47 -08:00
|
|
|
" -j --json print matches as a list of JSON objects\n"
|
2020-09-14 12:39:31 -07:00
|
|
|
" -i --ignore-case preform matching case-insensitively\n"
|
2021-01-15 18:23:18 -08:00
|
|
|
" -I --inplace modify a file in-place\n"
|
|
|
|
" -C --confirm ask for confirmation on each replacement\n"
|
2020-12-17 16:23:29 -08:00
|
|
|
" -l --list-files list filenames only\n"
|
2020-12-12 16:31:53 -08:00
|
|
|
" -p --pattern <pat> provide a pattern (equivalent to bp '\\(<pat>)')\n"
|
2020-09-14 12:39:31 -07:00
|
|
|
" -P --pattern-string <pat> provide a string pattern (may be useful if '<pat>' begins with a '-')\n"
|
|
|
|
" -r --replace <replacement> replace the input pattern with the given replacement\n"
|
2021-01-15 01:19:10 -08:00
|
|
|
" -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"
|
2020-09-14 12:39:31 -07:00
|
|
|
" -g --grammar <grammar file> use the specified file as a grammar\n");
|
2020-09-07 23:05:38 -07:00
|
|
|
|
2021-01-15 02:05:17 -08:00
|
|
|
// Flag-configurable options:
|
2021-01-15 01:19:10 -08:00
|
|
|
#define USE_DEFAULT_CONTEXT -2
|
|
|
|
#define ALL_CONTEXT -1
|
2021-01-15 02:05:17 -08:00
|
|
|
static int context_lines = USE_DEFAULT_CONTEXT;
|
|
|
|
static unsigned int print_color = 0;
|
|
|
|
static unsigned int print_line_numbers = 0;
|
|
|
|
static unsigned int ignorecase = 0;
|
|
|
|
static unsigned int verbose = 0;
|
2021-01-15 18:23:18 -08:00
|
|
|
typedef enum { CONFIRM_ASK, CONFIRM_ALL, CONFIRM_NONE } confirm_t;
|
|
|
|
static confirm_t confirm = CONFIRM_ALL;
|
2021-01-15 02:05:17 -08:00
|
|
|
static enum {
|
|
|
|
MODE_NORMAL,
|
|
|
|
MODE_LISTFILES,
|
|
|
|
MODE_INPLACE,
|
|
|
|
MODE_JSON,
|
|
|
|
MODE_EXPLAIN,
|
|
|
|
} mode = MODE_NORMAL;
|
2020-12-12 16:31:53 -08:00
|
|
|
|
2021-01-15 18:23:18 -08:00
|
|
|
// If a filename is put here, it will be deleted if a signal is received
|
|
|
|
const char *in_use_tempfile = NULL;
|
|
|
|
|
|
|
|
// Used for user input/output that doesn't interfere with unix pipeline
|
|
|
|
FILE *tty_out = NULL, *tty_in = NULL;
|
|
|
|
|
|
|
|
//
|
|
|
|
// Helper function to reduce code duplication
|
|
|
|
//
|
|
|
|
static inline void fprint_filename(FILE *out, const char *filename)
|
|
|
|
{
|
|
|
|
if (!filename[0]) return;
|
|
|
|
if (print_color) fprintf(out, "\033[0;1;4;33m%s\033[0m\n", filename);
|
|
|
|
else fprintf(out, "%s:\n", filename);
|
|
|
|
}
|
2021-01-12 22:33:28 -08:00
|
|
|
|
2021-01-12 22:22:38 -08:00
|
|
|
//
|
|
|
|
// Return a pointer to the value part of a flag, if present, otherwise NULL.
|
|
|
|
// This works for --foo=value or --foo value
|
|
|
|
//
|
2021-01-15 18:23:18 -08:00
|
|
|
__attribute__((nonnull))
|
2020-09-11 02:55:15 -07:00
|
|
|
static char *getflag(const char *flag, char *argv[], int *i)
|
|
|
|
{
|
|
|
|
size_t n = strlen(flag);
|
2020-09-14 12:39:31 -07:00
|
|
|
check(argv[*i], "Attempt to get flag from NULL argument");
|
2020-09-11 02:55:15 -07:00
|
|
|
if (strncmp(argv[*i], flag, n) == 0) {
|
|
|
|
if (argv[*i][n] == '=') {
|
|
|
|
return &argv[*i][n+1];
|
|
|
|
} else if (argv[*i][n] == '\0') {
|
2020-09-14 12:39:31 -07:00
|
|
|
check(argv[*i+1], "Expected argument after '%s'\n\n%s", flag, usage);
|
2020-09-11 02:55:15 -07:00
|
|
|
++(*i);
|
|
|
|
return argv[*i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
2020-09-13 15:55:09 -07:00
|
|
|
|
2021-01-15 01:19:10 -08:00
|
|
|
//
|
|
|
|
// Scan the first few dozen bytes of a file and return 1 if the contents all
|
|
|
|
// look like printable text characters, otherwise return 0.
|
|
|
|
//
|
|
|
|
static int is_text_file(const char *filename)
|
|
|
|
{
|
|
|
|
int fd = open(filename, O_RDONLY);
|
|
|
|
if (fd < 0) return 0;
|
|
|
|
unsigned char buf[64];
|
|
|
|
int len = read(fd, buf, sizeof(buf)/sizeof(unsigned char));
|
|
|
|
if (len < 0) return 0;
|
|
|
|
(void)close(fd);
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
if (!(buf[i] == '\t' || buf[i] == '\n' || buf[i] == '\r'
|
|
|
|
|| buf[i] >= '\x20'))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print matches in JSON format.
|
|
|
|
//
|
2021-01-15 18:38:06 -08:00
|
|
|
static int print_matches_as_json(def_t *defs, file_t *f, pat_t *pattern)
|
2021-01-15 01:19:10 -08:00
|
|
|
{
|
|
|
|
int matches = 0;
|
2021-01-15 02:05:17 -08:00
|
|
|
for (match_t *m = NULL; (m = next_match(defs, f, m, pattern, ignorecase)); ) {
|
2021-01-15 01:19:10 -08:00
|
|
|
if (++matches > 1)
|
|
|
|
printf(",\n");
|
2021-01-15 02:05:17 -08:00
|
|
|
printf("{\"filename\":\"%s\",", f->filename);
|
2021-01-15 01:19:10 -08:00
|
|
|
printf("\"tree\":{\"rule\":\"text\",\"start\":%d,\"end\":%ld,\"children\":[",
|
|
|
|
0, f->end - f->contents);
|
2021-01-15 02:05:17 -08:00
|
|
|
json_match(f->contents, m, verbose);
|
2021-01-15 01:19:10 -08:00
|
|
|
printf("]}}\n");
|
|
|
|
}
|
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print matches in a visual explanation style
|
|
|
|
//
|
2021-01-15 18:38:06 -08:00
|
|
|
static int explain_matches(def_t *defs, file_t *f, pat_t *pattern)
|
2021-01-15 01:19:10 -08:00
|
|
|
{
|
|
|
|
int matches = 0;
|
2021-01-15 02:05:17 -08:00
|
|
|
for (match_t *m = NULL; (m = next_match(defs, f, m, pattern, ignorecase)); ) {
|
2021-01-15 01:19:10 -08:00
|
|
|
if (++matches == 1) {
|
2021-01-15 18:23:18 -08:00
|
|
|
fprint_filename(stdout, f->filename);
|
2021-01-15 01:19:10 -08:00
|
|
|
} else {
|
|
|
|
printf("\n\n");
|
|
|
|
}
|
|
|
|
visualize_match(m);
|
|
|
|
}
|
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
|
2021-01-15 18:23:18 -08:00
|
|
|
//
|
|
|
|
// Cleanup function to ensure no temp files are left around if the program
|
|
|
|
// exits unexpectedly.
|
|
|
|
//
|
|
|
|
static void cleanup(void)
|
|
|
|
{
|
|
|
|
if (in_use_tempfile) {
|
|
|
|
remove(in_use_tempfile);
|
|
|
|
in_use_tempfile = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Signal handler to ensure cleanup happens.
|
|
|
|
//
|
|
|
|
static void sig_handler(int sig) { (void)sig; cleanup(); }
|
|
|
|
|
|
|
|
//
|
|
|
|
// Present the user with a prompt to confirm replacements before they happen.
|
|
|
|
// If the user rejects a replacement, the match object is set to the underlying
|
|
|
|
// non-replacement value.
|
|
|
|
//
|
|
|
|
static void confirm_replacements(file_t *f, match_t *m, confirm_t *confirm)
|
|
|
|
{
|
|
|
|
if (*confirm == CONFIRM_ALL) return;
|
2021-01-16 10:29:09 -08:00
|
|
|
if (m->pat->type == BP_REPLACE) {
|
2021-01-15 18:23:18 -08:00
|
|
|
if (*confirm == CONFIRM_NONE) {
|
|
|
|
m->skip_replacement = 1;
|
|
|
|
goto check_children;
|
|
|
|
}
|
|
|
|
|
|
|
|
{ // Print the original
|
2021-01-15 18:43:03 -08:00
|
|
|
printer_t pr = {.file = f, .context_lines = context_lines,
|
2021-01-15 18:23:18 -08:00
|
|
|
.use_color = 1, .print_line_numbers = 1};
|
|
|
|
print_match(tty_out, &pr, m->child);
|
|
|
|
// Print trailing context lines:
|
|
|
|
print_match(tty_out, &pr, NULL);
|
|
|
|
}
|
2021-01-15 18:43:03 -08:00
|
|
|
if (context_lines > 1) fprintf(tty_out, "\n");
|
2021-01-15 18:23:18 -08:00
|
|
|
{ // Print the replacement
|
2021-01-15 18:43:03 -08:00
|
|
|
printer_t pr = {.file = f, .context_lines = context_lines,
|
2021-01-15 18:23:18 -08:00
|
|
|
.use_color = 1, .print_line_numbers = 1};
|
|
|
|
print_match(tty_out, &pr, m);
|
|
|
|
// Print trailing context lines:
|
|
|
|
print_match(tty_out, &pr, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
retry:
|
|
|
|
fprintf(tty_out, "\033[1mReplace? (y)es (n)o (r)emaining (d)one\033[0m ");
|
|
|
|
fflush(tty_out);
|
|
|
|
|
|
|
|
char *answer = NULL;
|
|
|
|
size_t len = 0;
|
|
|
|
if (getline(&answer, &len, tty_in) > 0) {
|
2021-01-15 18:47:30 -08:00
|
|
|
if (strlen(answer) > 2) goto retry;
|
2021-01-15 18:23:18 -08:00
|
|
|
switch (answer[0]) {
|
2021-01-15 18:47:30 -08:00
|
|
|
case 'y': case '\n': break;
|
2021-01-15 18:23:18 -08:00
|
|
|
case 'n': m->skip_replacement = 1; break;
|
|
|
|
case 'r': *confirm = CONFIRM_ALL; break;
|
|
|
|
case 'd': m->skip_replacement = 1; *confirm = CONFIRM_NONE; break;
|
|
|
|
default: goto retry;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (answer) xfree(&answer);
|
|
|
|
fprintf(tty_out, "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
check_children:
|
|
|
|
if (m->child)
|
|
|
|
confirm_replacements(f, m->child, confirm);
|
|
|
|
if (m->nextsibling)
|
|
|
|
confirm_replacements(f, m->nextsibling, confirm);
|
|
|
|
}
|
|
|
|
|
2021-01-15 01:19:10 -08:00
|
|
|
//
|
|
|
|
// Replace a file's contents with the text version of a match.
|
|
|
|
// (Useful for replacements)
|
|
|
|
//
|
2021-01-15 18:38:06 -08:00
|
|
|
static int inplace_modify_file(def_t *defs, file_t *f, pat_t *pattern)
|
2021-01-15 01:19:10 -08:00
|
|
|
{
|
2021-01-15 18:23:18 -08:00
|
|
|
char tmp_filename[PATH_MAX+1] = {0};
|
2021-01-15 01:19:10 -08:00
|
|
|
printer_t pr = {
|
|
|
|
.file = f,
|
2021-01-15 18:43:03 -08:00
|
|
|
.context_lines = ALL_CONTEXT,
|
2021-01-15 01:19:10 -08:00
|
|
|
.use_color = 0,
|
|
|
|
.print_line_numbers = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
FILE *inplace_file = NULL; // Lazy-open this on the first match
|
|
|
|
int matches = 0;
|
2021-01-15 18:23:18 -08:00
|
|
|
confirm_t confirm_file = confirm;
|
2021-01-15 02:05:17 -08:00
|
|
|
for (match_t *m = NULL; (m = next_match(defs, f, m, pattern, ignorecase)); ) {
|
2021-01-15 01:19:10 -08:00
|
|
|
++matches;
|
|
|
|
if (print_errors(&pr, m) > 0)
|
|
|
|
exit(1);
|
|
|
|
// Lazy-open file for writing upon first match:
|
|
|
|
if (inplace_file == NULL) {
|
2021-01-15 18:23:18 -08:00
|
|
|
check(snprintf(tmp_filename, PATH_MAX, "%s.tmp.XXXXXX", f->filename) <= PATH_MAX,
|
|
|
|
"Failed to build temporary file template");
|
|
|
|
int out_fd = mkstemp(tmp_filename);
|
|
|
|
check(out_fd >= 0, "Failed to create temporary inplace file");
|
|
|
|
in_use_tempfile = tmp_filename;
|
|
|
|
inplace_file = fdopen(out_fd, "w");
|
|
|
|
if (confirm == CONFIRM_ASK && f->filename)
|
|
|
|
fprint_filename(tty_out, f->filename);
|
|
|
|
}
|
|
|
|
confirm_replacements(f, m, &confirm_file);
|
|
|
|
if (!in_use_tempfile) { // signal interrupted, so abort
|
|
|
|
fclose(inplace_file);
|
2021-01-15 18:45:33 -08:00
|
|
|
exit(1);
|
2021-01-15 01:19:10 -08:00
|
|
|
}
|
|
|
|
print_match(inplace_file, &pr, m);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inplace_file) {
|
2021-01-15 13:12:22 -08:00
|
|
|
// Print trailing context lines:
|
|
|
|
print_match(inplace_file, &pr, NULL);
|
2021-01-15 18:23:18 -08:00
|
|
|
if (confirm == CONFIRM_ALL)
|
|
|
|
printf("%s\n", f->filename);
|
2021-01-15 01:19:10 -08:00
|
|
|
fclose(inplace_file);
|
2021-01-15 18:23:18 -08:00
|
|
|
|
|
|
|
// TODO: if I want to implement backup files then add a line like this:
|
|
|
|
// if (backup) rename(f->filename, f->filename + ".bak");
|
|
|
|
check(rename(tmp_filename, f->filename) == 0,
|
|
|
|
"Failed to write file replacement for %s", f->filename);
|
|
|
|
|
|
|
|
in_use_tempfile = NULL;
|
2021-01-15 01:19:10 -08:00
|
|
|
}
|
2021-01-15 18:23:18 -08:00
|
|
|
|
2021-01-15 01:19:10 -08:00
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Print all the matches in a file.
|
|
|
|
//
|
2021-01-15 18:38:06 -08:00
|
|
|
static int print_matches(def_t *defs, file_t *f, pat_t *pattern)
|
2021-01-15 01:19:10 -08:00
|
|
|
{
|
|
|
|
static int printed_filenames = 0;
|
|
|
|
int matches = 0;
|
|
|
|
printer_t pr = {
|
|
|
|
.file = f,
|
2021-01-15 02:05:17 -08:00
|
|
|
.context_lines = context_lines,
|
2021-01-15 01:19:10 -08:00
|
|
|
.use_color = print_color,
|
|
|
|
.print_line_numbers = print_line_numbers,
|
|
|
|
};
|
|
|
|
|
2021-01-15 18:23:18 -08:00
|
|
|
confirm_t confirm_file = confirm;
|
2021-01-15 02:05:17 -08:00
|
|
|
for (match_t *m = NULL; (m = next_match(defs, f, m, pattern, ignorecase)); ) {
|
2021-01-15 01:19:10 -08:00
|
|
|
if (print_errors(&pr, m) > 0)
|
|
|
|
exit(1);
|
|
|
|
|
|
|
|
if (++matches == 1) {
|
|
|
|
if (printed_filenames++ > 0) printf("\n");
|
2021-01-15 18:23:18 -08:00
|
|
|
fprint_filename(stdout, f->filename);
|
2021-01-15 01:19:10 -08:00
|
|
|
}
|
2021-01-15 18:23:18 -08:00
|
|
|
confirm_replacements(f, m, &confirm_file);
|
2021-01-15 01:19:10 -08:00
|
|
|
print_match(stdout, &pr, m);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matches > 0) {
|
|
|
|
// Print trailing context lines:
|
|
|
|
print_match(stdout, &pr, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return matches;
|
|
|
|
}
|
|
|
|
|
2021-01-12 22:22:38 -08:00
|
|
|
//
|
|
|
|
// For a given filename, open the file and attempt to match the given pattern
|
|
|
|
// against it, printing any results according to the flags.
|
|
|
|
//
|
2021-01-15 18:38:06 -08:00
|
|
|
static int process_file(def_t *defs, const char *filename, pat_t *pattern)
|
2020-09-13 15:55:09 -07:00
|
|
|
{
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *f = load_file(NULL, filename);
|
2020-09-16 21:32:08 -07:00
|
|
|
check(f, "Could not open file: %s", filename);
|
2021-01-15 01:19:10 -08:00
|
|
|
|
|
|
|
int matches = 0;
|
2021-01-15 02:05:17 -08:00
|
|
|
if (mode == MODE_EXPLAIN) {
|
|
|
|
matches += explain_matches(defs, f, pattern);
|
|
|
|
} else if (mode == MODE_LISTFILES) {
|
|
|
|
match_t *m = next_match(defs, f, NULL, pattern, ignorecase);
|
2021-01-15 01:19:10 -08:00
|
|
|
if (m) {
|
|
|
|
recycle_if_unused(&m);
|
|
|
|
printf("%s\n", f->filename);
|
|
|
|
matches += 1;
|
2020-12-12 16:31:53 -08:00
|
|
|
}
|
2021-01-15 02:05:17 -08:00
|
|
|
} else if (mode == MODE_JSON) {
|
|
|
|
matches += print_matches_as_json(defs, f, pattern);
|
|
|
|
} else if (mode == MODE_INPLACE) {
|
|
|
|
matches += inplace_modify_file(defs, f, pattern);
|
2021-01-15 01:19:10 -08:00
|
|
|
} else {
|
2021-01-15 02:05:17 -08:00
|
|
|
matches += print_matches(defs, f, pattern);
|
2020-09-13 15:55:09 -07:00
|
|
|
}
|
2021-01-12 18:35:41 -08:00
|
|
|
|
2021-01-14 19:43:30 -08:00
|
|
|
#ifdef DEBUG_HEAP
|
2021-01-14 19:21:31 -08:00
|
|
|
check(recycle_all_matches() == 0, "Memory leak: there should no longer be any matches in use at this point.");
|
2021-01-14 19:43:30 -08:00
|
|
|
#endif
|
2021-01-12 18:35:41 -08:00
|
|
|
destroy_file(&f);
|
2021-01-15 01:19:10 -08:00
|
|
|
fflush(stdout);
|
|
|
|
return matches;
|
2020-09-13 15:55:09 -07:00
|
|
|
}
|
|
|
|
|
2020-09-11 02:55:15 -07:00
|
|
|
#define FLAG(f) (flag=getflag((f), argv, &i))
|
2020-09-09 22:29:09 -07:00
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2020-09-11 02:55:15 -07:00
|
|
|
char *flag = NULL;
|
2020-09-09 22:29:09 -07:00
|
|
|
|
2021-01-10 01:45:40 -08:00
|
|
|
def_t *defs = NULL;
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *loaded_files = NULL;
|
|
|
|
|
2021-01-15 18:57:09 -08:00
|
|
|
// Define a pattern that is just a reference to the rule `pattern`
|
2021-01-14 19:21:31 -08:00
|
|
|
file_t *pat_file = spoof_file(&loaded_files, "<pattern>", "pattern");
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *pattern = bp_pattern(loaded_files, pat_file->contents);
|
2021-01-14 19:21:31 -08:00
|
|
|
|
2021-01-15 18:57:09 -08:00
|
|
|
// Define a pattern that is just a reference to the rule `replacement`
|
2021-01-15 01:19:10 -08:00
|
|
|
file_t *rep_file = spoof_file(&loaded_files, "<replacement>", "replacement");
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *replacement = bp_pattern(rep_file, rep_file->contents);
|
2021-01-15 01:19:10 -08:00
|
|
|
|
2020-09-13 23:31:38 -07:00
|
|
|
// Load builtins:
|
2021-01-15 18:32:56 -08:00
|
|
|
file_t *xdg_file = load_file(&loaded_files, "/etc/xdg/"BP_NAME"/builtins.bp");
|
2021-01-15 02:05:17 -08:00
|
|
|
if (xdg_file) defs = load_grammar(defs, xdg_file);
|
2021-01-15 18:32:56 -08:00
|
|
|
file_t *local_file = load_file(&loaded_files, "%s/.config/"BP_NAME"/builtins.bp", getenv("HOME"));
|
2021-01-15 02:05:17 -08:00
|
|
|
if (local_file) defs = load_grammar(defs, local_file);
|
2020-09-12 15:11:44 -07:00
|
|
|
|
2020-09-14 12:39:31 -07:00
|
|
|
check(argc > 1, "%s", usage);
|
2021-01-17 09:21:58 -08:00
|
|
|
int i, npatterns = 0, git = 0;
|
2020-09-11 02:55:15 -07:00
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
if (streq(argv[i], "--")) {
|
|
|
|
++i;
|
|
|
|
break;
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (streq(argv[i], "--help")) {
|
|
|
|
flag_help:
|
2020-09-09 22:29:09 -07:00
|
|
|
printf("%s\n", usage);
|
|
|
|
return 0;
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (streq(argv[i], "--verbose")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
verbose = 1;
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (streq(argv[i], "--explain")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
mode = MODE_EXPLAIN;
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (streq(argv[i], "--json")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
mode = MODE_JSON;
|
2020-12-27 19:48:52 -08:00
|
|
|
} else if (streq(argv[i], "--inplace")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
mode = MODE_INPLACE;
|
2021-01-15 18:23:18 -08:00
|
|
|
} else if (streq(argv[i], "--confirm")) {
|
|
|
|
confirm = CONFIRM_ASK;
|
2021-01-17 09:21:58 -08:00
|
|
|
} else if (streq(argv[i], "--git")) {
|
|
|
|
git = 1;
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (streq(argv[i], "--ignore-case")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
ignorecase = 1;
|
2020-12-17 16:23:29 -08:00
|
|
|
} else if (streq(argv[i], "--list-files")) {
|
2021-01-15 02:05:17 -08:00
|
|
|
mode = MODE_LISTFILES;
|
2020-09-11 02:55:15 -07:00
|
|
|
} else if (FLAG("--replace") || FLAG("-r")) {
|
2021-01-08 00:57:46 -08:00
|
|
|
// TODO: spoof file as sprintf("pattern => '%s'", flag)
|
|
|
|
// except that would require handling edge cases like quotation marks etc.
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *replace_file = spoof_file(&loaded_files, "<replace argument>", flag);
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *rep = bp_replacement(replace_file, pattern, replace_file->contents);
|
2020-09-28 22:02:00 -07:00
|
|
|
check(rep, "Replacement failed to compile: %s", flag);
|
2021-01-13 18:56:22 -08:00
|
|
|
defs = with_def(defs, replace_file, strlen("replacement"), "replacement", rep);
|
2021-01-15 01:19:10 -08:00
|
|
|
pattern = replacement;
|
2020-09-11 02:55:15 -07:00
|
|
|
} else if (FLAG("--grammar") || FLAG("-g")) {
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *f = load_file(&loaded_files, flag);
|
2021-01-15 02:05:17 -08:00
|
|
|
if (f == NULL)
|
2021-01-15 18:32:56 -08:00
|
|
|
f = load_file(&loaded_files, "%s/.config/"BP_NAME"/%s.bp", getenv("HOME"), flag);
|
2021-01-15 02:05:17 -08:00
|
|
|
if (f == NULL)
|
2021-01-15 18:32:56 -08:00
|
|
|
f = load_file(&loaded_files, "/etc/xdg/"BP_NAME"/%s.bp", flag);
|
2020-09-16 19:35:43 -07:00
|
|
|
check(f != NULL, "Couldn't find grammar: %s", flag);
|
2021-01-10 01:45:40 -08:00
|
|
|
defs = load_grammar(defs, f); // Keep in memory for debug output
|
2020-09-13 23:31:38 -07:00
|
|
|
} else if (FLAG("--pattern") || FLAG("-p")) {
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", flag);
|
2021-01-15 12:40:19 -08:00
|
|
|
for (const char *str = arg_file->contents; str < arg_file->end; ) {
|
|
|
|
def_t *d = bp_definition(arg_file, str);
|
|
|
|
if (d) {
|
|
|
|
d->next = defs;
|
|
|
|
defs = d;
|
2021-01-15 19:02:36 -08:00
|
|
|
str = d->pat->end;
|
2021-01-15 12:40:19 -08:00
|
|
|
} else {
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *p = bp_pattern(arg_file, str);
|
2021-01-17 09:11:11 -08:00
|
|
|
if (!p) {
|
|
|
|
fprint_line(stdout, arg_file, str, arg_file->end,
|
|
|
|
"Failed to compile this part of the argument");
|
|
|
|
return 1;
|
|
|
|
}
|
2021-01-15 18:23:18 -08:00
|
|
|
check(npatterns == 0, "Cannot define multiple patterns");
|
2021-01-15 12:40:19 -08:00
|
|
|
defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p);
|
|
|
|
++npatterns;
|
|
|
|
str = p->end;
|
|
|
|
}
|
2021-01-17 09:11:11 -08:00
|
|
|
str = after_spaces(str);
|
2021-01-15 12:40:19 -08:00
|
|
|
str = strchr(str, ';') ? strchr(str, ';') + 1 : str;
|
|
|
|
}
|
2020-09-13 23:31:38 -07:00
|
|
|
} else if (FLAG("--pattern-string") || FLAG("-P")) {
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", flag);
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *p = bp_stringpattern(arg_file, arg_file->contents);
|
2020-09-28 22:02:00 -07:00
|
|
|
check(p, "Pattern failed to compile: %s", flag);
|
2021-01-13 18:56:22 -08:00
|
|
|
defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p);
|
2020-09-12 18:20:13 -07:00
|
|
|
++npatterns;
|
2021-01-15 01:19:10 -08:00
|
|
|
} else if (FLAG("--context") || FLAG("-c")) {
|
|
|
|
if (streq(flag, "all"))
|
2021-01-15 02:05:17 -08:00
|
|
|
context_lines = ALL_CONTEXT;
|
|
|
|
else if (streq(flag, "none"))
|
|
|
|
context_lines = 0;
|
2021-01-15 01:19:10 -08:00
|
|
|
else
|
2021-01-15 02:05:17 -08:00
|
|
|
context_lines = (int)strtol(flag, NULL, 10);
|
2020-12-14 22:32:47 -08:00
|
|
|
} else if (argv[i][0] == '-' && argv[i][1] && argv[i][1] != '-') { // single-char flags
|
|
|
|
for (char *c = &argv[i][1]; *c; ++c) {
|
|
|
|
switch (*c) {
|
2020-12-17 16:23:29 -08:00
|
|
|
case 'h': goto flag_help; // -h
|
2021-01-15 02:05:17 -08:00
|
|
|
case 'v': verbose = 1; break; // -v
|
2021-01-17 09:21:58 -08:00
|
|
|
case 'G': git = 1; break; // -G
|
2021-01-15 02:05:17 -08:00
|
|
|
case 'e': mode = MODE_EXPLAIN; break; // -e
|
|
|
|
case 'j': mode = MODE_JSON; break; // -j
|
|
|
|
case 'I': mode = MODE_INPLACE; break; // -I
|
2021-01-15 18:23:18 -08:00
|
|
|
case 'C': confirm = CONFIRM_ASK; break; // -C
|
2021-01-15 02:05:17 -08:00
|
|
|
case 'i': ignorecase = 1; break; // -i
|
|
|
|
case 'l': mode = MODE_LISTFILES; break; // -l
|
2020-12-14 22:32:47 -08:00
|
|
|
default:
|
|
|
|
printf("Unrecognized flag: -%c\n\n%s\n", *c, usage);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2020-09-11 02:55:15 -07:00
|
|
|
} else if (argv[i][0] != '-') {
|
|
|
|
if (npatterns > 0) break;
|
2021-01-08 00:57:46 -08:00
|
|
|
// TODO: spoof file with quotation marks for better debugging
|
2021-01-13 18:56:22 -08:00
|
|
|
file_t *arg_file = spoof_file(&loaded_files, "<pattern argument>", argv[i]);
|
2021-01-15 18:38:06 -08:00
|
|
|
pat_t *p = bp_stringpattern(arg_file, arg_file->contents);
|
2020-09-28 22:02:00 -07:00
|
|
|
check(p, "Pattern failed to compile: %s", argv[i]);
|
2021-01-13 18:56:22 -08:00
|
|
|
defs = with_def(defs, arg_file, strlen("pattern"), "pattern", p);
|
2020-09-11 02:55:15 -07:00
|
|
|
++npatterns;
|
2020-09-12 20:05:55 -07:00
|
|
|
} else {
|
2020-09-14 12:39:31 -07:00
|
|
|
printf("Unrecognized flag: %s\n\n%s\n", argv[i], usage);
|
2020-09-12 20:05:55 -07:00
|
|
|
return 1;
|
2020-09-09 22:29:09 -07:00
|
|
|
}
|
|
|
|
}
|
2020-09-07 23:05:38 -07:00
|
|
|
|
2021-01-15 02:05:17 -08:00
|
|
|
if (context_lines == USE_DEFAULT_CONTEXT) context_lines = 1;
|
|
|
|
if (context_lines < 0 && context_lines != ALL_CONTEXT) context_lines = 0;
|
2021-01-15 01:19:10 -08:00
|
|
|
|
|
|
|
if (isatty(STDOUT_FILENO)) {
|
|
|
|
print_color = 1;
|
|
|
|
print_line_numbers = 1;
|
|
|
|
}
|
2020-09-09 22:29:09 -07:00
|
|
|
|
2021-01-15 18:23:18 -08:00
|
|
|
// If any of these signals triggers, and there is a temporary file in use,
|
|
|
|
// be sure to clean it up before exiting.
|
|
|
|
int signals[] = {SIGTERM, SIGINT, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, SIGSEGV, SIGTSTP};
|
|
|
|
struct sigaction sa = {.sa_handler = &sig_handler, .sa_flags = (int)(SA_NODEFER | SA_RESETHAND)};
|
|
|
|
for (size_t i = 0; i < sizeof(signals)/sizeof(signals[0]); i++)
|
|
|
|
sigaction(signals[i], &sa, NULL);
|
|
|
|
|
|
|
|
// Handle exit() calls gracefully:
|
|
|
|
atexit(&cleanup);
|
|
|
|
|
|
|
|
// User input/output is handled through /dev/tty so that normal unix pipes
|
|
|
|
// can work properly while simultaneously asking for user input.
|
|
|
|
if (confirm == CONFIRM_ASK) {
|
|
|
|
tty_in = fopen("/dev/tty", "r");
|
|
|
|
tty_out = fopen("/dev/tty", "w");
|
|
|
|
}
|
|
|
|
|
2021-01-12 18:35:41 -08:00
|
|
|
int found = 0;
|
2021-01-15 02:05:17 -08:00
|
|
|
if (mode == MODE_JSON) printf("[");
|
2021-01-17 09:21:58 -08:00
|
|
|
if (git) {
|
|
|
|
int fds[2];
|
|
|
|
check(pipe(fds) == 0, "Failed to create pipe");
|
|
|
|
pid_t child = fork();
|
|
|
|
check(child != -1, "Failed to fork");
|
|
|
|
if (child == 0) {
|
|
|
|
char **git_args = calloc((size_t)(2+(argc-i)+1), sizeof(char*));
|
|
|
|
int g = 0;
|
|
|
|
git_args[g++] = "git";
|
|
|
|
git_args[g++] = "ls-files";
|
|
|
|
while (i < argc) git_args[g++] = argv[i++];
|
|
|
|
dup2(fds[STDOUT_FILENO], STDOUT_FILENO);
|
|
|
|
close(fds[STDIN_FILENO]);
|
|
|
|
execvp("git", git_args);
|
|
|
|
_exit(1);
|
|
|
|
}
|
|
|
|
close(fds[STDOUT_FILENO]);
|
|
|
|
char path[PATH_MAX+2] = {0}; // path + \n + \0
|
|
|
|
while (read(fds[STDIN_FILENO], path, PATH_MAX+1) > 0) { // Iterate over chunks
|
|
|
|
for (char *nl; (nl = strchr(path, '\n')); ) { // Iterate over nl-terminated lines
|
|
|
|
*nl = '\0';
|
|
|
|
found += process_file(defs, path, pattern);
|
|
|
|
memmove(path, nl+1, sizeof(path)-(size_t)(nl+1-path));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
close(fds[STDIN_FILENO]);
|
|
|
|
int status;
|
|
|
|
waitpid(child, &status, 0);
|
|
|
|
check(WIFEXITED(status) && WEXITSTATUS(status) == 0,
|
|
|
|
"`git --ls-files` failed. Do you have git installed?");
|
|
|
|
} else if (i < argc) {
|
2020-09-13 15:55:09 -07:00
|
|
|
// Files pass in as command line args:
|
|
|
|
for (int nfiles = 0; i < argc; nfiles++, i++) {
|
2021-01-15 02:05:17 -08:00
|
|
|
found += process_file(defs, argv[i], pattern);
|
2020-09-11 02:55:15 -07:00
|
|
|
}
|
2020-09-13 15:55:09 -07:00
|
|
|
} else if (isatty(STDIN_FILENO)) {
|
|
|
|
// No files, no piped in input, so use * **/*:
|
|
|
|
glob_t globbuf;
|
|
|
|
glob("*", 0, NULL, &globbuf);
|
|
|
|
glob("**/*", GLOB_APPEND, NULL, &globbuf);
|
|
|
|
for (size_t i = 0; i < globbuf.gl_pathc; i++) {
|
2021-01-15 01:19:10 -08:00
|
|
|
if (is_text_file(globbuf.gl_pathv[i]))
|
2021-01-15 02:05:17 -08:00
|
|
|
found += process_file(defs, globbuf.gl_pathv[i], pattern);
|
2020-09-11 02:55:15 -07:00
|
|
|
}
|
2020-09-13 15:55:09 -07:00
|
|
|
globfree(&globbuf);
|
|
|
|
} else {
|
|
|
|
// Piped in input:
|
2021-01-15 02:05:17 -08:00
|
|
|
found += process_file(defs, NULL, pattern);
|
2020-09-07 23:05:38 -07:00
|
|
|
}
|
2021-01-15 02:05:17 -08:00
|
|
|
if (mode == MODE_JSON) printf("]\n");
|
2020-09-07 23:05:38 -07:00
|
|
|
|
2021-01-15 18:23:18 -08:00
|
|
|
if (tty_out) fclose(tty_out);
|
|
|
|
if (tty_in) fclose(tty_in);
|
|
|
|
|
2021-01-14 19:43:30 -08:00
|
|
|
#ifdef DEBUG_HEAP
|
2021-01-14 19:21:31 -08:00
|
|
|
// This code frees up all residual heap-allocated memory. Since the program
|
|
|
|
// is about to exit, this step is unnecessary. However, it is useful for
|
|
|
|
// tracking down memory leaks.
|
2021-01-13 01:48:36 -08:00
|
|
|
free_defs(&defs, NULL);
|
2021-01-13 18:56:22 -08:00
|
|
|
while (loaded_files) {
|
|
|
|
file_t *next = loaded_files->next;
|
|
|
|
destroy_file(&loaded_files);
|
|
|
|
loaded_files = next;
|
|
|
|
}
|
2021-01-14 19:21:31 -08:00
|
|
|
free_all_matches();
|
|
|
|
#endif
|
2021-01-13 01:48:36 -08:00
|
|
|
|
2021-01-12 18:35:41 -08:00
|
|
|
return (found > 0) ? 0 : 1;
|
2020-09-07 23:05:38 -07:00
|
|
|
}
|
2020-09-07 23:34:41 -07:00
|
|
|
|
2020-09-11 01:38:44 -07:00
|
|
|
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
|