Discussion:
short forms for filter expressions
Johannes Weißl
2010-02-11 18:43:45 UTC
Permalink
Hi,

I use mutt as mail reader and I really like limiting and pattern
modifier there. Cmus' filters are great, but for quickly limiting they
are a bit cumbersome. So I wrote a patch to introduce a short form:

~aBeatles -> artist="*Beatles*"
~a "The Who" !~A quick -> artist="The Who"&!album="*quick*"
~d1990-1999 -> date>=1990&date<=1999

They are very similar to mutt [1] and aptitude [2], which also has
short forms of longer expressions. Long filters are not useless though,
because only they can refer to user-defined named filters.

The expansion from short expr to long expr is done in expr_parse.
I think it's pretty useful, what do you think?
The patch is not yet tested too well (wrote it yesterday), so it needs
some testing and maybe cleanup.

Another thing: It would be very cool to have something like "push" in
mutt:

bind common f "push :fi"

or something like that. The idea is that if you press "f" the
filter-prompt is already there.

Yet another thing (don't want to write two mails):
Cmus doesn't work with the new libmpcdec in Debian testing anymore, so I
adapted the mpc module. I would suggest to name it "mpc2" and adjust the
configure script to determine which is used. Any suggestions?


Greetings,
Johannes


[1] http://www.mutt.org/doc/devel/manual.html#patterns
[2] http://algebraicthunk.net/~dburrows/projects/aptitude/doc/en/ch02s03s05.html#tableSearchTermQuickGuide
--
Johannes Weißl
dead_orc
2010-02-11 18:51:04 UTC
Permalink
You scare me.

I just opened vim to hack together something that replaces the normal
search with an anonymous filter, and this patch in combination with your
requested push feature is nearly exactly what I imagined. I'd just like
some something that searches everything (artist, album, title) so I can
really replace the search.

I want to replace the search because I like the tree view but I hate the
search opening all artist trees while searching while typing.

So, I'll happily test the patch. :-)

Thanks for this idea and the patch!
dead_orc
Johannes Weißl
2010-02-11 19:14:37 UTC
Permalink
Hey dead_orc,

haha, funny! I have that idea on my todo list for months, but finally
took the time to code it! I even wanted to implement the simple searches
(as in mutt, if your filter doesn't contain '=' or '~' it is translated
according to a $simple_search [1] variable, e.g.
beatles -> artist="*beatles*"|album="*beatles*"|...). I thought
three modes are maybe too much, but now I will extend the patch!

I almost never use normal search in mutt, since limiting is so much more
useful, so a quick way for limiting would be definitly cool! As you
mentioned, normal searching messes up the tree view...

Greetings,
Johannes

[1] http://www.mutt.org/doc/devel/manual.html#simple-search
Post by dead_orc
You scare me.
I just opened vim to hack together something that replaces the normal
search with an anonymous filter, and this patch in combination with your
requested push feature is nearly exactly what I imagined. I'd just like
some something that searches everything (artist, album, title) so I can
really replace the search.
I want to replace the search because I like the tree view but I hate the
search opening all artist trees while searching while typing.
So, I'll happily test the patch. :-)
Thanks for this idea and the patch!
dead_orc
--
Johannes Weißl
Gregory Petrosyan
2010-02-11 20:25:03 UTC
Permalink
Post by Johannes Weißl
haha, funny! I have that idea on my todo list for months, but finally
took the time to code it! I even wanted to implement the simple searches
(as in mutt, if your filter doesn't contain '=' or '~' it is translated
according to a $simple_search [1] variable, e.g.
beatles -> artist="*beatles*"|album="*beatles*"|...). I thought
three modes are maybe too much, but now I will extend the patch!
I almost never use normal search in mutt, since limiting is so much more
useful, so a quick way for limiting would be definitly cool! As you
mentioned, normal searching messes up the tree view...
I think replacing search with simple_search like thing is a good idea. It will
make search much more powerfull.

Making current :filter update window content on the fly is another good idea.
It will make filtering much more convenient.

And introducing something like mutt-style "push", as Johannes suggested, is a
third good idea. With it, we can replace current magic search handling with a
simple 'bind / push ":search "' (and add similar shortcut to :filter).

Comments?

It would be really great to have them all implemented. One note: please don't
fold everything into one patch, as it will be very difficult to review.

Gregory
Johannes Weißl
2010-02-15 11:50:32 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
I think replacing search with simple_search like thing is a good idea. It will
make search much more powerfull.
I don't know if we should replace it, maybe one wants to first limit the
view and then search in the (now much reduced!) result!
Post by Gregory Petrosyan
Making current :filter update window content on the fly is another good idea.
It will make filtering much more convenient.
I don't know how hard it is, but would be cool as an option (like Vim's
incsearch).
Post by Gregory Petrosyan
And introducing something like mutt-style "push", as Johannes suggested, is a
third good idea. With it, we can replace current magic search handling with a
simple 'bind / push ":search "' (and add similar shortcut to :filter).
But then ":search " is editable in the prompt, which means C-U removes
also the command. So some kind of magic is maybe not too bad...
--
Johannes Weißl
Gregory Petrosyan
2010-02-11 20:02:36 UTC
Permalink
Post by Johannes Weißl
I use mutt as mail reader and I really like limiting and pattern
modifier there. Cmus' filters are great, but for quickly limiting they
~aBeatles -> artist="*Beatles*"
~a "The Who" !~A quick -> artist="The Who"&!album="*quick*"
~d1990-1999 -> date>=1990&date<=1999
They are very similar to mutt [1] and aptitude [2], which also has
short forms of longer expressions. Long filters are not useless though,
because only they can refer to user-defined named filters.
Great! I've pushed the patch to the newly created 'jw/filter' branch, so that
everybody can easily test/review it.
Post by Johannes Weißl
Cmus doesn't work with the new libmpcdec in Debian testing anymore, so I
adapted the mpc module. I would suggest to name it "mpc2" and adjust the
configure script to determine which is used. Any suggestions?
Why can't existing mpc module be adapted (#ifdefs etc) to compile on Debian
testing? I think this is the preferred way to handle this situation.

Anyway, don't hold the patch — post it!

Gregory
Johannes Weißl
2010-02-17 19:43:12 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
Cmus doesn't work with the new libmpcdec in Debian testing anymore, so I
adapted the mpc module. I would suggest to name it "mpc2" and adjust the
configure script to determine which is used. Any suggestions?
Why can't existing mpc module be adapted (#ifdefs etc) to compile on Debian
testing? I think this is the preferred way to handle this situation.
Anyway, don't hold the patch — post it!
The problem is, that it's not only the header position, parts of the API
changed as well, the patch (without diff -u) has 107 lines...

Most distributions ship SV7 (1.2.x) of Musepack, just Debian
(testing/sid) has the new SV8 (r453). I just discovered that there is
already a patch there [1], which is mostly equivalent to mine.
So should I write a patch using ifdefs (would be 10-20), or duplicate the
code?

Also the mp4 module doesn't compile on Debian testing anymore, which has
been discussed on the list before. I think before releasing 2.3.0, it
should compile on all major distributions out of the box, otherwise
users (that maybe are not able/willing to adapt source files) will
complain.

There has been a patch in [2], which works for me. Has it been committed
to git master?


