Squashed 'suckless/clipmenu/' content from commit e0ec3bc6
git-subtree-dir: suckless/clipmenu git-subtree-split: e0ec3bc6608ecc70ed17e8da76e27bf90b1e8e90
This commit is contained in:
commit
cb051ead76
32 changed files with 5760 additions and 0 deletions
162
src/clipdel.c
Normal file
162
src/clipdel.c
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#include <fcntl.h>
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "store.h"
|
||||
#include "util.h"
|
||||
|
||||
/**
|
||||
* The deletion mode for clipdel operations.
|
||||
*/
|
||||
enum delete_mode {
|
||||
DELETE_DRY_RUN,
|
||||
DELETE_REAL,
|
||||
};
|
||||
|
||||
/**
|
||||
* The match type for clipdel operations.
|
||||
*/
|
||||
enum match_type {
|
||||
MATCH_REGEX,
|
||||
MATCH_LITERAL,
|
||||
MATCH_COUNT_NEWEST,
|
||||
MATCH_COUNT_OLDEST,
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds the application state for a clipdel operation in preparation for
|
||||
* passing it as private data to the cs_remove callback.
|
||||
*/
|
||||
struct clipdel_state {
|
||||
enum delete_mode mode;
|
||||
enum match_type type;
|
||||
bool invert_match;
|
||||
union {
|
||||
regex_t rgx;
|
||||
const char *needle;
|
||||
struct {
|
||||
uint64_t current;
|
||||
uint64_t num_delete;
|
||||
} count;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for cs_remove. In order for the delete to actually happen, we must
|
||||
* be running DELETE_REAL.
|
||||
*/
|
||||
static enum cs_remove_action _nonnull_ remove_if_match(uint64_t hash _unused_,
|
||||
const char *line,
|
||||
void *private) {
|
||||
struct clipdel_state *state = private;
|
||||
int ret;
|
||||
bool matches;
|
||||
switch (state->type) {
|
||||
case MATCH_LITERAL:
|
||||
matches = strstr(line, state->needle) != NULL;
|
||||
break;
|
||||
case MATCH_REGEX:
|
||||
ret = regexec(&state->rgx, line, 0, NULL, 0);
|
||||
expect(ret == 0 || ret == REG_NOMATCH);
|
||||
matches = ret == 0;
|
||||
break;
|
||||
case MATCH_COUNT_NEWEST:
|
||||
case MATCH_COUNT_OLDEST:
|
||||
if (state->count.current < state->count.num_delete) {
|
||||
matches = true;
|
||||
++state->count.current;
|
||||
} else {
|
||||
matches = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
die("unreachable\n");
|
||||
}
|
||||
|
||||
bool wants_del = state->invert_match ? !matches : matches;
|
||||
if (wants_del) {
|
||||
puts(line);
|
||||
}
|
||||
|
||||
return state->mode == DELETE_REAL && wants_del ? CS_ACTION_REMOVE
|
||||
: CS_ACTION_KEEP;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char usage[] = "Usage: clipdel [-d] [-F] [-N] [-n] [-v] pattern";
|
||||
|
||||
_drop_(config_free) struct config cfg = setup("clipdel");
|
||||
|
||||
struct clipdel_state state = {0};
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "dFNnvh")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
state.mode = DELETE_REAL;
|
||||
break;
|
||||
case 'F':
|
||||
state.type = MATCH_LITERAL;
|
||||
break;
|
||||
case 'N':
|
||||
state.type = MATCH_COUNT_OLDEST;
|
||||
break;
|
||||
case 'n':
|
||||
state.type = MATCH_COUNT_NEWEST;
|
||||
break;
|
||||
case 'v':
|
||||
state.invert_match = true;
|
||||
break;
|
||||
case 'h':
|
||||
exec_man();
|
||||
break;
|
||||
default:
|
||||
die("%s\n", usage);
|
||||
}
|
||||
}
|
||||
|
||||
die_on(optind >= argc, "%s\n", usage);
|
||||
|
||||
_drop_(close) int content_dir_fd = open(get_cache_dir(&cfg), O_RDONLY);
|
||||
_drop_(close) int snip_fd =
|
||||
open(get_line_cache_path(&cfg), O_RDWR | O_CREAT, 0600);
|
||||
expect(content_dir_fd >= 0 && snip_fd >= 0);
|
||||
|
||||
_drop_(cs_destroy) struct clip_store cs;
|
||||
expect(cs_init(&cs, snip_fd, content_dir_fd) == 0);
|
||||
|
||||
enum cs_iter_direction direction = CS_ITER_OLDEST_FIRST;
|
||||
switch (state.type) {
|
||||
case MATCH_REGEX:
|
||||
die_on(regcomp(&state.rgx, argv[optind], REG_EXTENDED | REG_NOSUB),
|
||||
"Could not compile regex\n");
|
||||
break;
|
||||
case MATCH_LITERAL:
|
||||
state.needle = argv[optind];
|
||||
break;
|
||||
case MATCH_COUNT_NEWEST:
|
||||
case MATCH_COUNT_OLDEST:
|
||||
die_on(str_to_uint64(argv[optind], &state.count.num_delete) < 0,
|
||||
"Bad argument, expected integer: %s\n", argv[optind]);
|
||||
state.count.current = 0;
|
||||
if (state.type == MATCH_COUNT_NEWEST) {
|
||||
direction = CS_ITER_NEWEST_FIRST;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
die("unreachable\n");
|
||||
}
|
||||
|
||||
expect(cs_remove(&cs, direction, remove_if_match, &state) == 0);
|
||||
|
||||
if (state.type == MATCH_REGEX) {
|
||||
regfree(&state.rgx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue