Convert para_play to lopsub.
authorAndre Noll <maan@tuebingen.mpg.de>
Tue, 23 Aug 2016 10:04:19 +0000 (12:04 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 26 Mar 2017 09:02:28 +0000 (11:02 +0200)
This changes para_play to use the lopsub library rather than gengetopt
for parsing the command line and the config file.

The old approach of permuting argv[] no longer works with lopsub
because direct access to the array of non-option arguments is no
longer possible. Therefore we introduce shuffle_map[], an array of
indices which defaults to 1..n if --randomize is not given, and a
random permutation of 1..n otherwise. The new get_playlist_file()
helper returns the path to the input file determined by permuting
the given index.

The overall description for the manual page has also been improved
slightly.

Since m4/gengetopt/history_file.m4 has become unused, this file can
be removed.

Makefile.real
configure.ac
m4/gengetopt/history_file.m4 [deleted file]
m4/gengetopt/play.m4 [deleted file]
m4/lls/play.suite.m4 [new file with mode: 0644]
play.c

index 84872be..e119919 100644 (file)
@@ -43,14 +43,14 @@ all_objs := $(sort $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \
        $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \
        $(write_objs) $(afh_objs) $(play_objs))
 deps := $(addprefix $(dep_dir)/, $(filter-out %.cmdline.d, $(all_objs:.o=.d)))
-converted_executables := audioc client fade
+converted_executables := audioc client fade play
 unconverted_executables := $(filter-out $(converted_executables), $(executables))
 
 audioc_objs += audioc.lsg.o
 audiod_objs += audiod_cmd.lsg.o recv_cmd.lsg.o client.lsg.o
 fade_objs += fade.lsg.o
 server_objs += server_cmd.lsg.o
-play_objs += play_cmd.lsg.o recv_cmd.lsg.o
+play_objs += play_cmd.lsg.o recv_cmd.lsg.o play.lsg.o
 recv_objs += recv_cmd.lsg.o
 client_objs += client.lsg.o
 
index 909205e..d592fba 100644 (file)
@@ -846,7 +846,6 @@ play_errlist_objs="
        play
        fd
        sched
-       ggo
        buffer_tree
        time
        string
@@ -880,7 +879,6 @@ play_cmdline_objs="
        amp_filter
        prebuffer_filter
        file_write
-       play
        sync_filter
 "
 if test "$have_core_audio" = "yes"; then
diff --git a/m4/gengetopt/history_file.m4 b/m4/gengetopt/history_file.m4
deleted file mode 100644 (file)
index 73e98a7..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-<qu>
-option "history-file" -
-#~~~~~~~~~~~~~~~~~~~~~~
-"(default='</qu>DEFAULT_HISTORY_FILE<qu>')"
-string typestr = "filename"
-optional
-details = "
-       If </qu>CURRENT_PROGRAM<qu> runs in interactive mode, it reads the history
-       file on startup. Upon exit, the in-memory history is appended
-       to the history file.
-"
-</qu>
diff --git a/m4/gengetopt/play.m4 b/m4/gengetopt/play.m4
deleted file mode 100644 (file)
index 1687559..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-args "--unamed-opts=audio_file --no-handle-version --conf-parser --no-handle-help"
-
-purpose "Command line audio player"
-
-description "para_play operates either in command mode or in insert
-mode. In insert mode it presents a prompt and allows to enter commands
-like stop, play, pause etc. In command mode the current audio file
-is shown and the program reads single key strokes from stdin. Keys
-may be mapped to commands. Whenever a mapped key is pressed, the
-associated command is executed."
-
-include(header.m4)
-define(CURRENT_PROGRAM,para_play)
-define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf)
-define(DEFAULT_HISTORY_FILE,~/.paraslash/play.history)
-
-<qu>
-#########################
-section "General options"
-#########################
-</qu>
-include(loglevel.m4)
-include(config_file.m4)
-include(history_file.m4)
-<qu>
-
-###############################
-section "Options for para_play"
-###############################
-
-option "randomize" z
-#~~~~~~~~~~~~~~~~~~~
-"randomize playlist at startup."
-flag off
-
-option "key-map" k
-#~~~~~~~~~~~~~~~~~
-"Map key k to a command."
-
-string typestr = "key:command [args]"
-optional
-multiple
-details = "
-       This option may be given multiple times, one for each key
-       mapping. Example:
-               5:jmp 50
-"
-</qu>
diff --git a/m4/lls/play.suite.m4 b/m4/lls/play.suite.m4
new file mode 100644 (file)
index 0000000..0fbba0c
--- /dev/null
@@ -0,0 +1,40 @@
+m4_define(PROGRAM, para_play)
+m4_define(DEFAULT_HISTORY_FILE, ~/.paraslash/play.history)
+m4_define(DEFAULT_CONFIG_FILE,~/.paraslash/play.conf)
+[suite play]
+version-string = GIT_VERSION()
+[supercommand para_play]
+       purpose = command line audio player
+       non-opts-name = <audio_file>...
+       [description]
+               para_play operates either in command mode or in insert mode. In
+               insert mode it presents a prompt and allows to enter commands like
+               stop, play, pause etc. In command mode the current audio file and the
+               playback position are shown and the program reads single key strokes
+               from stdin. Keys may be mapped to commands so that the configured
+               command is executed when a mapped key is pressed.
+       [/description]
+       m4_include(common-option-section.m4)
+       m4_include(help.m4)
+       m4_include(detailed-help.m4)
+       m4_include(version.m4)
+       m4_include(loglevel.m4)
+       m4_include(config-file.m4)
+       m4_include(history-file.m4)
+       m4_include(per-command-options-section.m4)
+       [option randomize]
+               short_opt = z
+               summary = randomize playlist at startup
+       [option key-map]
+               short_opt = k
+               summary = map a key to a command
+               arg_info = required_arg
+               arg_type = string
+               flag multiple
+               typestr = key:command [args]
+               [help]
+                       This option may be given multiple times, one for each key
+                       mapping. Example:
+
+                               --key-map 5:jmp 50
+               [/help]
diff --git a/play.c b/play.c
index 4dab1ca..330c8b2 100644 (file)
--- a/play.c
+++ b/play.c
 #include <lopsub.h>
 
 #include "recv_cmd.lsg.h"