[1] http://patch-tracker.debian.org/patch/misc/view/cmus/2.2.0-4.1/mpc.c
[2] Message-Id: <1258495576-8992-1-git-send-email-***@gmail.com>
--
Johannes Weißl
Gregory Petrosyan
2010-02-17 22:58:27 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
Post by Johannes Weißl
Cmus doesn't work with the new libmpcdec in Debian testing anymore, so I
adapted the mpc module. I would suggest to name it "mpc2" and adjust the
configure script to determine which is used. Any suggestions?
Why can't existing mpc module be adapted (#ifdefs etc) to compile on Debian
testing? I think this is the preferred way to handle this situation.
Anyway, don't hold the patch — post it!
The problem is, that it's not only the header position, parts of the API
changed as well, the patch (without diff -u) has 107 lines...
Most distributions ship SV7 (1.2.x) of Musepack, just Debian
(testing/sid) has the new SV8 (r453). I just discovered that there is
already a patch there [1], which is mostly equivalent to mine.
So should I write a patch using ifdefs (would be 10-20), or duplicate the
code?
I think #ifdefs are the better way. mp4 is 409 lines long, so duplicating it
is not very good. Also, support for different FLAC APIs was hacked in similar
way — grep for FLAC_NEW_API. One thing — it would be nice (if possible, of
course) to abstract changed API parts and #ifdefs into small functions, and
not just inject #ifdefs everywhere in the file.

Do you use Musepack? If yes, why and what for? Just curious.
Post by Johannes Weißl
Also the mp4 module doesn't compile on Debian testing anymore, which has
been discussed on the list before. I think before releasing 2.3.0, it
should compile on all major distributions out of the box, otherwise
users (that maybe are not able/willing to adapt source files) will
complain.
There has been a patch in [2], which works for me. Has it been committed
to git master?
Thanks for reminding — I've just pushed that patch to Gitorious. Does git
master work for you now?

Gregory
Johannes Weißl
2010-02-18 04:41:28 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
I think #ifdefs are the better way. mp4 is 409 lines long, so duplicating it
is not very good. Also, support for different FLAC APIs was hacked in similar
way — grep for FLAC_NEW_API. One thing — it would be nice (if possible, of
course) to abstract changed API parts and #ifdefs into small functions, and
not just inject #ifdefs everywhere in the file.
Do you use Musepack? If yes, why and what for? Just curious.
mp4.c is for mp4 files, mpc.c is for musepack files :-) (which is 297
lines). Anyway, the changes are minimal, most involving callback
functions, so abstracting wouldn't help much. I will post the patch
soon.

I have a handful of albums, which already had been encoded in mpc... I
didn't convert them mainly to test mpc in cmus :-)
Post by Gregory Petrosyan
Post by Johannes Weißl
Also the mp4 module doesn't compile on Debian testing anymore, which has
been discussed on the list before. I think before releasing 2.3.0, it
should compile on all major distributions out of the box, otherwise
users (that maybe are not able/willing to adapt source files) will
complain.
There has been a patch in [2], which works for me. Has it been committed
to git master?
Thanks for reminding — I've just pushed that patch to Gitorious. Does git
master work for you now?
Yeah, now it works! One step towards 2.3.0!
--
Johannes Weißl
Johannes Weißl
2010-02-20 03:06:45 UTC
Permalink
I will post the patch soon.
Here it is! I used the Debian patch from [1] and tested it with both
versions of the library, so it should be pretty safe.

[1] http://patch-tracker.debian.org/patch/misc/view/cmus/2.2.0-4.1/mpc.c
--
Johannes Weißl
Gregory Petrosyan
2010-02-20 13:26:08 UTC
Permalink
Post by Johannes Weißl
I will post the patch soon.
Here it is! I used the Debian patch from [1] and tested it with both
versions of the library, so it should be pretty safe.
I have to believe you — can't test SV8 support myself :-)

This was the last thing holding 2.3.0 release! I'll release 2.3.0-rc1 in a
couple of days (to let people review/discuss latest patches), and if
everything goes well, 2.3.0 will follow soon.

Thanks!

Gregory
Johannes Weißl
2010-02-20 15:09:41 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
This was the last thing holding 2.3.0 release! I'll release 2.3.0-rc1 in a
couple of days (to let people review/discuss latest patches), and if
everything goes well, 2.3.0 will follow soon.
Great! Maybe we should change the webpage for this release, to let
people, who have been wondering if cmus is dead, know that it's back alive
(officially now!).

I would keep it text-based and simple as before, as this fits to cmus,
but maybe

1. Move "News" or something to the top, containing latest 2-3
news, maybe release information or something else.

2. Shorten "Features" (don't need a feature for every
supported format, should be pretty standard by now, maybe just
a one liner or "common formats including ...")

3. Fix broken links (e.g. "files/announcements/")

4. Move dependencies down

5. Maybe explain long stagnancy / absence of Timo in 1-2 sentences?
Does anybody know if he just abandoned cmus/computers and is still well?
--
Johannes Weißl
Gregory Petrosyan
2010-02-20 15:44:57 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
This was the last thing holding 2.3.0 release! I'll release 2.3.0-rc1 in a
couple of days (to let people review/discuss latest patches), and if
everything goes well, 2.3.0 will follow soon.
Great! Maybe we should change the webpage for this release, to let
people, who have been wondering if cmus is dead, know that it's back alive
(officially now!).
Yes, good idea.
Post by Johannes Weißl
I would keep it text-based and simple as before, as this fits to cmus,
but maybe
1. Move "News" or something to the top, containing latest 2-3
news, maybe release information or something else.
Well, I guess we'll have no news except for new releases.
Post by Johannes Weißl
2. Shorten "Features" (don't need a feature for every
supported format, should be pretty standard by now, maybe just
a one liner or "common formats including ...")
3. Fix broken links (e.g. "files/announcements/")
I think we can remove them entirely.
Post by Johannes Weißl
4. Move dependencies down
5. Maybe explain long stagnancy / absence of Timo in 1-2 sentences?
It can be a part of the relaase announcement.
Post by Johannes Weißl
Does anybody know if he just abandoned cmus/computers and is still well?
No idea, really — he doesn't answer to emails, and I was unable to find any
futher info about him.

While we are at it: I think we should move website code to the git repository,
to the www/ directory. It should contain index.html, index.css and a couple of
screenshots — that should be enough.

Volunteers?

Gregory
Johannes Weißl
2010-02-20 16:33:53 UTC
Permalink
Hi Gregory,
Post by Johannes Weißl
Does anybody know if he just abandoned cmus/computers and is still well?
No idea, really — he doesn't answer to emails, and I was unable to find any
futher info about him.
While we are at it: I think we should move website code to the git repository,
to the www/ directory. It should contain index.html, index.css and a couple of
screenshots — that should be enough.
Volunteers?
I've attached the patches.
Is it the right thing to put the website in a subdirectory of the
source? Wouldn't it be cleaner to initiate a new repository for it? Is
that possible on gitorious?

Otherwise everyone who wants to work on the source has to download
the pictures...
--
Johannes Weißl
Gregory Petrosyan
2010-02-20 16:48:21 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
Post by Johannes Weißl
Does anybody know if he just abandoned cmus/computers and is still well?
No idea, really — he doesn't answer to emails, and I was unable to find any
futher info about him.
While we are at it: I think we should move website code to the git repository,
to the www/ directory. It should contain index.html, index.css and a couple of
screenshots — that should be enough.
Volunteers?
I've attached the patches.
Thanks! That was quick!
Post by Johannes Weißl
Is it the right thing to put the website in a subdirectory of the
source? Wouldn't it be cleaner to initiate a new repository for it? Is
that possible on gitorious?
It is much more convenient to keep everything in one place.
Post by Johannes Weißl
Otherwise everyone who wants to work on the source has to download
the pictures...
Right now, my .git is 7.1M, so adding ~200K of screenshots is not a problem at
all.

