* Added simple filters, e.g. foo bar -> artist="*foo*"|artist="*bar*",
configurable using variable simple_search.
* Made changed suggested by Gregory, e.g. modified short keys.
Signed-off-by: Johannes Weißl <***@molb.org>
---
expr.c | 307 ++++++++++++++++++++++++++++++++++++++++++++-----------------
expr.h | 2 +
options.c | 17 ++++
3 files changed, 243 insertions(+), 83 deletions(-)
diff --git a/expr.c b/expr.c
index 9af7bcc..23adc21 100644
--- a/expr.c
+++ b/expr.c
@@ -54,6 +54,12 @@ struct token {
char str[0];
};
+enum filter_type {
+ FI_SIMPLE,
+ FI_SHORT,
+ FI_LONG
+};
+
/* same order as TOK_* */
static const char specials[NR_SPECIALS] = "!<>=&|()";
@@ -386,40 +392,38 @@ static const struct {
char short_key;
const char *long_key;
} map_short2long[] = {
- { 'A', "album" },
- { 'D', "discnumber" },
- { 'N', "discnumber" },
- { 'T', "tag", },
- { 'a', "artist" },
- { 'c', "comment" },
- { 'd', "date" },
- { 'f', "filename" },
- { 'g', "genre" },
- { 'l', "duration" },
- { 'n', "tracknumber" },
- { 's', "stream" },
- { 't', "title" },
- { 'y', "date" },
- { '\0', NULL },
+ { 'D', "discnumber" },
+ { 'T', "tag", },
+ { 'a', "artist" },
+ { 'c', "comment" },
+ { 'd', "duration" },
+ { 'f', "filename" },
+ { 'g', "genre" },
+ { 'l', "album" },
+ { 'n', "tracknumber" },
+ { 's', "stream" },
+ { 't', "title" },
+ { 'y', "date" },
+ { '\0', NULL },
};
static const struct {
const char *key;
enum expr_type type;
} builtin[] = {
- { "album", EXPR_STR },
- { "artist", EXPR_STR },
- { "comment", EXPR_STR },
- { "date", EXPR_INT },
- { "discnumber", EXPR_INT },
- { "duration", EXPR_INT },
- { "filename", EXPR_STR },
- { "genre", EXPR_STR },
- { "stream", EXPR_BOOL },
- { "tag", EXPR_BOOL },
- { "title", EXPR_STR },
- { "tracknumber",EXPR_INT },
- { NULL, -1 },
+ { "album", EXPR_STR },
+ { "artist", EXPR_STR },
+ { "comment", EXPR_STR },
+ { "date", EXPR_INT },
+ { "discnumber", EXPR_INT },
+ { "duration", EXPR_INT },
+ { "filename", EXPR_STR },
+ { "genre", EXPR_STR },
+ { "stream", EXPR_BOOL },
+ { "tag", EXPR_BOOL },
+ { "title", EXPR_STR },
+ { "tracknumber",EXPR_INT },
+ { NULL, -1 },
};
static const char *lookup_long_key(char c)
@@ -432,24 +436,45 @@ static const char *lookup_long_key(char c)
return NULL;
}
-static int lookup_builtin(const char *key)
+static enum expr_type lookup_key_type(const char *key)
{
int i;
for (i = 0; builtin[i].key; i++) {
int cmp = strcmp(key, builtin[i].key);
if (cmp == 0)
- return i;
+ return builtin[i].type;
if (cmp < 0)
break;
}
return -1;
}
+static unsigned long stack4_new(void)
+{
+ return 0;
+}
+static void stack4_push(unsigned long *s, unsigned long e)
+{
+ *s = (*s << 4) | e;
+}
+static void stack4_pop(unsigned long *s)
+{
+ *s = *s >> 4;
+}
+static unsigned long stack4_top(unsigned long s)
+{
+ return s & 0xf;
+}
+static void stack4_replace_top(unsigned long *s, unsigned long e)
+{
+ *s = (*s & ~0xf) | e;
+}
+
static char *expand_short_expr(const char *expr_short)
{
- /* state space, can contain maximal 16 states */
+ /* state space, can contain maximal 15 states */
enum state_type {
- ST_SKIP_SPACE,
+ ST_SKIP_SPACE = 1,
ST_TOP,
ST_EXPECT_KEY,
ST_EXPECT_OP,
@@ -464,46 +489,47 @@ static char *expand_short_expr(const char *expr_short)
size_t len_expr_short = strlen(expr_short);
/* worst case blowup of expr_short is 29/5 (e.g. ~n1-2), so take x6 */
- char *out = xmalloc(len_expr_short * 6);
+ char *out = xnew(char, len_expr_short * 6);
char *num = NULL;
size_t i, i_num = 0, k = 0;
const char *key;
- int key_index, level = 0;
- /* used as state-stack, can contain 32/4 = 8 states */
- uint32_t state = (ST_TOP << 4) | ST_SKIP_SPACE;
+ int level = 0;
+ enum expr_type etype;
+ /* used as state-stack, can contain at least 32/4 = 8 states */
+ unsigned long state_stack = stack4_new();
+ stack4_push(&state_stack, ST_TOP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
/* include terminal '\0' to recognize end of string */
for (i = 0; i <= len_expr_short; i++) {
unsigned char c = expr_short[i];
- switch (state & 0xf) {
+ switch (stack4_top(state_stack)) {
case ST_SKIP_SPACE:
- if (!isspace(c)) {
- state >>= 4;
+ if (c != ' ') {
+ stack4_pop(&state_stack);
i--;
}
break;
case ST_TOP:
switch (c) {
case '~':
- state = (state << 4) | ST_EXPECT_OP;
- state = (state << 4) | ST_SKIP_SPACE;
- state = (state << 4) | ST_EXPECT_KEY;
+ stack4_push(&state_stack, ST_EXPECT_OP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+ stack4_push(&state_stack, ST_EXPECT_KEY);
break;
case '(':
level++;
+ /* Fall through */
case '!':
+ case '|':
out[k++] = c;
- state = (state << 4) | ST_SKIP_SPACE;
+ stack4_push(&state_stack, ST_SKIP_SPACE);
break;
case ')':
level--;
out[k++] = c;
- state = (state << 4) | ST_EXPECT_OP;
- state = (state << 4) | ST_SKIP_SPACE;
- break;
- case '|':
- out[k++] = c;
- state = (state << 4) | ST_SKIP_SPACE;
+ stack4_push(&state_stack, ST_EXPECT_OP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
break;
case '\0':
if (level > 0) {
@@ -513,46 +539,48 @@ static char *expand_short_expr(const char *expr_short)
out[k++] = c;
break;
default:
- set_error("unexpected '%c'", expr_short, c);
+ set_error("unexpected '%c'", c);
goto error_exit;
}
break;
case ST_EXPECT_KEY:
- state >>= 4;
+ stack4_pop(&state_stack);
key = lookup_long_key(c);
if (!key) {
- set_error("unkown key ~%c", c);
+ set_error("unkown short key %c", c);
goto error_exit;
}
strcpy(out+k, key);
k += strlen(key);
- key_index = lookup_builtin(key);
- if (builtin[key_index].type == EXPR_INT) {
- state = (state << 4) | ST_EXPECT_INT;
- } else if (builtin[key_index].type == EXPR_STR) {
- state = (state << 4) | ST_EXPECT_STR;
+ etype = lookup_key_type(key);
+ if (etype == EXPR_INT) {
+ stack4_push(&state_stack, ST_EXPECT_INT);
+ } else if (etype == EXPR_STR) {
+ stack4_push(&state_stack, ST_EXPECT_STR);
+ } else {
+ BUG("wrong etype: %d\n", etype);
}
- state = (state << 4) | ST_SKIP_SPACE;
+ stack4_push(&state_stack, ST_SKIP_SPACE);
break;
case ST_EXPECT_OP:
if (c == '~' || c == '(' || c == '!')
out[k++] = '&';
i--;
- state = (state & ~0xf) | ST_SKIP_SPACE;
+ stack4_replace_top(&state_stack, ST_SKIP_SPACE);
break;
case ST_EXPECT_INT:
if (c == '<' || c == '>') {
out[k++] = c;
- state = (state & ~0xf) | ST_IN_INT;
+ stack4_replace_top(&state_stack, ST_IN_INT);
} else if (c == '-') {
out[k++] = '<';
out[k++] = '=';
- state = (state & ~0xf) | ST_IN_INT;
+ stack4_replace_top(&state_stack, ST_IN_INT);
} else if (isdigit(c)) {
if (!num)
- num = xmalloc(len_expr_short);
+ num = xnew(char, len_expr_short);
num[i_num++] = c;
- state = (state & ~0xf) | ST_MEM_INT;
+ stack4_replace_top(&state_stack, ST_MEM_INT);
} else {
set_error("integer expected", expr_short);
goto error_exit;
@@ -563,7 +591,7 @@ static char *expand_short_expr(const char *expr_short)
out[k++] = c;
} else {
i -= 1;
- state >>= 4;
+ stack4_pop(&state_stack);
}
break;
case ST_MEM_INT:
@@ -573,11 +601,11 @@ static char *expand_short_expr(const char *expr_short)
if (c == '-') {
out[k++] = '>';
out[k++] = '=';
- state = (state & ~0xf) | ST_IN_2ND_INT;
+ stack4_replace_top(&state_stack, ST_IN_2ND_INT);
} else {
out[k++] = '=';
i--;
- state >>= 4;
+ stack4_pop(&state_stack);
}
strncpy(out+k, num, i_num);
k += i_num;
@@ -589,7 +617,7 @@ static char *expand_short_expr(const char *expr_short)
num[i_num++] = c;
} else {
i--;
- state >>= 4;
+ stack4_pop(&state_stack);
if (i_num > 0) {
out[k++] = '&';
strcpy(out+k, key);
@@ -604,38 +632,46 @@ static char *expand_short_expr(const char *expr_short)
case ST_EXPECT_STR:
out[k++] = '=';
if (c == '"') {
- state = (state & ~0xf) | ST_IN_QUOTE_STR;
+ stack4_replace_top(&state_stack, ST_IN_QUOTE_STR);
out[k++] = c;
/* out[k++] = '*'; */
} else {
- state = (state & ~0xf) | ST_IN_STR;
+ stack4_replace_top(&state_stack, ST_IN_STR);
out[k++] = '"';
out[k++] = '*';
out[k++] = c;
}
break;
case ST_IN_QUOTE_STR:
- if (c == '"') {
- state >>= 4;
+ if (c == '"' && expr_short[i-1] != '\\') {
+ stack4_pop(&state_stack);
/* out[k++] = '*'; */
}
out[k++] = c;
break;
case ST_IN_STR:
- if (isalnum(c)) {
+ /* isalnum() doesn't work for multi-byte characters */
+ if (c != ' ' && c != ')' && c != '~' && c != '|'
+ && c != '(' && c != '!' && c != '\0') {
out[k++] = c;
} else {
out[k++] = '*';
out[k++] = '"';
i--;
- state >>= 4;
+ stack4_pop(&state_stack);
}
break;
+ default:
+ BUG("state %d not covered", stack4_top(state_stack));
+ break;
}
}
if (num)
free(num);
+
+ d_print("expanded \"%s\" to \"%s\"\n", expr_short, out);
+
return out;
error_exit:
@@ -645,18 +681,117 @@ error_exit:
return NULL;
}
-/* return 1 if str is a short filter expression (e.g. ~a beatles) */
-static int is_short_expr(const char *str)
+static enum filter_type get_filter_type(const char *str)
{
int i;
for (i = 0; str[i]; i++) {
- unsigned char c = str[i];
- if (c == '~')
- return 1;
- else if (!isspace(c) && c != '(' && c != '!')
- return 0;
+ if (str[i] == '~')
+ return FI_SHORT;
+ if (str[i] == '=')
+ return FI_LONG;
}
- return 0;
+ return FI_SIMPLE;
+}
+
+static char **get_quoted_words(const char *text)
+{
+ char **words;
+ int i, j, count, in_quote;
+
+ while (*text == ' ')
+ text++;
+
+ count = 0;
+ i = 0;
+ while (text[i]) {
+ count++;
+ in_quote = (text[i] == '"') ? 1 : 0;
+ if (in_quote)
+ i++;
+
+ while (text[i] && ((!in_quote && text[i] != ' ') || (in_quote && (text[i] != '"' || text[i-1] == '\\'))))
+ i++;
+ if (in_quote)
+ i++;
+ while (text[i] == ' ')
+ i++;
+ }
+ words = xnew(char *, count + 1);
+
+ i = 0;
+ j = 0;
+ while (text[i]) {
+ int start = i;
+ in_quote = (text[i] == '"') ? 1 : 0;
+ if (in_quote)
+ i++;
+
+ while (text[i] && ((!in_quote && text[i] != ' ') || (in_quote && (text[i] != '"' || text[i-1] == '\\'))))
+ i++;
+ if (in_quote) {
+ i++;
+ words[j++] = xstrndup(text + start, i - start);
+ } else {
+ char *tmp = xnew(char, i - start + 4 + 1);
+ memcpy(tmp + 0, "\"*", 2);
+ memcpy(tmp + 2, text + start, i - start);
+ memcpy(tmp + 2 + i - start, "*\"", 3);
+ words[j++] = tmp;
+ }
+
+ while (text[i] == ' ')
+ i++;
+ }
+ words[j] = NULL;
+ return words;
+}
+
+static char *expand_simple_expr(const char *expr_simple)
+{
+ size_t i, k = 0, n_words, max_out_size, repl_count;
+ char **words, *out, *tmp;
+
+ words = get_quoted_words(expr_simple);
+ for (n_words = 0; words[n_words]; n_words++)
+ ;
+
+ tmp = simple_search;
+ for (repl_count = 0; (tmp = strstr(tmp, "%s")); repl_count++)
+ tmp += 2;
+
+ max_out_size = n_words * (repl_count * (strlen(expr_simple) + 2) + strlen(simple_search) + 3) + 1;
+ out = xnew(char, max_out_size);
+
+ for (i = 0; words[i]; i++) {
+ char *word = words[i];
+ const char *hit;
+ out[k++] = '(';
+ tmp = simple_search;
+ while ((hit = strstr(tmp, "%s"))) {
+ memcpy(out+k, tmp, hit - tmp);
+ k += hit - tmp;
+ tmp += hit - tmp + 2;
+ memcpy(out+k, word, strlen(word));
+ k += strlen(word);
+ }
+ memcpy(out+k, tmp, strlen(tmp));
+ k += strlen(tmp);
+ out[k++] = ')';
+ out[k++] = '|';
+ }
+ out[k > 0 ? k-1 : k] = '\0';
+
+ free_str_array(words);
+
+ d_print("expanded \"%s\" to \"%s\"\n", expr_simple, out);
+
+ if (get_filter_type(out) == FI_SHORT) {
+ char *new_out = expand_short_expr(out);
+ free(out);
+ return new_out;
+ }
+
+ return out;
}
struct expr *expr_parse(const char *str)
@@ -664,7 +799,8 @@ struct expr *expr_parse(const char *str)
LIST_HEAD(head);
struct expr *root = NULL;
struct list_head *item;
- char *new_str = NULL;
+ const char *new_str = NULL;
+ enum filter_type ftype;
int i;
for (i = 0; str[i]; i++) {
@@ -679,7 +815,12 @@ struct expr *expr_parse(const char *str)
return NULL;
}
- if (is_short_expr(str)) {
+ ftype = get_filter_type(str);
+ if (ftype == FI_SIMPLE) {
+ new_str = expand_simple_expr(str);
+ if (!new_str)
+ return NULL;
+ } else if (ftype == FI_SHORT) {
new_str = expand_short_expr(str);
if (!new_str)
return NULL;
diff --git a/expr.h b/expr.h
index 56032f2..dfbde65 100644
--- a/expr.h
+++ b/expr.h
@@ -11,6 +11,8 @@
enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT, OP_NE };
#define NR_OPS (OP_NE + 1)
+extern char *simple_search;
+
enum expr_type {
EXPR_AND,
EXPR_OR,
diff --git a/options.c b/options.c
index 45696f1..07ffa62 100644
--- a/options.c
+++ b/options.c
@@ -36,6 +36,7 @@
char *output_plugin = NULL;
char *status_display_program = NULL;
+char *simple_search = NULL;
char *server_password;
int auto_reshuffle = 0;
int confirm_run = 1;
@@ -366,6 +367,20 @@ err:
error_msg("two integers in range 0..100 expected");
}
+static void get_simple_search(unsigned int id, char *buf)
+{
+ if (simple_search)
+ strcpy(buf, simple_search);
+}
+
+static void set_simple_search(unsigned int id, const char *buf)
+{
+ free(simple_search);
+ simple_search = NULL;
+ if (buf[0])
+ simple_search = xstrdup(buf);
+}
+
static void get_status_display_program(unsigned int id, char *buf)
{
if (status_display_program)
@@ -846,6 +861,7 @@ static const struct {
DT(show_remaining_time)
DT(set_term_title)
DT(shuffle)
+ DN(simple_search)
DT(softvol)
DN(softvol_state)
DN(status_display_program)
@@ -897,6 +913,7 @@ static const struct {
{ "lib_sort" , "artist album discnumber tracknumber title filename" },
{ "pl_sort", "" },
{ "id3_default_charset","ISO-8859-1" },
+ { "simple_search", "artist=%s|album=%s" },
{ NULL, NULL }
};
--
1.6.6.1