+#include "play_cmd.lsg.h"
+#include "play.lsg.h"
 #include "para.h"
 #include "list.h"
-#include "play.cmdline.h"
-#include "play_cmd.lsg.h"
 #include "error.h"
 #include "ggo.h"
 #include "buffer_tree.h"
 /** Array of error strings. */
 DEFINE_PARA_ERRLIST;
 
+static struct lls_parse_result *play_lpr;
+
+#define CMD_PTR (lls_cmd(0, play_suite))
+#define OPT_RESULT(_name) \
+       (lls_opt_result(LSG_PLAY_PARA_PLAY_OPT_ ## _name, play_lpr))
+#define OPT_GIVEN(_name) (lls_opt_given(OPT_RESULT(_name)))
+#define OPT_UINT32_VAL(_name) (lls_uint32_val(0, OPT_RESULT(_name)))
+#define OPT_STRING_VAL(_name) (lls_string_val(0, OPT_RESULT(_name)))
+
 /**
  * Describes a request to change the state of para_play.
  *
@@ -118,73 +127,105 @@ INIT_STDERR_LOGGING(loglevel);
 
 char *stat_item_values[NUM_STAT_ITEMS] = {NULL};
 
-/** Iterate over all files in the playlist. */
-#define FOR_EACH_PLAYLIST_FILE(i) for (i = 0; i < conf.inputs_num; i++)
-static struct play_args_info conf;
-
 static struct sched sched = {.max_fileno = 0};
 static struct play_task play_task;
 
 #define AFH_RECV_CMD (lls_cmd(LSG_RECV_CMD_CMD_AFH, recv_cmd_suite))
 #define AFH_RECV ((struct receiver *)lls_user_data(AFH_RECV_CMD))
 
-__noreturn static void print_help_and_die(void)
+static unsigned *shuffle_map;
+
+static const char *get_playlist_file(unsigned idx)
+{
+       return lls_input(shuffle_map[idx], play_lpr);
+}
+
+static void handle_help_flags(void)
 {
-       struct ggo_help help = DEFINE_GGO_HELP(play);
-       unsigned flags = conf.detailed_help_given?
-               GPH_STANDARD_FLAGS_DETAILED : GPH_STANDARD_FLAGS;
+       char *help;
 
-       ggo_print_help(&help, flags);
-       printf("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
-       exit(0);
+       if (OPT_GIVEN(DETAILED_HELP))
+               help = lls_long_help(CMD_PTR);
+       else if (OPT_GIVEN(HELP))
+               help = lls_short_help(CMD_PTR);
+       else
+               return;
+       printf("%s\n", help);
+       free(help);
+       exit(EXIT_SUCCESS);
 }
 
 static void parse_config_or_die(int argc, char *argv[])
 {
-       int i, ret;
-       char *config_file;
-       struct play_cmdline_parser_params params = {
-               .override = 0,
-               .initialize = 1,
-               .check_required = 0,
-               .check_ambiguity = 0,
-               .print_errors = 1
-       };
+       const struct lls_command *cmd = CMD_PTR;
+       int i, ret, cf_argc;
+       char *cf, *errctx, **cf_argv;
+       struct lls_parse_result *cf_lpr, *merged_lpr;
+       unsigned num_kmas;
+       void *map;
+       size_t sz;
 
-       play_cmdline_parser_ext(argc, argv, &conf, &params);
-       loglevel = get_loglevel_by_name(conf.loglevel_arg);
-       version_handle_flag("play", conf.version_given);
-       if (conf.help_given || conf.detailed_help_given)
-               print_help_and_die();
-       if (conf.config_file_given)
-               config_file = para_strdup(conf.config_file_arg);
+       ret = lls(lls_parse(argc, argv, cmd, &play_lpr, &errctx));
+       if (ret < 0)
+               goto fail;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+       version_handle_flag("play", OPT_GIVEN(VERSION));
+       handle_help_flags();
+       if (OPT_GIVEN(CONFIG_FILE))
+               cf = para_strdup(OPT_STRING_VAL(CONFIG_FILE));
        else {
                char *home = para_homedir();
-               config_file = make_message("%s/.paraslash/play.conf", home);
+               cf = make_message("%s/.paraslash/play.conf", home);
                free(home);
        }
-       ret = file_exists(config_file);
-       if (conf.config_file_given && !ret) {
-               PARA_EMERG_LOG("can not read config file %s\n", config_file);
-               goto err;
-       }
-       if (ret) {
-               params.initialize = 0;
-               params.check_required = 1;
-               play_cmdline_parser_config_file(config_file, &conf, &params);
-               loglevel = get_loglevel_by_name(conf.loglevel_arg);
+       ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
+       if (ret < 0) {
+               if (ret != -E_EMPTY && ret != -ERRNO_TO_PARA_ERROR(ENOENT))
+                       goto free_cf;
+               if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && OPT_GIVEN(CONFIG_FILE))
+                       goto free_cf;
+               ret = 0;
+               goto free_cf;
        }
-       for (i = 0; i < conf.key_map_given; i++) {
-               char *kma = conf.key_map_arg[i];
+       ret = lls(lls_convert_config(map, sz, NULL, &cf_argv, &errctx));
+       para_munmap(map, sz);
+       if (ret < 0)
+               goto free_cf;
+       cf_argc = ret;
+       ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
+       lls_free_argv(cf_argv);
+       if (ret < 0)
+               goto free_cf;
+       ret = lls(lls_merge(play_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
+       lls_free_parse_result(cf_lpr, cmd);
+       if (ret < 0)
+               goto free_cf;
+       lls_free_parse_result(play_lpr, cmd);
+       play_lpr = merged_lpr;
+       loglevel = OPT_UINT32_VAL(LOGLEVEL);
+
+       ret = lls(lls_check_arg_count(play_lpr, 1, INT_MAX, &errctx));
+       if (ret < 0)
+               goto free_cf;
+       num_kmas = OPT_GIVEN(KEY_MAP);
+       for (i = 0; i < num_kmas; i++) {
+               const char *kma = lls_string_val(i, OPT_RESULT(KEY_MAP));
                if (*kma && strchr(kma + 1, ':'))
                        continue;
                PARA_EMERG_LOG("invalid key map arg: %s\n", kma);
-               goto err;
+               goto free_cf;
        }
-       free(config_file);
-       return;
-err:
-       free(config_file);
+       ret = 1;
+free_cf:
+       free(cf);
+       if (ret >= 0)
+               return;
+       lls_free_parse_result(play_lpr, cmd);
+fail:
+       if (errctx)
+               PARA_EMERG_LOG("%s\n", errctx);
+       free(errctx);
+       PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        exit(EXIT_FAILURE);
 }
 
@@ -292,10 +333,16 @@ static int shuffle_compare(__a_unused const void *a, __a_unused const void *b)
        return para_random(100) - 50;
 }
 
-static void shuffle(char **base, size_t num)
+static void init_shuffle_map(void)
 {
+       unsigned n, num_inputs = lls_num_inputs(play_lpr);
+       shuffle_map = para_malloc(num_inputs * sizeof(unsigned));
+       for (n = 0; n < num_inputs; n++)
+               shuffle_map[n] = n;
+       if (!OPT_GIVEN(RANDOMIZE))
+               return;
        srandom(time(NULL));
-       qsort(base, num, sizeof(char *), shuffle_compare);
+       qsort(shuffle_map, num_inputs, sizeof(unsigned), shuffle_compare);
 }
 
 static struct btr_node *new_recv_btrn(struct receiver_node *rn)
@@ -308,8 +355,9 @@ static struct btr_node *new_recv_btrn(struct receiver_node *rn)
 static int open_new_file(struct play_task *pt)
 {
        int ret;
-       char *tmp, *path = conf.inputs[pt->next_file], *errctx = NULL,
-               *argv[] = {"play", "-f", path, "-b", "0", NULL};
+       const char *path = get_playlist_file(pt->next_file);
+       char *tmp = para_strdup(path), *errctx;
+       char *argv[] = {"play", "-f", tmp, "-b", "0", NULL};
 
        PARA_NOTICE_LOG("next file: %s\n", path);
        wipe_receiver_node(pt);
@@ -425,9 +473,10 @@ fail:
 static int next_valid_file(struct play_task *pt)
 {
        int i, j = pt->current_file;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       FOR_EACH_PLAYLIST_FILE(i) {
-               j = (j + 1) % conf.inputs_num;
+       for (i = 0; i < num_inputs; i++) {
+               j = (j + 1) % num_inputs;
                if (!pt->invalid[j])
                        return j;
        }
@@ -472,11 +521,12 @@ static void kill_stream(struct play_task *pt)
 static int previous_valid_file(struct play_task *pt)
 {
        int i, j = pt->current_file;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       FOR_EACH_PLAYLIST_FILE(i) {
+       for (i = 0; i < num_inputs; i++) {
                j--;
                if (j < 0)
-                       j = conf.inputs_num - 1;
+                       j = num_inputs - 1;
                if (!pt->invalid[j])
                        return j;
        }
@@ -527,7 +577,7 @@ static const char *default_keyseqs[] = {INTERNAL_KEYMAP_ENTRIES};
 static const char *default_commands[] = {INTERNAL_KEYMAP_ENTRIES};
 #undef KEYMAP_ENTRY
 #define NUM_INTERNALLY_MAPPED_KEYS ARRAY_SIZE(default_commands)
-#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + conf.key_map_given)
+#define NUM_MAPPED_KEYS (NUM_INTERNALLY_MAPPED_KEYS + OPT_GIVEN(KEY_MAP))
 #define FOR_EACH_MAPPED_KEY(i) for (i = 0; i < NUM_MAPPED_KEYS; i++)
 
 static inline bool is_internal_key(int key)
@@ -558,9 +608,9 @@ static inline int get_key_map_idx(int key)
                get_internal_key_map_idx(key) : get_user_key_map_idx(key);
 }
 
-static inline char *get_user_key_map_arg(int key)
+static inline const char *get_user_key_map_arg(int key)
 {
-       return conf.key_map_arg[get_user_key_map_idx(key)];
+       return lls_string_val(get_user_key_map_idx(key), OPT_RESULT(KEY_MAP));
 }
 
 static inline char *get_internal_key_map_seq(int key)
@@ -761,7 +811,7 @@ static int com_info(struct play_task *pt,
        static char dflt[] = "[no information available]";
 
        sz = xasprintf(&buf, "playlist_pos: %u\npath: %s\n",
-               pt->current_file, conf.inputs[pt->current_file]);
+               pt->current_file, get_playlist_file(pt->current_file));
        btr_add_output(buf, sz, pt->btrn);
        buf = pt->afhi_txt? pt->afhi_txt : dflt;
        btr_add_output_dont_free(buf, strlen(buf), pt->btrn);
@@ -775,7 +825,7 @@ static void list_file(struct play_task *pt, int num)
        size_t sz;
 
        sz = xasprintf(&buf, "%s %4d %s\n", num == pt->current_file?
-               "*" : " ", num, conf.inputs[num]);
+               "*" : " ", num, get_playlist_file(num));
        btr_add_output(buf, sz, pt->btrn);
 }
 
@@ -799,8 +849,9 @@ static int com_ls(struct play_task *pt,
        __a_unused struct lls_parse_result *lpr)
 {
        int i;
+       unsigned num_inputs = lls_num_inputs(play_lpr);
 
-       FOR_EACH_PLAYLIST_FILE(i)
+       for (i = 0; i < num_inputs; i++)
                list_file(pt, i);
        return 0;
 }
@@ -831,7 +882,7 @@ static int com_play(struct play_task *pt, struct lls_parse_result *lpr)
        ret = para_atoi32(lls_input(0, lpr), &x);
        if (ret < 0)
                return ret;
-       if (x < 0 || x >= conf.inputs_num)
+       if (x < 0 || x >= lls_num_inputs(play_lpr))
                return -ERRNO_TO_PARA_ERROR(EINVAL);
        kill_stream(pt);
        pt->next_file = x;
@@ -1057,8 +1108,8 @@ static void session_open(struct play_task *pt)
        struct sigaction act;
 
        PARA_NOTICE_LOG("\n%s\n", version_text("play"));
-       if (conf.history_file_given)
-               history_file = para_strdup(conf.history_file_arg);
+       if (OPT_GIVEN(HISTORY_FILE))
+               history_file = para_strdup(OPT_STRING_VAL(HISTORY_FILE));
        else {
                char *home = para_homedir();
                history_file = make_message("%s/.paraslash/play.history",
@@ -1201,7 +1252,7 @@ static unsigned get_time_string(struct play_task *pt, char **result)
                length? (seconds * 100 + length / 2) / length : 0,
                length / 60,
                length % 60,
-               conf.inputs[pt->current_file]
+               get_playlist_file(pt->current_file)
        );
 }
 
@@ -1258,6 +1309,7 @@ int main(int argc, char *argv[])
 {
        int ret;
        struct play_task *pt = &play_task;
+       unsigned num_inputs;
 
        /* needed this early to make help work */
        recv_init();
@@ -1265,17 +1317,14 @@ int main(int argc, char *argv[])
        writer_init();
 
        sched.default_timeout.tv_sec = 5;
-
        parse_config_or_die(argc, argv);
-       if (conf.inputs_num == 0)
-               print_help_and_die();
        AFH_RECV->init();
        session_open(pt);
-       if (conf.randomize_given)
-               shuffle(conf.inputs, conf.inputs_num);
-       pt->invalid = para_calloc(sizeof(*pt->invalid) * conf.inputs_num);
+       num_inputs = lls_num_inputs(play_lpr);
+       init_shuffle_map();
+       pt->invalid = para_calloc(sizeof(*pt->invalid) * num_inputs);
        pt->rq = CRT_FILE_CHANGE;
-       pt->current_file = conf.inputs_num - 1;
+       pt->current_file = num_inputs - 1;
        pt->playing = true;
        pt->task = task_register(&(struct task_info){
                .name = "play",