I've pushed the patches to jw/www branch, and will update the website shortly.

Gregory
dead_orc
2010-02-20 17:16:41 UTC
Permalink
Post by Johannes Weißl
Hi Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
Does anybody know if he just abandoned cmus/computers and is still well?
No idea, really — he doesn't answer to emails, and I was unable to find any
futher info about him.
While we are at it: I think we should move website code to the git repository,
to the www/ directory. It should contain index.html, index.css and a couple of
screenshots — that should be enough.
Volunteers?
I've attached the patches.
Is it the right thing to put the website in a subdirectory of the
source? Wouldn't it be cleaner to initiate a new repository for it? Is
that possible on gitorious?
I really don't think that's the right way.
Those who want to fetch the cmus source (mind, that might be users as
well!) have no interest in the homepage. And the homepage may grow and
deserves an own repository.

Greetings,
dead_orc
Gregory Petrosyan
2010-02-20 17:24:56 UTC
Permalink
Post by dead_orc
Post by Johannes Weißl
Hi Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
Does anybody know if he just abandoned cmus/computers and is still well?
No idea, really — he doesn't answer to emails, and I was unable to find any
futher info about him.
While we are at it: I think we should move website code to the git repository,
to the www/ directory. It should contain index.html, index.css and a couple of
screenshots — that should be enough.
Volunteers?
I've attached the patches.
Is it the right thing to put the website in a subdirectory of the
source? Wouldn't it be cleaner to initiate a new repository for it? Is
that possible on gitorious?
I really don't think that's the right way.
Those who want to fetch the cmus source (mind, that might be users as
well!) have no interest in the homepage. And the homepage may grow and
deserves an own repository.
Come on — let's not make a big deal from this thing!

Following this logic, we should put contrib/ in its own repository as well :-)

Single repository is much easier to use (for everybody) and manage (for me).
cmus.sf.net is not going to contain more than a single html file — as long as
cmus will remain cmus, its website will remain clean, simple and static.

Gregory
Gregory Petrosyan
2010-02-20 13:28:51 UTC
Permalink
Signed-off-by: Gregory Petrosyan <***@gmail.com>
---

This cleanup and Johannes' patch can be both found in new jw/sv8 branch.

configure | 4 ++--
mpc.c | 18 ++++++++++--------
2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/configure b/configure
index 71bcbc6..fd3114a 100755
--- a/configure
+++ b/configure
@@ -96,7 +96,7 @@ check_ncurses()
check_mpc()
{
check_header mpc/mpcdec.h
- MPC_USE_OLD_HEADER=$?
+ MPC_SV7=$?
check_library MPC "" "-lmpcdec"
return $?
}
@@ -348,7 +348,7 @@ config_header config/datadir.h DATADIR
config_header config/libdir.h LIBDIR
config_header config/debug.h DEBUG
config_header config/tremor.h CONFIG_TREMOR
-config_header config/mpc.h MPC_USE_OLD_HEADER
+config_header config/mpc.h MPC_SV7
config_header config/mp4.h MP4_USE_OLD_HEADER

makefile_vars bindir datadir libdir mandir exampledir
diff --git a/mpc.c b/mpc.c
index 2f020c0..3fa72c8 100644
--- a/mpc.c
+++ b/mpc.c
@@ -10,9 +10,11 @@
#include "file.h"
#include "xmalloc.h"
#include "read_wrapper.h"
+
#include "config/mpc.h"
+#define MPC_SV8 (!MPC_SV7)

-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
#include <mpc/mpcdec.h>
#define callback_t mpc_reader
#define get_ip_data(d) (d)->data
@@ -28,7 +30,7 @@
#include <errno.h>

