cast: New macro NULL_SENTINEL.
[pspp] / src / ui / terminal / terminal-opts.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2010  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "terminal-opts.h"
20
21 #include <stdbool.h>
22 #include <xalloc.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25
26 #include "data/settings.h"
27 #include "data/file-name.h"
28 #include "language/syntax-file.h"
29 #include "libpspp/argv-parser.h"
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32 #include "libpspp/compiler.h"
33 #include "libpspp/getl.h"
34 #include "libpspp/llx.h"
35 #include "libpspp/str.h"
36 #include "libpspp/string-array.h"
37 #include "libpspp/string-map.h"
38 #include "libpspp/string-set.h"
39 #include "libpspp/version.h"
40 #include "output/driver.h"
41 #include "output/driver-provider.h"
42 #include "output/msglog.h"
43 #include "ui/terminal/msg-ui.h"
44 #include "ui/terminal/read-line.h"
45
46 #include "gl/error.h"
47 #include "gl/progname.h"
48 #include "gl/version-etc.h"
49 #include "gl/xmemdup0.h"
50
51 #include "gettext.h"
52 #define _(msgid) gettext (msgid)
53 #define N_(msgid) msgid
54
55 struct terminal_opts
56   {
57     struct source_stream *source_stream;
58     enum syntax_mode syntax_mode;
59     struct string_map options;  /* Output driver options. */
60     bool has_output_driver;
61     bool has_terminal_driver;
62     bool has_error_file;
63     bool process_statrc;
64   };
65
66 enum
67   {
68     OPT_TESTING_MODE,
69     OPT_ERROR_FILE,
70     OPT_OUTPUT,
71     OPT_OUTPUT_OPTION,
72     OPT_NO_OUTPUT,
73     OPT_INTERACTIVE,
74     OPT_NO_STATRC,
75     OPT_HELP,
76     OPT_VERSION,
77     N_TERMINAL_OPTIONS
78   };
79
80 static struct argv_option terminal_argv_options[N_TERMINAL_OPTIONS] =
81   {
82     {"testing-mode", 0, no_argument, OPT_TESTING_MODE},
83     {"error-file", 'e', required_argument, OPT_ERROR_FILE},
84     {"output", 'o', required_argument, OPT_OUTPUT},
85     {NULL, 'O', required_argument, OPT_OUTPUT_OPTION},
86     {"no-output", 0, no_argument, OPT_NO_OUTPUT},
87     {"interactive", 'i', no_argument, OPT_INTERACTIVE},
88     {"no-statrc", 'r', no_argument, OPT_NO_STATRC},
89     {"help", 'h', no_argument, OPT_HELP},
90     {"version", 'V', no_argument, OPT_VERSION},
91   };
92
93 static void
94 register_output_driver (struct terminal_opts *to)
95 {
96   if (!string_map_is_empty (&to->options))
97     {
98       struct output_driver *driver;
99
100       driver = output_driver_create (&to->options);
101       if (driver != NULL)
102         {
103           output_driver_register (driver);
104
105           to->has_output_driver = true;
106           if (driver->device_type == SETTINGS_DEVICE_TERMINAL)
107             to->has_terminal_driver = true;
108         }
109       string_map_clear (&to->options);
110     }
111 }
112
113 static void
114 parse_output_option (struct terminal_opts *to, const char *option)
115 {
116   const char *equals;
117   char *key, *value;
118
119   equals = strchr (option, '=');
120   if (equals == NULL)
121     {
122       error (0, 0, _("%s: output option missing `='"), option);
123       return;
124     }
125
126   key = xmemdup0 (option, equals - option);
127   if (string_map_contains (&to->options, key))
128     {
129       error (0, 0, _("%s: output option specified more than once"), key);
130       free (key);
131       return;
132     }
133
134   value = xmemdup0 (equals + 1, strlen (equals + 1));
135   string_map_insert_nocopy (&to->options, key, value);
136 }
137
138 static char *
139 get_supported_formats (void)
140 {
141   const struct string_set_node *node;
142   struct string_array format_array;
143   struct string_set format_set;
144   char *format_string;
145   const char *format;
146   size_t i;
147
148   /* Get supported formats as unordered set. */
149   string_set_init (&format_set);
150   output_get_supported_formats (&format_set);
151
152   /* Converted supported formats to sorted array. */
153   string_array_init (&format_array);
154   STRING_SET_FOR_EACH (format, node, &format_set)
155     string_array_append (&format_array, format);
156   string_array_sort (&format_array);
157   string_set_destroy (&format_set);
158
159   /* Converted supported formats to string. */
160   format_string = string_array_join (&format_array, " ");
161   string_array_destroy (&format_array);
162   return format_string;
163 }
164
165 static char *
166 get_default_include_path (void)
167 {
168   struct source_stream *ss;
169   struct string dst;
170   char **path;
171   size_t i;
172
173   ss = create_source_stream ();
174   path = getl_include_path (ss);
175   ds_init_empty (&dst);
176   for (i = 0; path[i] != NULL; i++)
177     ds_put_format (&dst, " %s", path[i]);
178   destroy_source_stream (ss);
179
180   return ds_steal_cstr (&dst);
181 }
182
183 static void
184 usage (void)
185 {
186   char *supported_formats = get_supported_formats ();
187   char *default_include_path = get_default_include_path ();
188
189   printf (_("\
190 PSPP, a program for statistical analysis of sample data.\n\
191 Usage: %s [OPTION]... FILE...\n\
192 \n\
193 Arguments to long options also apply to equivalent short options.\n\
194 \n\
195 Output options:\n\
196   -o, --output=FILE         output to FILE, default format from FILE's name\n\
197   -O format=FORMAT          override format for previous -o\n\
198   -O OPTION=VALUE           set output option to customize previous -o\n\
199   -O device={terminal|listing}  override device type for previous -o\n\
200   -e, --error-file=FILE     append errors, warnings, and notes to FILE\n\
201   --no-output               disable default output driver\n\
202 Supported output formats: %s\n\
203 \n\
204 Language options:\n\
205   -I, --include=DIR         append DIR to search path\n\
206   -I-, --no-include         clear search path\n\
207   -r, --no-statrc           disable running rc file at startup\n\
208   -a, --algorithm={compatible|enhanced}\n\
209                             set to `compatible' if you want output\n\
210                             calculated from broken algorithms\n\
211   -x, --syntax={compatible|enhanced}\n\
212                             set to `compatible' to disable PSPP extensions\n\
213   -i, --interactive         interpret syntax in interactive mode\n\
214   -s, --safer               don't allow some unsafe operations\n\
215 Default search path:%s\n\
216 \n\
217 Informative output:\n\
218   -h, --help                display this help and exit\n\
219   -V, --version             output version information and exit\n\
220 \n\
221 Non-option arguments are interpreted as syntax files to execute.\n"),
222           program_name, supported_formats, default_include_path);
223
224   free (supported_formats);
225   free (default_include_path);
226
227   emit_bug_reporting_address ();
228   exit (EXIT_SUCCESS);
229 }
230
231 static void
232 terminal_option_callback (int id, void *to_)
233 {
234   struct terminal_opts *to = to_;
235
236   switch (id)
237     {
238     case OPT_TESTING_MODE:
239       settings_set_testing_mode (true);
240       break;
241
242     case OPT_ERROR_FILE:
243       if (!strcmp (optarg, "none") || msglog_create (optarg))
244         to->has_error_file = true;
245       break;
246
247     case OPT_OUTPUT:
248       register_output_driver (to);
249       string_map_insert (&to->options, "output-file", optarg);
250       break;
251
252     case OPT_OUTPUT_OPTION:
253       parse_output_option (to, optarg);
254       break;
255
256     case OPT_NO_OUTPUT:
257       /* Pretend that we already have an output driver, which disables adding
258          one in terminal_opts_done() when we don't already have one. */
259       to->has_output_driver = true;
260       break;
261
262     case OPT_INTERACTIVE:
263       to->syntax_mode = GETL_INTERACTIVE;
264       break;
265
266     case OPT_NO_STATRC:
267       to->process_statrc = false;
268       break;
269
270     case OPT_HELP:
271       usage ();
272       exit (EXIT_SUCCESS);
273
274     case OPT_VERSION:
275       version_etc (stdout, "pspp", PACKAGE_NAME, PACKAGE_VERSION,
276                    "Ben Pfaff", "John Darrington", "Jason Stover",
277                    NULL_SENTINEL);
278       exit (EXIT_SUCCESS);
279
280     default:
281       NOT_REACHED ();
282     }
283 }
284
285 struct terminal_opts *
286 terminal_opts_init (struct argv_parser *ap, struct source_stream *ss)
287 {
288   struct terminal_opts *to;
289
290   to = xzalloc (sizeof *to);
291   to->source_stream = ss;
292   to->syntax_mode = GETL_BATCH;
293   string_map_init (&to->options);
294   to->has_output_driver = false;
295   to->has_error_file = false;
296   to->process_statrc = true;
297
298   argv_parser_add_options (ap, terminal_argv_options, N_TERMINAL_OPTIONS,
299                            terminal_option_callback, to);
300   return to;
301 }
302
303 static void
304 add_syntax_file (struct terminal_opts *to, const char *file_name)
305 {
306   if (!strcmp (file_name, "-") && isatty (STDIN_FILENO))
307     getl_append_source (to->source_stream, create_readln_source (),
308                         GETL_INTERACTIVE, ERRMODE_CONTINUE);
309   else
310     getl_append_source (to->source_stream,
311                         create_syntax_file_source (file_name),
312                         to->syntax_mode, ERRMODE_CONTINUE);
313 }
314
315 void
316 terminal_opts_done (struct terminal_opts *to, int argc, char *argv[])
317 {
318   if (to->process_statrc)
319     {
320       char *rc = fn_search_path ("rc", getl_include_path (to->source_stream));
321       if (rc != NULL)
322         {
323           getl_append_source (to->source_stream,
324                               create_syntax_file_source (rc), GETL_BATCH,
325                               ERRMODE_CONTINUE);
326           free (rc);
327         }
328     }
329
330   if (optind < argc)
331     {
332       int i;
333
334       for (i = optind; i < argc; i++)
335         add_syntax_file (to, argv[i]);
336     }
337   else
338     add_syntax_file (to, "-");
339
340   register_output_driver (to);
341   if (!to->has_output_driver)
342     {
343       string_map_insert (&to->options, "output-file", "-");
344       string_map_insert (&to->options, "format", "txt");
345       register_output_driver (to);
346     }
347
348   if (!to->has_terminal_driver && !to->has_error_file)
349     msglog_create ("-");
350
351   string_map_destroy (&to->options);
352   free (to);
353 }