475 lines
9.5 KiB
C
475 lines
9.5 KiB
C
/*
|
|
* Copyright 2008-2013 Various Authors
|
|
* Copyright 2005 Timo Hirvonen
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "filters.h"
|
|
#include "cmdline.h"
|
|
#include "expr.h"
|
|
#include "window.h"
|
|
#include "search.h"
|
|
#include "uchar.h"
|
|
#include "lib.h"
|
|
#include "misc.h"
|
|
#include "file.h"
|
|
#include "ui_curses.h"
|
|
#include "xmalloc.h"
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
struct window *filters_win;
|
|
struct searchable *filters_searchable;
|
|
LIST_HEAD(filters_head);
|
|
|
|
static const char *recursive_filter;
|
|
|
|
static inline void filter_entry_to_iter(struct filter_entry *e, struct iter *iter)
|
|
{
|
|
iter->data0 = &filters_head;
|
|
iter->data1 = e;
|
|
iter->data2 = NULL;
|
|
}
|
|
|
|
static GENERIC_ITER_PREV(filters_get_prev, struct filter_entry, node)
|
|
static GENERIC_ITER_NEXT(filters_get_next, struct filter_entry, node)
|
|
|
|
static int filters_search_get_current(void *data, struct iter *iter, enum search_direction dir)
|
|
{
|
|
return window_get_sel(filters_win, iter);
|
|
}
|
|
|
|
static int filters_search_matches(void *data, struct iter *iter, const char *text)
|
|
{
|
|
char **words = get_words(text);
|
|
int matched = 0;
|
|
|
|
if (words[0] != NULL) {
|
|
struct filter_entry *e;
|
|
int i;
|
|
|
|
e = iter_to_filter_entry(iter);
|
|
for (i = 0; ; i++) {
|
|
if (words[i] == NULL) {
|
|
window_set_sel(filters_win, iter);
|
|
matched = 1;
|
|
break;
|
|
}
|
|
if (u_strcasestr(e->name, words[i]) == NULL)
|
|
break;
|
|
}
|
|
}
|
|
free_str_array(words);
|
|
return matched;
|
|
}
|
|
|
|
static const struct searchable_ops filters_search_ops = {
|
|
.get_prev = filters_get_prev,
|
|
.get_next = filters_get_next,
|
|
.get_current = filters_search_get_current,
|
|
.matches = filters_search_matches
|
|
};
|
|
|
|
static void free_filter(struct filter_entry *e)
|
|
{
|
|
free(e->name);
|
|
free(e->filter);
|
|
free(e);
|
|
}
|
|
|
|
static struct filter_entry *find_filter(const char *name)
|
|
{
|
|
struct filter_entry *e;
|
|
|
|
list_for_each_entry(e, &filters_head, node) {
|
|
if (strcmp(e->name, name) == 0)
|
|
return e;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *get_filter(const char *name)
|
|
{
|
|
struct filter_entry *e = find_filter(name);
|
|
|
|
if (e) {
|
|
if (e->visited) {
|
|
recursive_filter = e->name;
|
|
return NULL;
|
|
}
|
|
e->visited = 1;
|
|
return e->filter;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void edit_sel_filter(void)
|
|
{
|
|
struct iter sel;
|
|
struct filter_entry *e;
|
|
char buf[512];
|
|
|
|
if (!window_get_sel(filters_win, &sel))
|
|
return;
|
|
|
|
e = iter_to_filter_entry(&sel);
|
|
snprintf(buf, sizeof(buf), "fset %s=%s", e->name, e->filter);
|
|
cmdline_set_text(buf);
|
|
enter_command_mode();
|
|
}
|
|
|
|
void filters_activate(int win_activate)
|
|
{
|
|
struct filter_entry *f;
|
|
struct expr *e, *expr = NULL;
|
|
int unchanged = 1;
|
|
|
|
/* if no pending selection is to apply, edit currently select filter */
|
|
list_for_each_entry(f, &filters_head, node) {
|
|
if (f->act_stat != f->sel_stat)
|
|
unchanged = 0;
|
|
}
|
|
|
|
if (unchanged) {
|
|
if (win_activate)
|
|
edit_sel_filter();
|
|
else
|
|
return;
|
|
}
|
|
|
|
/* mark visited and AND together all selected filters
|
|
* mark any other filters unvisited */
|
|
list_for_each_entry(f, &filters_head, node) {
|
|
f->visited = 0;
|
|
if (f->sel_stat == FS_IGNORE)
|
|
continue;
|
|
|
|
f->visited = 1;
|
|
e = expr_parse(f->filter);
|
|
if (e == NULL) {
|
|
error_msg("error parsing filter %s: %s", f->name, expr_error());
|
|
if (expr)
|
|
expr_free(expr);
|
|
return;
|
|
}
|
|
|
|
if (f->sel_stat == FS_NO) {
|
|
/* add ! */
|
|
struct expr *not = xnew(struct expr, 1);
|
|
|
|
not->type = EXPR_NOT;
|
|
not->key = NULL;
|
|
not->left = e;
|
|
not->right = NULL;
|
|
e = not;
|
|
}
|
|
if (expr == NULL) {
|
|
expr = e;
|
|
} else {
|
|
struct expr *and = xnew(struct expr, 1);
|
|
|
|
and->type = EXPR_AND;
|
|
and->key = NULL;
|
|
and->left = expr;
|
|
and->right = e;
|
|
expr->parent = and;
|
|
e->parent = and;
|
|
expr = and;
|
|
}
|
|
}
|
|
|
|
recursive_filter = NULL;
|
|
if (expr && expr_check_leaves(&expr, get_filter)) {
|
|
if (recursive_filter) {
|
|
error_msg("recursion detected in filter %s", recursive_filter);
|
|
} else {
|
|
error_msg("error parsing filter: %s", expr_error());
|
|
}
|
|
expr_free(expr);
|
|
return;
|
|
}
|
|
|
|
/* update active flag */
|
|
list_for_each_entry(f, &filters_head, node) {
|
|
f->act_stat = f->sel_stat;
|
|
}
|
|
lib_set_filter(expr);
|
|
filters_win->changed = 1;
|
|
}
|
|
|
|
static int for_each_name(const char *str, int (*cb)(const char *name, int sel_stat))
|
|
{
|
|
char buf[64];
|
|
int s, e, len;
|
|
|
|
e = 0;
|
|
do {
|
|
int sel_stat = FS_YES;
|
|
|
|
s = e;
|
|
while (str[s] == ' ')
|
|
s++;
|
|
if (str[s] == '!') {
|
|
sel_stat = FS_NO;
|
|
s++;
|
|
}
|
|
e = s;
|
|
while (str[e] && str[e] != ' ')
|
|
e++;
|
|
|
|
len = e - s;
|
|
if (len == 0)
|
|
return 0;
|
|
if (len >= sizeof(buf)) {
|
|
error_msg("filter name too long");
|
|
return -1;
|
|
}
|
|
|
|
memcpy(buf, str + s, len);
|
|
buf[len] = 0;
|
|
|
|
if (cb(buf, sel_stat))
|
|
return -1;
|
|
} while (1);
|
|
}
|
|
|
|
static int ensure_filter_name(const char *name, int sel_stat)
|
|
{
|
|
if (find_filter(name) == NULL) {
|
|
error_msg("no such filter %s", name);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int select_filter(const char *name, int sel_stat)
|
|
{
|
|
struct filter_entry *e = find_filter(name);
|
|
|
|
e->sel_stat = sel_stat;
|
|
return 0;
|
|
}
|
|
|
|
void filters_activate_names(const char *str)
|
|
{
|
|
struct filter_entry *f;
|
|
|
|
/* first validate all filter names */
|
|
if (str && for_each_name(str, ensure_filter_name))
|
|
return;
|
|
|
|
/* mark all filters unselected */
|
|
list_for_each_entry(f, &filters_head, node)
|
|
f->sel_stat = FS_IGNORE;
|
|
|
|
/* select the filters */
|
|
if (str)
|
|
for_each_name(str, select_filter);
|
|
|
|
/* activate selected */
|
|
filters_activate(0);
|
|
}
|
|
|
|
void filters_toggle_filter(void)
|
|
{
|
|
struct iter iter;
|
|
|
|
if (window_get_sel(filters_win, &iter)) {
|
|
struct filter_entry *e;
|
|
|
|
e = iter_to_filter_entry(&iter);
|
|
e->sel_stat = (e->sel_stat + 1) % 3;
|
|
filters_win->changed = 1;
|
|
}
|
|
}
|
|
|
|
void filters_delete_filter(void)
|
|
{
|
|
struct iter iter;
|
|
|
|
if (window_get_sel(filters_win, &iter)) {
|
|
struct filter_entry *e;
|
|
|
|
e = iter_to_filter_entry(&iter);
|
|
if (yes_no_query("Delete filter '%s'? [y/N]", e->name) == UI_QUERY_ANSWER_YES) {
|
|
window_row_vanishes(filters_win, &iter);
|
|
list_del(&e->node);
|
|
free_filter(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int validate_filter_name(const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; name[i]; i++) {
|
|
if (isalnum((unsigned char)name[i]))
|
|
continue;
|
|
if (name[i] == '_' || name[i] == '-')
|
|
continue;
|
|
return 0;
|
|
}
|
|
return i != 0;
|
|
}
|
|
|
|
static void do_filters_set_filter(const char *keyval)
|
|
{
|
|
const char *eq = strchr(keyval, '=');
|
|
char *key, *val;
|
|
struct expr *expr;
|
|
struct filter_entry *new;
|
|
struct list_head *item;
|
|
|
|
if (eq == NULL) {
|
|
if (ui_initialized)
|
|
error_msg("invalid argument ('key=value' expected)");
|
|
return;
|
|
}
|
|
key = xstrndup(keyval, eq - keyval);
|
|
val = xstrdup(eq + 1);
|
|
if (!validate_filter_name(key)) {
|
|
if (ui_initialized)
|
|
error_msg("invalid filter name (can only contain 'a-zA-Z0-9_-' characters)");
|
|
free(key);
|
|
free(val);
|
|
return;
|
|
}
|
|
expr = expr_parse(val);
|
|
if (expr == NULL) {
|
|
if (ui_initialized)
|
|
error_msg("error parsing filter %s: %s", val, expr_error());
|
|
free(key);
|
|
free(val);
|
|
return;
|
|
}
|
|
expr_free(expr);
|
|
|
|
new = xnew(struct filter_entry, 1);
|
|
new->name = key;
|
|
new->filter = val;
|
|
new->act_stat = FS_IGNORE;
|
|
new->sel_stat = FS_IGNORE;
|
|
|
|
/* add or replace filter */
|
|
list_for_each(item, &filters_head) {
|
|
struct filter_entry *e = container_of(item, struct filter_entry, node);
|
|
int res = strcmp(key, e->name);
|
|
|
|
if (res < 0)
|
|
break;
|
|
if (res == 0) {
|
|
/* replace */
|
|
struct iter iter;
|
|
|
|
new->sel_stat = e->sel_stat;
|
|
if (ui_initialized) {
|
|
filter_entry_to_iter(e, &iter);
|
|
window_row_vanishes(filters_win, &iter);
|
|
}
|
|
item = item->next;
|
|
list_del(&e->node);
|
|
free_filter(e);
|
|
break;
|
|
}
|
|
}
|
|
/* add before item */
|
|
list_add_tail(&new->node, item);
|
|
if (ui_initialized)
|
|
window_changed(filters_win);
|
|
}
|
|
|
|
void filters_init(void)
|
|
{
|
|
struct iter iter;
|
|
|
|
filters_win = window_new(filters_get_prev, filters_get_next);
|
|
window_set_contents(filters_win, &filters_head);
|
|
window_changed(filters_win);
|
|
|
|
iter.data0 = &filters_head;
|
|
iter.data1 = NULL;
|
|
iter.data2 = NULL;
|
|
filters_searchable = searchable_new(NULL, &iter, &filters_search_ops);
|
|
}
|
|
|
|
void filters_exit(void)
|
|
{
|
|
searchable_free(filters_searchable);
|
|
window_free(filters_win);
|
|
}
|
|
|
|
void filters_set_filter(const char *keyval)
|
|
{
|
|
do_filters_set_filter(keyval);
|
|
}
|
|
|
|
struct expr *parse_filter(const char *val)
|
|
{
|
|
struct expr *e = NULL;
|
|
struct filter_entry *f;
|
|
|
|
if (val) {
|
|
e = expr_parse(val);
|
|
if (e == NULL) {
|
|
error_msg("error parsing filter %s: %s", val, expr_error());
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* mark all unvisited so that we can check recursion */
|
|
list_for_each_entry(f, &filters_head, node)
|
|
f->visited = 0;
|
|
|
|
recursive_filter = NULL;
|
|
if (e && expr_check_leaves(&e, get_filter)) {
|
|
if (recursive_filter) {
|
|
error_msg("recursion detected in filter %s", recursive_filter);
|
|
} else {
|
|
error_msg("error parsing filter: %s", expr_error());
|
|
}
|
|
expr_free(e);
|
|
return NULL;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
void filters_set_anonymous(const char *val)
|
|
{
|
|
struct filter_entry *f;
|
|
struct expr *e = NULL;
|
|
|
|
if (val) {
|
|
e = parse_filter(val);
|
|
if (e == NULL)
|
|
return;
|
|
}
|
|
|
|
/* deactive all filters */
|
|
list_for_each_entry(f, &filters_head, node)
|
|
f->act_stat = FS_IGNORE;
|
|
|
|
lib_set_filter(e);
|
|
|
|
filters_win->changed = 1;
|
|
}
|
|
|
|
void filters_set_live(const char *val)
|
|
{
|
|
lib_set_live_filter(val);
|
|
update_filterline();
|
|
}
|