struct mpc_private {
-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
mpc_demux *decoder;
#else
mpc_decoder decoder;
@@ -128,7 +130,7 @@ static int mpc_open(struct input_plugin_data *ip_data)
ip_data->private = priv;

/* read file's streaminfo data */
-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
priv->decoder = mpc_demux_init(&priv->reader);
if (!priv->decoder) {
mpc_demux_exit(priv->decoder);
@@ -140,7 +142,7 @@ static int mpc_open(struct input_plugin_data *ip_data)
return -IP_ERROR_FILE_FORMAT;
}

-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
mpc_demux_get_info(priv->decoder, &priv->info);
#else
/* instantiate a decoder with our file reader */
@@ -163,7 +165,7 @@ static int mpc_close(struct input_plugin_data *ip_data)
{
struct mpc_private *priv = ip_data->private;

-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
mpc_demux_exit(priv->decoder);
#endif
free(priv);
@@ -214,7 +216,7 @@ static int mpc_read(struct input_plugin_data *ip_data, char *buffer, int count)
{
struct mpc_private *priv = ip_data->private;

-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
mpc_status status;
mpc_frame_info frame;
int samples;
@@ -266,7 +268,7 @@ static int mpc_seek(struct input_plugin_data *ip_data, double offset)
priv->samples_pos = 0;
priv->samples_avail = 0;

-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
if (mpc_demux_seek_second(priv->decoder, offset) == MPC_STATUS_OK)
#else
if (mpc_decoder_seek_seconds(&priv->decoder, offset))
@@ -339,7 +341,7 @@ static int mpc_duration(struct input_plugin_data *ip_data)
/* priv->info.pcm_samples seems to be number of frames
* priv->info.frames is _not_ pcm frames
*/
-#if !MPC_USE_OLD_HEADER
+#if MPC_SV8
return mpc_streaminfo_get_length(&priv->info);
#else
return priv->info.pcm_samples / priv->info.sample_freq;
--
1.6.3.3
Gregory Petrosyan
2010-02-11 23:41:46 UTC
Permalink
Post by Johannes Weißl
I use mutt as mail reader and I really like limiting and pattern
modifier there. Cmus' filters are great, but for quickly limiting they
~aBeatles -> artist="*Beatles*"
~a "The Who" !~A quick -> artist="The Who"&!album="*quick*"
~d1990-1999 -> date>=1990&date<=1999
+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 },
+};
Nitpick: align braces.
Post by Johannes Weißl
+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 },
+};
Same here.
Post by Johannes Weißl
+static int lookup_builtin(const char *key)
Why not return expr_type directly?
Post by Johannes Weißl
+static char *expand_short_expr(const char *expr_short)
+{
+ /* state space, can contain maximal 16 states */
+ enum state_type {
+ ST_SKIP_SPACE,
+ ST_TOP,
+ ST_EXPECT_KEY,
+ ST_EXPECT_OP,
+ ST_EXPECT_INT,
+ ST_IN_INT,
+ ST_MEM_INT,
+ ST_IN_2ND_INT,
+ ST_EXPECT_STR,
+ ST_IN_QUOTE_STR,
+ ST_IN_STR,
+ };
+
+ 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);
Frankly, I don't like such clever things. They require too much attention for
IMO too little gain. Why not simply use "big enough for everything" aka 1024
bytes buffer?
Post by Johannes Weißl
+ 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;
+
+ /* 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) {
+ if (!isspace(c)) {
+ state >>= 4;
+ i--;
+ }
+ break;
+ switch (c) {
+ state = (state << 4) | ST_EXPECT_OP;
+ state = (state << 4) | ST_SKIP_SPACE;
+ state = (state << 4) | ST_EXPECT_KEY;
+ break;
+ level++;
/* Fall through */
Post by Johannes Weißl
+ out[k++] = c;
+ state = (state << 4) | ST_SKIP_SPACE;
+ break;
+ level--;
+ out[k++] = c;
+ state = (state << 4) | ST_EXPECT_OP;
+ state = (state << 4) | ST_SKIP_SPACE;
+ break;
+ out[k++] = c;
+ state = (state << 4) | ST_SKIP_SPACE;
+ break;
This duplicates "case '!':".
Post by Johannes Weißl
+ if (level > 0) {
+ set_error("')' expected");
+ goto error_exit;
+ }
+ out[k++] = c;
+ break;
+ set_error("unexpected '%c'", expr_short, c);
Extra argument?
Post by Johannes Weißl
+ goto error_exit;
+ }
+ break;
+ state >>= 4;
+ key = lookup_long_key(c);
+ if (!key) {
+ set_error("unkown 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;
+ }
else BUG(...)
Post by Johannes Weißl
+ state = (state << 4) | ST_SKIP_SPACE;
BTW, maybe introduce functions like state_push(stack, state),
state_replace_top(stack, state) and state_pop(stack)? It will make the source
easier to read and understand.
Post by Johannes Weißl
+ if (isalnum(c)) {
Will this work for UTF-8 multibyte alpha-numeric characters?
Post by Johannes Weißl
+ out[k++] = c;
+ } else {
+ out[k++] = '*';
+ out[k++] = '"';
+ i--;
+ state >>= 4;
+ }
+ break;
default: BUG(...)
Post by Johannes Weißl
+ }
+ }
+
+ if (num)
+ free(num);
d_print("expanded \"%s\" to \"%s\"\n", expr_short, out);
Post by Johannes Weißl
+ return out;
+
+ if (num)
+ free(num);
+ free(out);
+ return NULL;
+}
+static int is_short_expr(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;
No need for 'else' here.
Post by Johannes Weißl
+ }
+ return 0;
+}
struct expr *expr_parse(const char *str)
{
LIST_HEAD(head);
struct expr *root = NULL;
struct list_head *item;
+ char *new_str = NULL;
Maybe const char *new_str?

Overall, the code looks extremely good. I really like it!

Gregory
Johannes Weißl
2010-02-15 11:14:18 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
+static int lookup_builtin(const char *key)
Why not return expr_type directly?
I'm sure I had a reason in mind, but it was probably just caused by
sleepiness. Changed!
Post by Gregory Petrosyan
Post by Johannes Weißl
+ 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);
Frankly, I don't like such clever things. They require too much attention for
IMO too little gain. Why not simply use "big enough for everything" aka 1024
bytes buffer?
I normally don't like clever things either, but 1024 seems a pretty
random number. Is there a maximum length for input? I tried to find out
by grepping for constants and experimenting, but did not find one.
Limiting otherwise variable-length input arbitrarily here seems ugly.
As far as I can tell "~n1-2" has maximal blowup, so buffer overflow
can't happen here...
Post by Gregory Petrosyan
Post by Johannes Weißl
+ state = (state << 4) | ST_SKIP_SPACE;
BTW, maybe introduce functions like state_push(stack, state),
state_replace_top(stack, state) and state_pop(stack)? It will make the source
easier to read and understand.
Yeah, that seems like a good idea! Would you do it as macros or inline
functions? In utils.h or simplestack.h or expr.c?
Post by Gregory Petrosyan
Post by Johannes Weißl
+ if (isalnum(c)) {
Will this work for UTF-8 multibyte alpha-numeric characters?
Hmm, I don't think so... in cmus we're using real UTF-8 sequences, not
wide characters, so we can't use locale-dependent iswalnum().
I guess checking for blanks and tilde is enough!
Post by Gregory Petrosyan
Overall, the code looks extremely good. I really like it!
Thanks :-)! I will change it to work for simple expressions also!
--
Johannes Weißl
Gregory Petrosyan
2010-02-15 12:21:11 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
+                   state = (state << 4) | ST_SKIP_SPACE;
BTW, maybe introduce functions like state_push(stack, state),
state_replace_top(stack, state) and state_pop(stack)? It will make the source
easier to read and understand.
Yeah, that seems like a good idea! Would you do it as macros or inline
functions? In utils.h or simplestack.h or expr.c?
I would go for expr.c -- if in the future we will reuse these routines
somewhere (e.g. in format_print), then we'll move them to
simplestack.h/c.

And, of course, no macros! Make them plain functions -- no need for
"inline" or anything like that. "inline" means pretty much nothing to
compiler, and should not be used.

Gregory
Johannes Weißl
2010-02-18 04:52:09 UTC
Permalink
* 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
Gregory Petrosyan
2010-02-18 10:05:52 UTC
Permalink
Signed-off-by: Gregory Petrosyan <***@gmail.com>
---
expr.c | 12 ++++++------
1 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/expr.c b/expr.c
index 23adc21..76b5967 100644
--- a/expr.c
+++ b/expr.c
@@ -492,7 +492,7 @@ static char *expand_short_expr(const char *expr_short)
char *out = xnew(char, len_expr_short * 6);
char *num = NULL;
size_t i, i_num = 0, k = 0;
- const char *key;
+ const char *key = NULL;
int level = 0;
enum expr_type etype;
/* used as state-stack, can contain at least 32/4 = 8 states */
@@ -558,7 +558,7 @@ static char *expand_short_expr(const char *expr_short)
} else if (etype == EXPR_STR) {
stack4_push(&state_stack, ST_EXPECT_STR);
} else {
- BUG("wrong etype: %d\n", etype);
+ BUG("wrong etype: %d\n", etype);
}
stack4_push(&state_stack, ST_SKIP_SPACE);
break;
@@ -651,8 +651,8 @@ static char *expand_short_expr(const char *expr_short)
break;
case ST_IN_STR:
/* isalnum() doesn't work for multi-byte characters */
- if (c != ' ' && c != ')' && c != '~' && c != '|'
- && c != '(' && c != '!' && c != '\0') {
+ if (c != ' ' && c != ')' && c != '~' && c != '|' &&
+ c != '(' && c != '!' && c != '\0') {
out[k++] = c;
} else {
out[k++] = '*';
@@ -662,7 +662,7 @@ static char *expand_short_expr(const char *expr_short)
}
break;
default:
- BUG("state %d not covered", stack4_top(state_stack));
+ BUG("state %ld not covered", stack4_top(state_stack));
break;
}
}
@@ -799,7 +799,7 @@ struct expr *expr_parse(const char *str)
LIST_HEAD(head);
struct expr *root = NULL;
struct list_head *item;
- const char *new_str = NULL;
+ char *new_str = NULL;
enum filter_type ftype;
int i;
--
1.6.3.3
Johannes Weißl
2010-02-18 12:05:13 UTC
Permalink
Signed-off-by: Johannes Weißl <***@molb.org>
---
Doc/cmus.txt | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 58 insertions(+), 6 deletions(-)

diff --git a/Doc/cmus.txt b/Doc/cmus.txt
index 5f11554..cb8da97 100644
--- a/Doc/cmus.txt
+++ b/Doc/cmus.txt
@@ -864,6 +864,9 @@ shuffle (false)
Play in shuffled order. Shuffle works in the library views (1-2) and
playlist view (3).

+simple_search (artist=%s|album=%s)
+ Used to expand simple filters, see `FILTERS`.
+
softvol (false)
Use software volume control.

@@ -1014,27 +1017,76 @@ Filters are used mostly for filtering contents of library views (1 & 2).
Filters do not change the actual library content, i.e. *:save* command will
still save all tracks to playlist file whether they are visible or not.

-@h2 Syntax
+@h2 Types
+
+There are three types of filter expressions, each offering more
+expressiveness:
+
+ @li *simple*
+ `e.g.` beatles "rolling s\*"
+
+ @li *short*
+ `e.g.` ~a beatles (!~y1960-1965 | ~d>600)
+
+ @li *long*
+ `e.g.` artist="\*beatles\*"&album="R\*"

