8ccf80d541929194f984759e38d7ffa7998d3c97
[paraslash.git] / lsu.c
1 /* Copyright (C) 2018 Andre Noll <maan@tuebingen.mpg.de>, see file COPYING. */
2
3 /** \file lsu.c Utilities related to the lopsub library. */
4
5 #include <lopsub.h>
6 #include <regex.h>
7
8 #include "para.h"
9 #include "error.h"
10 #include "string.h"
11 #include "lsu.h"
12 #include "fd.h"
13
14 static int lsu_lopsub_error(int ret, char **errctx, char **result, unsigned *num_chars)
15 {
16         const char *se = para_strerror(-ret);
17         unsigned n;
18
19         if (*errctx)
20                 n = xasprintf(result, "%s: %s\n", *errctx, se);
21         else
22                 n = xasprintf(result, "lopsub error: %s\n", se);
23         free(*errctx);
24         *errctx = NULL;
25         if (num_chars)
26                 *num_chars = n;
27         return ret;
28 }
29
30 static void lsu_get_subcommand_summary(bool long_summary,
31                 const struct lls_suite *suite,
32                 const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
33                 char **result, unsigned *num_chars)
34 {
35         int i;
36         const struct lls_command *cmd;
37         const char *name, *aux_info = NULL;
38         struct para_buffer pb = {.max_size = 0 /* unlimited */};
39
40         para_printf(&pb, "Available subcommands:\n");
41         if (long_summary) {
42                 int maxname = 0, max_aux_info = 0;
43                 for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
44                         maxname = PARA_MAX(maxname,
45                                 (int)strlen(lls_command_name(cmd)));
46                         if (aux_info_cb) {
47                                 aux_info = aux_info_cb(i, false);
48                                 if (!aux_info)
49                                         continue;
50                                 max_aux_info = PARA_MAX(max_aux_info,
51                                         (int)strlen(aux_info));
52                         }
53                 }
54                 for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
55                         if (aux_info_cb)
56                                 aux_info = aux_info_cb(i, false);
57                         para_printf(&pb, "%-*s %-*s %s\n", maxname,
58                                 lls_command_name(cmd), max_aux_info, aux_info?
59                                 aux_info : "", lls_purpose(cmd));
60                 }
61         } else {
62                 unsigned n = 8;
63                 para_printf(&pb, "\t");
64                 for (i = 1; (cmd = lls_cmd(i, suite)); i++) {
65                         name = lls_command_name(cmd);
66                         if (i > 1)
67                                 n += para_printf(&pb, ", ");
68                         if (n > 70) {
69                                 para_printf(&pb, "\n\t");
70                                 n = 8;
71                         }
72                         n += para_printf(&pb, "%s", name);
73                 }
74                 para_printf(&pb, "\n");
75         }
76         *result = pb.buf;
77         if (num_chars)
78                 *num_chars = pb.offset;
79 }
80
81 /**
82  * A generic implementation of the help subcommand.
83  *
84  * This function returns the help text for the given subcommand, or the list of
85  * all subcommands if no non-option argument is given. The function is generic
86  * in that it works for arbitrary lopsub suites.
87  *
88  * \param long_help Applies to both command list and command help.
89  * \param suite The supercommand, if any, is omitted.
90  * \param lpr Used to determine whether a non-option argument is given.
91  * \param aux_info_cb Optional callback, may return NULL, static memory.
92  * \param result Must be freed by the caller.
93  * \param num_chars Initialized to the length of the returned string, optional.
94  *
95  * If the optional aux_info_cb function pointer is not NULL, the callback
96  * function must return the string representation of the aux_info structure of
97  * the given command, or NULL to indicate that this command has no aux info
98  * structure.
99  *
100  * The function fails if lpr has more than one non-option argument, or if there
101  * is exactly one non-option argument, but this argument is not the name of a
102  * subcommand in the given lopsub suite.
103  *
104  * \return Standard. In the failure case a suitable error message is returned
105  * via the result pointer and num_chars is set accordingly.
106  */
107 int lsu_com_help(bool long_help, const struct lls_parse_result *lpr,
108                 const struct lls_suite *suite,
109                 const char *(*aux_info_cb)(unsigned cmd_num, bool verbose),
110                 char **result, unsigned *num_chars)
111 {
112         int ret;
113         unsigned n;
114         char *errctx, *tmp;
115         const char *arg, *aux_info = NULL;
116         const struct lls_command *cmd;
117
118         ret = lls(lls_check_arg_count(lpr, 0, 1, &errctx));
119         if (ret < 0)
120                 return lsu_lopsub_error(ret, &errctx, result, num_chars);
121         if (lls_num_inputs(lpr) == 0) {
122                 lsu_get_subcommand_summary(long_help, suite,
123                         aux_info_cb, result, num_chars);
124                 return 0;
125         }
126         arg = lls_input(0, lpr);
127         ret = lls(lls_lookup_subcmd(arg, suite, &errctx));
128         if (ret < 0)
129                 return lsu_lopsub_error(ret, &errctx, result, num_chars);
130         cmd = lls_cmd(ret, suite);
131         tmp = long_help? lls_long_help(cmd) : lls_short_help(cmd);
132         if (aux_info_cb)
133                 aux_info = aux_info_cb(ret, true);
134         n = xasprintf(result, "%s%s%s", tmp, aux_info? aux_info : "",
135                 aux_info? "\n" : "");
136         free(tmp);
137         if (num_chars)
138                 *num_chars = n;
139         return 1;
140 }
141
142 /**
143  * Merge command line options and config file options.
144  *
145  * This function parses the options stored in the configuration file and merges
146  * them with the currently effective options. If the application supports
147  * config files, it is supposed to call this after the command line options
148  * have been parsed. If the application also supports config file reloading,
149  * the function will be called for that purpose.
150  *
151  * \param path Config file path, usually the argument to --config-file.
152  * \param dflt Relative to ~/.paraslash, ignored if path is not NULL.
153  * \param lpr Value-result pointer.
154  * \param cmd Passed to lls_parse() and lls_merge().
155  * \param suite Needed to tell whether cmd is the supercommand.
156  * \param flags See enum \ref lsu_merge_cf_flags.
157  *
158  * The function does nothing if path is NULL and the default config file does
159  * not exist, or if path is an empty file. Otherwise, the options of the config
160  * file are parsed, the parse result is merged with lpr, and the merged parse
161  * result is returned via lpr.
162  *
163  * By default, lpr is freed if the merge was done, but this can be changed by
164  * including MCF_DONT_FREE flags.
165  *
166  * \return Zero if there was nothing to do, one if the config file options were
167  * merged successfully, negative error code on failure. It is considered an error
168  * if path is given, but the file does not exist.
169  */
170 int lsu_merge_config_file_options(const char *path, const char *dflt,
171                 struct lls_parse_result **lpr, const struct lls_command *cmd,
172                 const struct lls_suite *suite, unsigned flags)
173 {
174         int ret;
175         void *map;
176         size_t sz;
177         int cf_argc;
178         char *cf, **cf_argv, *errctx = NULL;
179         struct lls_parse_result *old_lpr = *lpr, *cf_lpr, *merged_lpr;
180         const char *subcmd_name;
181
182         if (path)
183                 cf = para_strdup(path);
184         else {
185                 char *home = para_homedir();
186                 cf = make_message("%s/.paraslash/%s", home, dflt);
187                 free(home);
188         }
189         ret = mmap_full_file(cf, O_RDONLY, &map, &sz, NULL);
190         if (ret < 0) {
191                 if (ret == -E_EMPTY)
192                         ret = 0;
193                 else if (ret == -ERRNO_TO_PARA_ERROR(ENOENT) && !path)
194                         ret = 0;
195                 else
196                         PARA_ERROR_LOG("failed to mmap config file %s\n", cf);
197                 goto free_cf;
198         }
199         subcmd_name = (lls_cmd(0, suite) == cmd)? NULL : lls_command_name(cmd);
200         ret = lls(lls_convert_config(map, sz, subcmd_name, &cf_argv, &errctx));
201         para_munmap(map, sz);
202         if (ret < 0) {
203                 PARA_ERROR_LOG("failed to convert config file %s\n", cf);
204                 goto lopsub_error;
205         }
206         cf_argc = ret;
207         ret = lls(lls_parse(cf_argc, cf_argv, cmd, &cf_lpr, &errctx));
208         lls_free_argv(cf_argv);
209         if (ret < 0) {
210                 PARA_ERROR_LOG("failed to parse config file %s\n", cf);
211                 goto lopsub_error;
212         }
213         if (flags & MCF_OVERRIDE)
214                 ret = lls(lls_merge(cf_lpr, old_lpr, cmd, &merged_lpr, &errctx));
215         else
216                 ret = lls(lls_merge(old_lpr, cf_lpr, cmd, &merged_lpr, &errctx));
217         lls_free_parse_result(cf_lpr, cmd);
218         if (ret < 0) {
219                 PARA_ERROR_LOG("could not merge options in %s\n", cf);
220                 goto lopsub_error;
221         }
222         if (!(flags & MCF_DONT_FREE))
223                 lls_free_parse_result(old_lpr, cmd);
224         *lpr = merged_lpr;
225         ret = 1;
226         goto free_cf;
227 lopsub_error:
228         assert(ret < 0);
229         if (errctx)
230                 PARA_ERROR_LOG("lopsub error: %s\n", errctx);
231         free(errctx);
232 free_cf:
233         free(cf);
234         return ret;
235 }