-Filter expression is list of built-in filters or user defined filters
-separated with *&* (and) or *|* (or). Parenthesis can be used group
-subexpressions and *!* negates result of the expression following it.
+The type is always auto-detected, so you can use them
+interchangeable in every context filters can be used.
+
+@h2 Simple expressions
+
+Expressions are split up into words, which can also be quoted. Then
+every occurrence of *%s* in the configuration option "simple_search"
+is substituted for such a word. This is done for all words, and the
+result is concatenated using *|*. simple_search can be any short or
+long filter expression, where *%s* (without quotes!) is used
+instead of a string. Example:
+
+@pre
+simple_search=artist=%s
+beatles "rolling s*" --> artist="*beatles*"|artist="rolling s*"
+@endpre
+
+@h2 Short and long expressions
+
+This expression is a list of filters separated with *&* (and) or *|* (or).
+Parenthesis can be used to group subexpressions and *!* negates result of the
+expression following it. Short expressions can only be built of built-in
+filters, long expressions can also consist of user defined ones.

@h2 Strings

+@li long
*filename*, *artist*, *album*, *title*, *genre*, *comment*
-
+@br
Comparators: *=* and *!=* (not equal)

+@li short
+*~f*, *~a*, *~l*, *~t*, *~g*, *~c*
+@br
+Comparators: none
+
+
@h2 Integers

+@li long
*discnumber*, *tracknumber*, *date* (year), *duration* (seconds)
-
+@br
Comparators: *<*, *<=*, *=*, *>=*, *>*, *!=*

+@li short
+*~D*, *~n*, *~y*, *~d*
+@br
+Comparators: *<*, *<=*, *>=*, *>*
+@br
+Ranges: *a-b* (>=a&<=b), *-b* (<=b), *a-* (>=a)
+
@h2 Booleans

*tag* (true if track has tags), *stream* (true if track is a stream)
+@br
+For short expressions: *~T* and *~s*

@h2 Defining Filters
--
1.6.6.1
Gregory Petrosyan
2010-02-18 13:02:08 UTC
Permalink
 Doc/cmus.txt |   64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 58 insertions(+), 6 deletions(-)
Thanks -- I've pushed it to jw/filter, too.
+
+This expression is a list of filters separated with *&* (and) or *|* (or).
+Parenthesis can be used to group subexpressions and *!* negates result of the
+expression following it.  Short expressions can only be built of built-in
+filters, long expressions can also consist of user defined ones.
This limitation looks artificial -- it should be possible to use
filters like "ogg & ~a foobar". (BTW, it looks like new filter code
will assume that "ogg | mp3" is FI_SIMPLE filter, and it is not).

I think the logic should be more like this:
- if filter expression contains no non-quoted special characters, and
no existing filter names, it is FI_SIMPLE one. Expand it according to
simple_search (BTW, the name is misleading -- it is simple_filter
really)
- otherwise, always expand every shortcut that is expandable, and then
pass the result to parse()

What do you think?

Gregory
Johannes Weißl
2010-02-18 15:10:06 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
+expression following it.  Short expressions can only be built of built-in
+filters, long expressions can also consist of user defined ones.
This limitation looks artificial -- it should be possible to use
filters like "ogg & ~a foobar".
The problem is that short expressions (compatible to mutt and aptitude)
can be written really short (e.g. ~abeatles~lrevolver). So '&' isn't
needed (and is not even allowed!), and spaces can also be omitted.
So key-words longer than one letter or without tilde are not possible.
I don't think it's a big limitation, because short expressions will be
often used as throw-away filters.

The documentation is wrong on this, here is a better paragraph:

Long expressions are lists of built-in filters or user defined filters
separated with *&* (and) or *|* (or). Parenthesis can be used group
subexpressions and *!* negates result of the expression following it.
Same is true for short expressions, but they can only be made of
built-in filters. Also (and)-grouping is done implicitly.
Post by Gregory Petrosyan
(BTW, it looks like new filter code will assume that "ogg | mp3" is
FI_SIMPLE filter, and it is not).
Argh, I tried to do it too short... the problem is that we can't access
get_filter() easily from expr.c. Here is my second try [1].
This is definitely not what we want, since user defined filters get
masked by simple expressions... but without get_filter() it is probably
the best we can do... any suggestions?
Post by Gregory Petrosyan
(BTW, the name is misleading -- it is simple_filter really)
I thought about this, I just kept it that way because of mutt, but no
problem in renaming it to simple_filter.
Post by Gregory Petrosyan
- otherwise, always expand every shortcut that is expandable, and then
pass the result to parse()
We could also just use the parser and see if it returns zero, and than
expand. Maybe this is ugly because error_buf etc. is written.


[1]

static enum filter_type get_filter_type(const char *str)
{
int i, j, one_word = 1;
enum expr_type etype;

while (*str == ' ')
str++;

for (i = 0; str[i]; i++) {
char c = str[i];
if (c == '"')
return FI_SIMPLE;
if (c == '~')
return FI_SHORT;
for (j = 0; j < NR_SPECIALS; j++) {
if (c == specials[j])
return FI_LONG;
}
if (c == ' ')
one_word = 0;
}

if (!one_word)
return FI_SIMPLE;

etype = lookup_key_type(str);
if (etype == EXPR_BOOL)
return FI_LONG;

return FI_SIMPLE;
}
--
Johannes Weißl
Gregory Petrosyan
2010-02-18 21:00:17 UTC
Permalink
From: Johannes Weißl <***@molb.org>

Signed-off-by: Gregory Petrosyan <***@gmail.com>
---
Doc/cmus.txt | 9 +++++----
1 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/Doc/cmus.txt b/Doc/cmus.txt
index cb8da97..056d3fc 100644
--- a/Doc/cmus.txt
+++ b/Doc/cmus.txt
@@ -1050,10 +1050,11 @@ beatles "rolling s*" --> artist="*beatles*"|artist="rolling s*"

@h2 Short and long expressions

-This expression is a list of filters separated with *&* (and) or *|* (or).
-Parenthesis can be used to group subexpressions and *!* negates result of the
-expression following it. Short expressions can only be built of built-in
-filters, long expressions can also consist of user defined ones.
+Long expressions are lists of built-in filters or user defined filters
+separated with *&* (and) or *|* (or). Parenthesis can be used group
+subexpressions and *!* negates result of the expression following it.
+Same is true for short expressions, but they can only be made of
+built-in filters. Also (and)-grouping is done implicitly.

@h2 Strings
--
1.6.3.3
Gregory Petrosyan
2010-02-18 21:00:18 UTC
Permalink
This is what it really means.

Signed-off-by: Gregory Petrosyan <***@gmail.com>
---
Doc/cmus.txt | 8 ++++----
expr.c | 6 +++---
expr.h | 2 +-
options.c | 20 ++++++++++----------
4 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/Doc/cmus.txt b/Doc/cmus.txt
index 056d3fc..17e9ad0 100644
--- a/Doc/cmus.txt
+++ b/Doc/cmus.txt
@@ -864,7 +864,7 @@ shuffle (false)
Play in shuffled order. Shuffle works in the library views (1-2) and
playlist view (3).

-simple_search (artist=%s|album=%s)
+simple_filter (artist=%s|album=%s)
Used to expand simple filters, see `FILTERS`.

softvol (false)
@@ -1037,14 +1037,14 @@ interchangeable in every context filters can be used.
@h2 Simple expressions

Expressions are split up into words, which can also be quoted. Then
-every occurrence of *%s* in the configuration option "simple_search"
+every occurrence of *%s* in the configuration option "simple_filter"
is substituted for such a word. This is done for all words, and the
-result is concatenated using *|*. simple_search can be any short or
+result is concatenated using *|*. simple_filter can be any short or
long filter expression, where *%s* (without quotes!) is used
instead of a string. Example:

@pre
-simple_search=artist=%s
+simple_filter=artist=%s
beatles "rolling s*" --> artist="*beatles*"|artist="rolling s*"
@endpre

diff --git a/expr.c b/expr.c
index 76b5967..51981c7 100644
--- a/expr.c
+++ b/expr.c
@@ -755,18 +755,18 @@ static char *expand_simple_expr(const char *expr_simple)
for (n_words = 0; words[n_words]; n_words++)
;

- tmp = simple_search;
+ tmp = simple_filter;
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;
+ max_out_size = n_words * (repl_count * (strlen(expr_simple) + 2) + strlen(simple_filter) + 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;
+ tmp = simple_filter;
while ((hit = strstr(tmp, "%s"))) {
memcpy(out+k, tmp, hit - tmp);
k += hit - tmp;
diff --git a/expr.h b/expr.h
index dfbde65..3a2eac5 100644
--- a/expr.h
+++ b/expr.h
@@ -11,7 +11,7 @@
enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT, OP_NE };
#define NR_OPS (OP_NE + 1)

-extern char *simple_search;
+extern char *simple_filter;

enum expr_type {
EXPR_AND,
diff --git a/options.c b/options.c
index 07ffa62..1dadaae 100644
--- a/options.c
+++ b/options.c
@@ -36,7 +36,7 @@

char *output_plugin = NULL;
char *status_display_program = NULL;
-char *simple_search = NULL;
+char *simple_filter = NULL;
char *server_password;
int auto_reshuffle = 0;
int confirm_run = 1;
@@ -367,18 +367,18 @@ err:
error_msg("two integers in range 0..100 expected");
}

-static void get_simple_search(unsigned int id, char *buf)
+static void get_simple_filter(unsigned int id, char *buf)
{
- if (simple_search)
- strcpy(buf, simple_search);
+ if (simple_filter)
+ strcpy(buf, simple_filter);
}

-static void set_simple_search(unsigned int id, const char *buf)
+static void set_simple_filter(unsigned int id, const char *buf)
{
- free(simple_search);
- simple_search = NULL;
+ free(simple_filter);
+ simple_filter = NULL;
if (buf[0])
- simple_search = xstrdup(buf);
+ simple_filter = xstrdup(buf);
}

static void get_status_display_program(unsigned int id, char *buf)
@@ -861,7 +861,7 @@ static const struct {
DT(show_remaining_time)
DT(set_term_title)
DT(shuffle)
- DN(simple_search)
+ DN(simple_filter)
DT(softvol)
DN(softvol_state)
DN(status_display_program)
@@ -913,7 +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" },
+ { "simple_filter", "artist=%s|album=%s" },
{ NULL, NULL }
};
--
1.6.3.3
Gregory Petrosyan
2010-02-18 21:14:52 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
(BTW, it looks like new filter code will assume that "ogg | mp3" is
FI_SIMPLE filter, and it is not).
Argh, I tried to do it too short... the problem is that we can't access
get_filter() easily from expr.c. Here is my second try [1].
This is definitely not what we want, since user defined filters get
masked by simple expressions... but without get_filter() it is probably
the best we can do... any suggestions?
Hm... For me, the whole point of simple filters was that they provide a
mechanism to replace (or complement, at least) current search, which
completely messes the tree view.

One thing we can do is require that simple filter expression must start with
e.g. "/". I don't like complex heuristics to determine what kind of filter
user is, well, using.

Another thing is to only allow them as replacement of search for the tree
view, and ignore them everywhere else. IMO short filters are concise enough to
be very usefull, and do not require extra simplifications.

Gregory
Johannes Weißl
2010-02-18 23:27:58 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
Hm... For me, the whole point of simple filters was that they provide a
mechanism to replace (or complement, at least) current search, which
completely messes the tree view.
Only complement, as search is useful for other views than the first.
Post by Gregory Petrosyan
One thing we can do is require that simple filter expression must start with
e.g. "/". I don't like complex heuristics to determine what kind of filter
user is, well, using.
I thought about this too, because I also hate complex heuristics (this
is the reason I liked the short expressions). But maybe one should try
to make the long options more unique, because simple expressions should
be just that - simple.

How about the following:

1. Simple expressions can only be used in :filter mode
2. User-defined expressions in :filter can be forced using a prefix
key, e.g.
:filter =mp3|ogg
:filter =artist="*beatles*"&mp3
can also be:
:filter artist="*beatles*"&mp3

"=" is good because it fits the current detection code :-)
In :filter, anything containing no "=" or "~" is a simple expr


Another option would be to introduce a new command, :sfilter (can be
abbreviated using :sf)

Opinions?
--
Johannes Weißl
Gregory Petrosyan
2010-02-18 23:49:59 UTC
Permalink
Post by Johannes Weißl
1. Simple expressions can only be used in :filter mode
2. User-defined expressions in :filter can be forced using a prefix
key, e.g.
:filter =mp3|ogg
:filter =artist="*beatles*"&mp3
:filter artist="*beatles*"&mp3
This is bad, because it is not backwards compatible.
Post by Johannes Weißl
Another option would be to introduce a new command, :sfilter (can be
abbreviated using :sf)
IMO this is much better. Also, this way we can make sfilter
updates-as-you-type kind of thing, which I think is desirable (not sure if
this is the right default for filters in general).

Gregory
Johannes Weißl
2010-02-19 00:32:36 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
1. Simple expressions can only be used in :filter mode
2. User-defined expressions in :filter can be forced using a prefix
This is bad, because it is not backwards compatible.
Hmm, you're right... it is backwards compatible in the config file
(because it is using fset/factivate), but not in interactive mode
using :filter. Nonetheless I attached the patch (because it's short and
works), but don't commit it to my branch.
Post by Gregory Petrosyan
Post by Johannes Weißl
Another option would be to introduce a new command, :sfilter (can be
abbreviated using :sf)
IMO this is much better. Also, this way we can make sfilter
updates-as-you-type kind of thing, which I think is desirable (not sure if
this is the right default for filters in general).
Hmm, depends... for long filter expressions it doesn't work at all,
because they are almost always only valid at the very end of editing
(except for integers...). With short expressions it becomes better.

Although the :sfilter approach seems cleaner, I don't like the feeling
that I need to have two limiting-shortcuts in my head. But I guess I can
live with it.
--
Johannes Weißl
Johannes Weißl
2010-02-19 01:35:05 UTC
Permalink
Hi Gregory,
Post by Johannes Weißl
Although the :sfilter approach seems cleaner, I don't like the feeling
that I need to have two limiting-shortcuts in my head. But I guess I can
live with it.
Another idea: maybe we should abandon backwards compatibility and do the
following change:

1. :filter does not accept self-defined filters anymore
(means :filter degenerates to :sfilter with optionally short or
long expressions, but without ambiguousness)
:factivate can be used for setting user defined filters anyway!
2. :fset / :factivate don't work with simple expressions anymore

If you don't like that, I've attached the :sfilter patch :-)
--
Johannes Weißl
Gregory Petrosyan
2010-02-19 20:35:27 UTC
Permalink
Post by Johannes Weißl
Post by Johannes Weißl
Although the :sfilter approach seems cleaner, I don't like the feeling
that I need to have two limiting-shortcuts in my head. But I guess I can
live with it.
Another idea: maybe we should abandon backwards compatibility and do the
1. :filter does not accept self-defined filters anymore
(means :filter degenerates to :sfilter with optionally short or
long expressions, but without ambiguousness)
:factivate can be used for setting user defined filters anyway!
2. :fset / :factivate don't work with simple expressions anymore
I think we should not abandon backwards compatibility unless we have a *very*
good reason for it.
Post by Johannes Weißl
If you don't like that, I've attached the :sfilter patch :-)
I think this is the best option, really. To make :filter and :sfilter really
distinguishable, I think we should make :sf update current window as you type;
and :filter will remain a more powerful mutt-like limiting thing.
Johannes Weißl
2010-02-20 02:11:00 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
I think this is the best option, really. To make :filter and :sfilter really
distinguishable, I think we should make :sf update current window as you type;
and :filter will remain a more powerful mutt-like limiting thing.
Yeah, update-as-you-type would make the :sf/:fi distinction more reasonable.
I put it on the todo list...
Post by Gregory Petrosyan
+ /* deactive all filters */
+ list_for_each_entry(f, &filters_head, node)
+ f->act_stat = FS_IGNORE;
Hm, maybe it will be more intuitive to apply :sf only to already filtered
tracks?
I removed the two lines, but apparently :sfilter still ignores other set
filters, although now they are still marked in view 6. Unless someone
implements this feature, I suggest we leave it as it is.

I've attached a view more patches (doc, etc.)
--
Johannes Weißl
Gregory Petrosyan
2010-02-20 13:07:14 UTC
Permalink
Post by Johannes Weißl
I've attached a view more patches (doc, etc.)
Thanks, they all look good. I've pushed them to jw/filter.

Is it OK that patches # 1, 4, 5, and 6 are present, and # 2, 3 are not?

Gregory
Johannes Weißl
2010-02-20 14:18:30 UTC
Permalink
Hi Gregory,
Post by Gregory Petrosyan
Is it OK that patches # 1, 4, 5, and 6 are present, and # 2, 3 are not?
Yes, the commits in between were merges from other branches and reverted
commits. Is there a better/designated way to send patches then?
--
Johannes Weißl
Gregory Petrosyan
2010-02-20 14:37:33 UTC
Permalink
Post by Johannes Weißl
Post by Gregory Petrosyan
Is it OK that patches # 1, 4, 5, and 6 are present, and # 2, 3 are not?
Yes, the commits in between were merges from other branches and reverted
commits. Is there a better/designated way to send patches then?
The best way is to use topic branches, which contain only topic-related
patches and nothing else.

Instead of reverting a commit in a topic branch, you can use "git rebase -i"
to erase it and keep the history clean. ("rebase -i" can be used for almost
anything history-rewriting related: fixing typos in commit messages, merging
several patches into one, reordering patches, etc.)

Also, you should not merge anything into a topic branch. Instead, you should
merge topic branches into branches like "master" or "proposed".

I'm not sure I am following this recommendations myself, but I try to :-)

Gregory
Johannes Weißl
2011-01-11 00:53:58 UTC
Permalink
old (wrong): !~y1960-1965 -> !date>=1960&date<=1965
new (right): !~y1960-1965 -> !(date>=1960&date<=1965)
---
expr.c | 16 ++++++++++++----
1 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/expr.c b/expr.c
index dd35185..1a7f8e4 100644
--- a/expr.c
+++ b/expr.c
@@ -482,8 +482,11 @@ 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 = xnew(char, len_expr_short * 6);
+ /* worst case blowup of expr_short is 31/5 (e.g. ~n1-2), so take x7:
+ * strlen("~n1-2") == 5
+ * strlen("(tracknumber>=1&tracknumber<=2)") == 31
+ */
+ char *out = xnew(char, len_expr_short * 7);
char *num = NULL;
size_t i, i_num = 0, k = 0;
const char *key = NULL;
@@ -544,16 +547,17 @@ static char *expand_short_expr(const char *expr_short)
set_error("unkown short key %c", c);
goto error_exit;
}
- strcpy(out+k, key);
- k += strlen(key);
etype = lookup_key_type(key);
if (etype == EXPR_INT) {
stack4_push(&state_stack, ST_EXPECT_INT);
+ out[k++] = '(';
} else if (etype == EXPR_STR) {
stack4_push(&state_stack, ST_EXPECT_STR);
} else {
BUG("wrong etype: %d\n", etype);
}
+ strcpy(out+k, key);
+ k += strlen(key);
stack4_push(&state_stack, ST_SKIP_SPACE);
break;
case ST_EXPECT_OP:
@@ -586,6 +590,7 @@ static char *expand_short_expr(const char *expr_short)
} else {
i -= 1;
stack4_pop(&state_stack);
+ out[k++] = ')';
}
break;
case ST_MEM_INT:
@@ -604,6 +609,8 @@ static char *expand_short_expr(const char *expr_short)
strncpy(out+k, num, i_num);
k += i_num;
i_num = 0;
+ if (c != '-')
+ out[k++] = ')';
}
break;
case ST_IN_2ND_INT:
@@ -621,6 +628,7 @@ static char *expand_short_expr(const char *expr_short)
strncpy(out+k, num, i_num);
k += i_num;
}
+ out[k++] = ')';
}
break;
case ST_EXPECT_STR:
--
1.7.2.3
Gregory Petrosyan
2010-02-18 10:12:10 UTC
Permalink
Post by Johannes Weißl
* 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.
Once again, thanks a lot! I've pushed the patch to jw/filter branch on
Gitorious.

In future, please split patches like this into smaller ones, like "clean up
short expr" + "introduce simple filters". There's no reason to merge several
unrelated patches into one.

Also, please update the documentation.
Post by Johannes Weißl
+ max_out_size = n_words * (repl_count * (strlen(expr_simple) + 2) + strlen(simple_search) + 3) + 1;
And this really deserves a comment :-)

Gregory
dead_orc
2010-02-18 15:31:13 UTC
Permalink
Thanks for the patch!
Post by Johannes Weißl
* Added simple filters, e.g. foo bar -> artist="*foo*"|artist="*bar*",
configurable using variable simple_search.
I would like it better if foo bar would translate to artist="*foo bar*",
but as that's achievable with "foo bar", I'm okay with this I suppose.

But I think a better default for simple_search would be
simple_search=artist=%s|album=%s|title=%s
because it's meant as a replacement for the search (isn't it?).

Greetings,
dead_orc
Johannes Weißl
2010-02-18 16:30:57 UTC
Permalink
Hi dead_orc,
Post by dead_orc
I would like it better if foo bar would translate to artist="*foo bar*",
but as that's achievable with "foo bar", I'm okay with this I suppose.
It would have been a lot simpler that way, but I thought if you do a
simple keyword based search (as in google), you expect the keywords to
be treated logically connected. I just don't know if it's been better to
OR them or to AND them... both makes kind of sense to me!
Post by dead_orc
But I think a better default for simple_search would be
simple_search=artist=%s|album=%s|title=%s
because it's meant as a replacement for the search (isn't it?).
I don't know, I just had to choose something for implementing. Including
title seems also good! Any suggestions? Maybe filename also?

Another thing: When I thought of short expressions, they we're
completely distinguishable from the standard filters. But simple filters
clash with named user-defined filters. I don't know how we should
handle this, i.e. which should take preference.
--
Johannes Weißl
Gregory Petrosyan
2010-02-12 00:39:33 UTC
Permalink
Post by Johannes Weißl
+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 },
+};
I think we should use the same letters we use for format strings.

Gregory
Johannes Weißl
2010-02-15 11:32:27 UTC
Permalink
Hey Gregory,
Post by Gregory Petrosyan
Post by Johannes Weißl
+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 },
+};
I think we should use the same letters we use for format strings.
Agreed, I didn't think about it, is this ok:

static const struct {
char short_key;
const char *long_key;
} map_short2long[] = {
{ '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 },
};

"s" and "T" are new...
--
Johannes Weißl
dead_orc
2010-02-18 14:28:03 UTC
Permalink
Hey,

My try on the push command.

It doesn't work from the commandline itself right now, because after
executing the command, the commandline is cleared, and I'm not sure if
it makes sense to set this command as "no-cmdline" (or something...) or
to make an exception for clearing.

But it can be bound like this:

bind w push filter artist="*

If there is a whitespace at the end, it will be pushed as well.

Comments, please?
dead_orc
Gregory Petrosyan
2010-02-18 21:50:45 UTC
Permalink
Post by dead_orc
My try on the push command.
It doesn't work from the commandline itself right now, because after
executing the command, the commandline is cleared, and I'm not sure if
it makes sense to set this command as "no-cmdline" (or something...) or
to make an exception for clearing.
bind w push filter artist="*
If there is a whitespace at the end, it will be pushed as well.
Comments, please?
Most of this text deserves to be in the commit message.

I like this patch because it is very simple, yet very useful. I've pushed it
to new jl/push branch.

Please update the documentation as well. Don't forget that undocumented
features do not exist for the majority of users.

Gregory
dead_orc
2010-02-18 22:23:49 UTC
Permalink
I just wanted to get feedback before writing documentation for a
rejected patch. ;)

Here the doc goes...
Gregory Petrosyan
2010-02-18 23:21:53 UTC
Permalink
Post by dead_orc
I just wanted to get feedback before writing documentation for a
rejected patch. ;)
Here the doc goes...
Thanks, I've pushed slightly modified version to jl/push branch.

Gregory
dead_orc
2010-02-21 09:18:02 UTC
Permalink
When they are not explicitly bound, they default to their old behavior.
No warning is given if you rebind any of them (not even : ).
Per-context overriding is of course possible.

This is mainly intended for use with the push command for something like
bind library / push fi
---
keys.c | 33 +++++++++++++++++++--------------
1 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/keys.c b/keys.c
index 9a9f53f..baa2b13 100644
--- a/keys.c
+++ b/keys.c
@@ -77,6 +77,7 @@ const struct key key_table[] = {
{ ",", KEY_IS_CHAR, 44 },
{ "-", KEY_IS_CHAR, 45 },
{ ".", KEY_IS_CHAR, 46 },
+ { "/", KEY_IS_CHAR, 47 },
{ "0", KEY_IS_CHAR, 48 },
{ "1", KEY_IS_CHAR, 49 },
{ "2", KEY_IS_CHAR, 50 },
@@ -87,10 +88,12 @@ const struct key key_table[] = {
{ "7", KEY_IS_CHAR, 55 },
{ "8", KEY_IS_CHAR, 56 },
{ "9", KEY_IS_CHAR, 57 },
+ { ":", KEY_IS_CHAR, 58 },
{ ";", KEY_IS_CHAR, 59 },
{ "<", KEY_IS_CHAR, 60 },
{ "=", KEY_IS_CHAR, 61 },
{ ">", KEY_IS_CHAR, 62 },
+ { "?", KEY_IS_CHAR, 63 },
{ "@", KEY_IS_CHAR, 64 },
{ "A", KEY_IS_CHAR, 65 },
{ "B", KEY_IS_CHAR, 66 },
@@ -608,19 +611,6 @@ void normal_mode_ch(uchar ch)
enum key_context c;
const struct key *k;

- /* you can't redefine these keys */
- switch (ch) {
- case ':':
- enter_command_mode();
- return;
- case '/':
- enter_search_mode();
- return;
- case '?':
- enter_search_backward_mode();
- return;
- }
-
c = view_to_context[cur_view];
k = ch_to_key(ch);

@@ -633,7 +623,22 @@ void normal_mode_ch(uchar ch)
return;

/* common ch */
- handle_key(key_bindings[CTX_COMMON], k);
+ if (handle_key(key_bindings[CTX_COMMON], k))
+ return;
+
+ /* these can be overridden but have default magic */
+ switch (ch) {
+ case ':':
+ enter_command_mode();
+ return;
+ case '/':
+ enter_search_mode();
+ return;
+ case '?':
+ enter_search_backward_mode();
+ return;
+ }
+
}

void normal_mode_key(int key)
--
1.6.3.3
Gregory Petrosyan
2010-02-21 10:08:19 UTC
Permalink
Post by dead_orc
When they are not explicitly bound, they default to their old behavior.
No warning is given if you rebind any of them (not even : ).
Per-context overriding is of course possible.
I think this is crazy, but I have just checked and vim allows this sort of
crazyness..
Post by dead_orc
This is mainly intended for use with the push command for something like
bind library / push fi
Hm, you want to hijack search in library view :-)

I've recently posted a patch to "bind common F push filter ", and plan to
introduce "bind common f push sfilter " when sfilter stuff will become ready —
these are better as defaults because they are backwards-compatible.

Will push to gp/proposed.

Gregory
Gregory Petrosyan
2010-02-21 10:27:41 UTC
Permalink
Post by dead_orc
keys.c | 33 +++++++++++++++++++--------------
1 files changed, 19 insertions(+), 14 deletions(-)
Please use git-send-email for posting patches (or attach git-format-patch
output). Your email client somehow damaged the patch, and I had to manually
apply the changes.

Gregory
Jan-Philipp Litza
2010-02-18 14:22:50 UTC
Permalink
push doesn't work from the commandline itself!
---
command_mode.c | 11 ++++++++++-
1 files changed, 10 insertions(+), 1 deletions(-)

diff --git a/command_mode.c b/command_mode.c
index b2f2718..96d1b79 100644
--- a/command_mode.c
+++ b/command_mode.c
@@ -1224,6 +1224,12 @@ static void cmd_view(char *arg)
}
}

+static void cmd_push(char *arg)
+{
+ cmdline_set_text(arg);
+ enter_command_mode();
+}
+
static void cmd_p_next(char *arg)
{
cmus_next();
@@ -2307,6 +2313,8 @@ static void expand_colorscheme(const char *str)
}
}

+static void expand_commands(const char *str);
+
/* tab exp }}} */

/* sort by name */
@@ -2330,7 +2338,8 @@ struct command commands[] = {
{ "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
{ "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
{ "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
- { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
+ { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
+ { "push", cmd_push, 1,-1, expand_commands, 0, 0 },
{ "quit", cmd_quit, 0, 0, NULL, 0, 0 },
{ "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
{ "run", cmd_run, 1,-1, NULL, 0, CMD_UNSAFE },
--
1.6.3.3


--=-EV/iBBF2/6vh36fSKcHm--
Jan-Philipp Litza
2010-02-18 22:17:48 UTC
Permalink
---
Doc/cmus.txt | 7 +++++++
1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/Doc/cmus.txt b/Doc/cmus.txt
index 5f11554..8737563 100644
--- a/Doc/cmus.txt
+++ b/Doc/cmus.txt
@@ -462,6 +462,13 @@ player-stop (*v*)
prev-view
Go to previously used view.

+push <text>
+ Enter command mode with the command line pre-set to text. Text can
+ contain spaces and even trailing spaces will be honored.
+
+ This command can only be bound to a key but not used in the command
+ line directly!
+
quit
Exit cmus.
--
1.6.3.3


--------------010408010208020804020107--
Loading...