output: Modernize how drivers are initialized.
[pspp] / src / output / options.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 2010, 2014 Free Software Foundation, Inc.
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 "output/options.h"
20
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "libpspp/hash-functions.h"
29 #include "libpspp/hmap.h"
30 #include "libpspp/str.h"
31 #include "libpspp/string-map.h"
32 #include "output/driver-provider.h"
33 #include "output/measure.h"
34 #include "output/table.h"
35
36 #include "gl/xalloc.h"
37
38 #include "gettext.h"
39 #define _(msgid) gettext (msgid)
40
41 /* Creates and returns a new struct driver_option for driver DRIVER_NAME (which
42    is used only in error messages).  The option named NAME is extracted from
43    OPTIONS.  DEFAULT_VALUE is the default value of the option, used if the
44    given option was not supplied or was invalid. */
45 struct driver_option *
46 driver_option_get (const char *driver_name, struct string_map *options,
47                    const char *name, const char *default_value)
48 {
49   struct driver_option *option = xmalloc (sizeof *option);
50   option->driver_name = xstrdup (driver_name);
51   option->name = xstrdup (name);
52   option->value = string_map_find_and_delete (options, name);
53   option->default_value = xstrdup_if_nonnull (default_value);
54   return option;
55 }
56
57 /* Frees driver option O. */
58 void
59 driver_option_destroy (struct driver_option *o)
60 {
61   if (o != NULL)
62     {
63       free (o->driver_name);
64       free (o->name);
65       free (o->value);
66       free (o->default_value);
67       free (o);
68     }
69 }
70
71 /* Stores the paper size of the value of option O into *H and *V, in 1/72000"
72    units.  Any syntax accepted by measure_paper() may be used.
73
74    Destroys O. */
75 void
76 parse_paper_size (struct driver_option *o, int *h, int *v)
77 {
78   if (o->value == NULL || !measure_paper (o->value, h, v))
79     measure_paper (o->default_value, h, v);
80   driver_option_destroy (o);
81 }
82
83 static int
84 do_parse_boolean (const char *driver_name, const char *key,
85                   const char *value)
86 {
87   if (!strcmp (value, "on") || !strcmp (value, "true")
88       || !strcmp (value, "yes") || !strcmp (value, "1"))
89     return true;
90   else if (!strcmp (value, "off") || !strcmp (value, "false")
91            || !strcmp (value, "no") || !strcmp (value, "0"))
92     return false;
93   else
94     {
95       msg (MW, _("%s: `%s' is `%s' but a Boolean value is required"),
96              driver_name, value, key);
97       return -1;
98     }
99 }
100
101 /* Parses and return O's value as a Boolean value.  "true" and "false", "yes"
102    and "no", "on" and "off", and "1" and "0" are acceptable boolean strings.
103
104    Destroys O. */
105 bool
106 parse_boolean (struct driver_option *o)
107 {
108   bool retval;
109
110   retval = do_parse_boolean (o->driver_name, o->name, o->default_value) > 0;
111   if (o->value != NULL)
112     {
113       int value = do_parse_boolean (o->driver_name, o->name, o->value);
114       if (value >= 0)
115         retval = value;
116     }
117
118   driver_option_destroy (o);
119
120   return retval;
121 }
122
123 /* Parses O's value as an enumeration constant.  The arguments to this function
124    consist of a series of string/int pairs, terminated by a null pointer value.
125    O's value is compared to each string in turn, and parse_enum() returns the
126    int associated with the first matching string.  If there is no match, or if
127    O has no user-specified value, then O's default value is treated the same
128    way.  If the default value still does not match, parse_enum() returns 0.
129
130    Example: parse_enum (o, "a", 1, "b", 2, NULL_SENTINEL) returns 1 if O's
131    value if "a", 2 if O's value is "b".
132
133    Destroys O. */
134 int
135 parse_enum (struct driver_option *o, ...)
136 {
137   va_list args;
138   int retval;
139
140   retval = 0;
141   va_start (args, o);
142   for (;;)
143     {
144       const char *s;
145       int value;
146
147       s = va_arg (args, const char *);
148       if (s == NULL)
149         {
150           if (o->value != NULL)
151             {
152               struct string choices;
153               int i;
154
155               ds_init_empty (&choices);
156               va_end (args);
157               va_start (args, o);
158               for (i = 0; ; i++)
159                 {
160                   s = va_arg (args, const char *);
161                   if (s == NULL)
162                     break;
163                   value = va_arg (args, int);
164
165                   if (i > 0)
166                     ds_put_cstr (&choices, ", ");
167                   ds_put_format (&choices, "`%s'", s);
168                 }
169
170               msg (MW, _("%s: `%s' is `%s' but one of the following "
171                              "is required: %s"),
172                      o->driver_name, o->name, o->value, ds_cstr (&choices));
173               ds_destroy (&choices);
174             }
175           break;
176         }
177       value = va_arg (args, int);
178
179       if (o->value != NULL && !strcmp (s, o->value))
180         {
181           retval = value;
182           break;
183         }
184       else if (!strcmp (s, o->default_value))
185         retval = value;
186     }
187   va_end (args);
188   driver_option_destroy (o);
189   return retval;
190 }
191
192 /* Parses O's value as an integer in the range MIN_VALUE to MAX_VALUE
193    (inclusive) and returns the integer.
194
195    Destroys O. */
196 int
197 parse_int (struct driver_option *o, int min_value, int max_value)
198 {
199   int retval = strtol (o->default_value, NULL, 0);
200
201   if (o->value != NULL)
202     {
203       int value;
204       char *tail;
205
206       errno = 0;
207       value = strtol (o->value, &tail, 0);
208       if (tail != o->value && *tail == '\0' && errno != ERANGE
209           && value >= min_value && value <= max_value)
210         retval = value;
211       else if (max_value == INT_MAX)
212         {
213           if (min_value == 0)
214             msg (MW, _("%s: `%s' is `%s' but a non-negative integer "
215                            "is required"),
216                    o->driver_name, o->name, o->value);
217           else if (min_value == 1)
218             msg (MW, _("%s: `%s' is `%s' but a positive integer is "
219                            "required"), o->driver_name, o->name, o->value);
220           else if (min_value == INT_MIN)
221             msg (MW, _("%s: `%s' is `%s' but an integer is required"),
222                    o->driver_name, o->name, o->value);
223           else
224             msg (MW, _("%s: `%s' is `%s' but an integer greater "
225                            "than %d is required"),
226                    o->driver_name, o->name, o->value, min_value - 1);
227         }
228       else
229         msg (MW, _("%s: `%s' is `%s'  but an integer between %d and "
230                        "%d is required"),
231                o->driver_name, o->name, o->value, min_value, max_value);
232     }
233
234   driver_option_destroy (o);
235   return retval;
236 }
237
238 /* Parses O's value as a dimension, as understood by measure_dimension(), and
239    returns its length in units of 1/72000".
240
241    Destroys O. */
242 int
243 parse_dimension (struct driver_option *o)
244 {
245   int retval;
246
247   retval = (o->value != NULL ? measure_dimension (o->value)
248             : o->default_value != NULL ? measure_dimension (o->default_value)
249             : -1);
250
251   driver_option_destroy (o);
252   return retval;
253 }
254
255 /* Parses O's value as a string and returns it as a malloc'd string that the
256    caller is responsible for freeing.
257
258    Destroys O. */
259 char *
260 parse_string (struct driver_option *o)
261 {
262   char *retval = xstrdup (o->value != NULL ? o->value : o->default_value);
263   driver_option_destroy (o);
264   return retval;
265 }
266
267 static char *
268 default_chart_file_name (const char *file_name)
269 {
270   if (strcmp (file_name, "-"))
271     {
272       const char *extension = strrchr (file_name, '.');
273       int stem_length = extension ? extension - file_name : strlen (file_name);
274       return xasprintf ("%.*s-#", stem_length, file_name);
275     }
276   else
277     return NULL;
278 }
279
280 /* Parses and returns a chart file name, or NULL if no charts should be output.
281    If a nonnull string is returned, it will contain at least one '#' character,
282    which the client will presumably replace by a number as part of writing
283    charts to separate files.
284
285    If O->value is "none", then this function returns NULL.
286
287    If O->value is non-NULL but not "none", returns a copy of that string (if it
288    contains '#').
289
290    If O->value is NULL, then O's default_value should be the name of the main
291    output file.  Returns NULL if default_value is "-", and otherwise returns a
292    copy of string string with its extension stripped off and "-#.png" appended.
293
294    Destroys O. */
295 char *
296 parse_chart_file_name (struct driver_option *o)
297 {
298   char *chart_file_name;
299
300   if (o->value != NULL)
301     {
302       if (!strcmp (o->value, "none"))
303         chart_file_name = NULL;
304       else if (strchr (o->value, '#') != NULL)
305         chart_file_name = xstrdup (o->value);
306       else
307         {
308           msg (MW, _("%s: `%s' is `%s' but a file name that contains "
309                          "`#' is required."),
310                o->driver_name, o->name, o->value);
311           chart_file_name = default_chart_file_name (o->default_value);
312         }
313     }
314   else
315     chart_file_name = default_chart_file_name (o->default_value);
316
317   driver_option_destroy (o);
318
319   return chart_file_name;
320 }
321
322 static int
323 lookup_color_name (const char *s)
324 {
325   struct color
326     {
327       struct hmap_node hmap_node;
328       const char *name;
329       int code;
330     };
331
332   static struct color colors[] =
333     {
334       { .name = "aliceblue", .code = 0xf0f8ff },
335       { .name = "antiquewhite", .code = 0xfaebd7 },
336       { .name = "aqua", .code = 0x00ffff },
337       { .name = "aquamarine", .code = 0x7fffd4 },
338       { .name = "azure", .code = 0xf0ffff },
339       { .name = "beige", .code = 0xf5f5dc },
340       { .name = "bisque", .code = 0xffe4c4 },
341       { .name = "black", .code = 0x000000 },
342       { .name = "blanchedalmond", .code = 0xffebcd },
343       { .name = "blue", .code = 0x0000ff },
344       { .name = "blueviolet", .code = 0x8a2be2 },
345       { .name = "brown", .code = 0xa52a2a },
346       { .name = "burlywood", .code = 0xdeb887 },
347       { .name = "cadetblue", .code = 0x5f9ea0 },
348       { .name = "chartreuse", .code = 0x7fff00 },
349       { .name = "chocolate", .code = 0xd2691e },
350       { .name = "coral", .code = 0xff7f50 },
351       { .name = "cornflowerblue", .code = 0x6495ed },
352       { .name = "cornsilk", .code = 0xfff8dc },
353       { .name = "crimson", .code = 0xdc143c },
354       { .name = "cyan", .code = 0x00ffff },
355       { .name = "darkblue", .code = 0x00008b },
356       { .name = "darkcyan", .code = 0x008b8b },
357       { .name = "darkgoldenrod", .code = 0xb8860b },
358       { .name = "darkgray", .code = 0xa9a9a9 },
359       { .name = "darkgreen", .code = 0x006400 },
360       { .name = "darkgrey", .code = 0xa9a9a9 },
361       { .name = "darkkhaki", .code = 0xbdb76b },
362       { .name = "darkmagenta", .code = 0x8b008b },
363       { .name = "darkolivegreen", .code = 0x556b2f },
364       { .name = "darkorange", .code = 0xff8c00 },
365       { .name = "darkorchid", .code = 0x9932cc },
366       { .name = "darkred", .code = 0x8b0000 },
367       { .name = "darksalmon", .code = 0xe9967a },
368       { .name = "darkseagreen", .code = 0x8fbc8f },
369       { .name = "darkslateblue", .code = 0x483d8b },
370       { .name = "darkslategray", .code = 0x2f4f4f },
371       { .name = "darkslategrey", .code = 0x2f4f4f },
372       { .name = "darkturquoise", .code = 0x00ced1 },
373       { .name = "darkviolet", .code = 0x9400d3 },
374       { .name = "deeppink", .code = 0xff1493 },
375       { .name = "deepskyblue", .code = 0x00bfff },
376       { .name = "dimgray", .code = 0x696969 },
377       { .name = "dimgrey", .code = 0x696969 },
378       { .name = "dodgerblue", .code = 0x1e90ff },
379       { .name = "firebrick", .code = 0xb22222 },
380       { .name = "floralwhite", .code = 0xfffaf0 },
381       { .name = "forestgreen", .code = 0x228b22 },
382       { .name = "fuchsia", .code = 0xff00ff },
383       { .name = "gainsboro", .code = 0xdcdcdc },
384       { .name = "ghostwhite", .code = 0xf8f8ff },
385       { .name = "gold", .code = 0xffd700 },
386       { .name = "goldenrod", .code = 0xdaa520 },
387       { .name = "gray", .code = 0x808080 },
388       { .name = "green", .code = 0x008000 },
389       { .name = "greenyellow", .code = 0xadff2f },
390       { .name = "grey", .code = 0x808080 },
391       { .name = "honeydew", .code = 0xf0fff0 },
392       { .name = "hotpink", .code = 0xff69b4 },
393       { .name = "indianred", .code = 0xcd5c5c },
394       { .name = "indigo", .code = 0x4b0082 },
395       { .name = "ivory", .code = 0xfffff0 },
396       { .name = "khaki", .code = 0xf0e68c },
397       { .name = "lavender", .code = 0xe6e6fa },
398       { .name = "lavenderblush", .code = 0xfff0f5 },
399       { .name = "lawngreen", .code = 0x7cfc00 },
400       { .name = "lemonchiffon", .code = 0xfffacd },
401       { .name = "lightblue", .code = 0xadd8e6 },
402       { .name = "lightcoral", .code = 0xf08080 },
403       { .name = "lightcyan", .code = 0xe0ffff },
404       { .name = "lightgoldenrodyellow", .code = 0xfafad2 },
405       { .name = "lightgray", .code = 0xd3d3d3 },
406       { .name = "lightgreen", .code = 0x90ee90 },
407       { .name = "lightgrey", .code = 0xd3d3d3 },
408       { .name = "lightpink", .code = 0xffb6c1 },
409       { .name = "lightsalmon", .code = 0xffa07a },
410       { .name = "lightseagreen", .code = 0x20b2aa },
411       { .name = "lightskyblue", .code = 0x87cefa },
412       { .name = "lightslategray", .code = 0x778899 },
413       { .name = "lightslategrey", .code = 0x778899 },
414       { .name = "lightsteelblue", .code = 0xb0c4de },
415       { .name = "lightyellow", .code = 0xffffe0 },
416       { .name = "lime", .code = 0x00ff00 },
417       { .name = "limegreen", .code = 0x32cd32 },
418       { .name = "linen", .code = 0xfaf0e6 },
419       { .name = "magenta", .code = 0xff00ff },
420       { .name = "maroon", .code = 0x800000 },
421       { .name = "mediumaquamarine", .code = 0x66cdaa },
422       { .name = "mediumblue", .code = 0x0000cd },
423       { .name = "mediumorchid", .code = 0xba55d3 },
424       { .name = "mediumpurple", .code = 0x9370db },
425       { .name = "mediumseagreen", .code = 0x3cb371 },
426       { .name = "mediumslateblue", .code = 0x7b68ee },
427       { .name = "mediumspringgreen", .code = 0x00fa9a },
428       { .name = "mediumturquoise", .code = 0x48d1cc },
429       { .name = "mediumvioletred", .code = 0xc71585 },
430       { .name = "midnightblue", .code = 0x191970 },
431       { .name = "mintcream", .code = 0xf5fffa },
432       { .name = "mistyrose", .code = 0xffe4e1 },
433       { .name = "moccasin", .code = 0xffe4b5 },
434       { .name = "navajowhite", .code = 0xffdead },
435       { .name = "navy", .code = 0x000080 },
436       { .name = "oldlace", .code = 0xfdf5e6 },
437       { .name = "olive", .code = 0x808000 },
438       { .name = "olivedrab", .code = 0x6b8e23 },
439       { .name = "orange", .code = 0xffa500 },
440       { .name = "orangered", .code = 0xff4500 },
441       { .name = "orchid", .code = 0xda70d6 },
442       { .name = "palegoldenrod", .code = 0xeee8aa },
443       { .name = "palegreen", .code = 0x98fb98 },
444       { .name = "paleturquoise", .code = 0xafeeee },
445       { .name = "palevioletred", .code = 0xdb7093 },
446       { .name = "papayawhip", .code = 0xffefd5 },
447       { .name = "peachpuff", .code = 0xffdab9 },
448       { .name = "peru", .code = 0xcd853f },
449       { .name = "pink", .code = 0xffc0cb },
450       { .name = "plum", .code = 0xdda0dd },
451       { .name = "powderblue", .code = 0xb0e0e6 },
452       { .name = "purple", .code = 0x800080 },
453       { .name = "red", .code = 0xff0000 },
454       { .name = "rosybrown", .code = 0xbc8f8f },
455       { .name = "royalblue", .code = 0x4169e1 },
456       { .name = "saddlebrown", .code = 0x8b4513 },
457       { .name = "salmon", .code = 0xfa8072 },
458       { .name = "sandybrown", .code = 0xf4a460 },
459       { .name = "seagreen", .code = 0x2e8b57 },
460       { .name = "seashell", .code = 0xfff5ee },
461       { .name = "sienna", .code = 0xa0522d },
462       { .name = "silver", .code = 0xc0c0c0 },
463       { .name = "skyblue", .code = 0x87ceeb },
464       { .name = "slateblue", .code = 0x6a5acd },
465       { .name = "slategray", .code = 0x708090 },
466       { .name = "slategrey", .code = 0x708090 },
467       { .name = "snow", .code = 0xfffafa },
468       { .name = "springgreen", .code = 0x00ff7f },
469       { .name = "steelblue", .code = 0x4682b4 },
470       { .name = "tan", .code = 0xd2b48c },
471       { .name = "teal", .code = 0x008080 },
472       { .name = "thistle", .code = 0xd8bfd8 },
473       { .name = "tomato", .code = 0xff6347 },
474       { .name = "turquoise", .code = 0x40e0d0 },
475       { .name = "violet", .code = 0xee82ee },
476       { .name = "wheat", .code = 0xf5deb3 },
477       { .name = "white", .code = 0xffffff },
478       { .name = "whitesmoke", .code = 0xf5f5f5 },
479       { .name = "yellow", .code = 0xffff00 },
480       { .name = "yellowgreen", .code = 0x9acd32 },
481     };
482
483   static struct hmap color_table = HMAP_INITIALIZER (color_table);
484
485   if (hmap_is_empty (&color_table))
486     for (size_t i = 0; i < sizeof colors / sizeof *colors; i++)
487       hmap_insert (&color_table, &colors[i].hmap_node,
488                    hash_string (colors[i].name, 0));
489
490   const struct color *color;
491   HMAP_FOR_EACH_WITH_HASH (color, struct color, hmap_node,
492                            hash_string (s, 0), &color_table)
493     if (!strcmp (color->name, s))
494       return color->code;
495   return -1;
496 }
497
498 bool
499 parse_color__ (const char *s, struct cell_color *color)
500 {
501   /* #rrrrggggbbbb */
502   uint16_t r16, g16, b16;
503   int len;
504   if (sscanf (s, "#%4"SCNx16"%4"SCNx16"%4"SCNx16"%n",
505               &r16, &g16, &b16, &len) == 3
506       && len == 13
507       && !s[len])
508     {
509       color->r = r16 >> 8;
510       color->g = g16 >> 8;
511       color->b = b16 >> 8;
512       color->alpha = 255;
513       return true;
514     }
515
516   /* #rrggbb */
517   uint8_t r, g, b;
518   if (sscanf (s, "#%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
519       && len == 7
520       && !s[len])
521     {
522       color->r = r;
523       color->g = g;
524       color->b = b;
525       color->alpha = 255;
526       return true;
527     }
528
529   /* rrggbb */
530   if (sscanf (s, "%2"SCNx8"%2"SCNx8"%2"SCNx8"%n", &r, &g, &b, &len) == 3
531       && len == 6
532       && !s[len])
533     {
534       color->r = r;
535       color->g = g;
536       color->b = b;
537       color->alpha = 255;
538       return true;
539     }
540
541   /* rgb(r,g,b) */
542   if (sscanf (s, "rgb (%"SCNi8" , %"SCNi8" , %"SCNi8") %n",
543               &r, &g, &b, &len) == 3
544       && !s[len])
545     {
546       color->r = r;
547       color->g = g;
548       color->b = b;
549       color->alpha = 255;
550       return true;
551     }
552
553   /* rgba(r,g,b,a), ignoring a. */
554   double alpha;
555   if (sscanf (s, "rgba (%"SCNi8" , %"SCNi8" , %"SCNi8", %lf) %n",
556               &r, &g, &b, &alpha, &len) == 4
557       && !s[len])
558     {
559       color->r = r;
560       color->g = g;
561       color->b = b;
562       color->alpha = alpha <= 0 ? 0 : alpha >= 1 ? 255 : alpha * 255.0;
563       return true;
564     }
565
566   int code = lookup_color_name (s);
567   if (code >= 0)
568     {
569       color->r = code >> 16;
570       color->g = code >> 8;
571       color->b = code;
572       color->alpha = 255;
573       return true;
574     }
575
576   if (!strcmp (s, "transparent"))
577     {
578       *color = (struct cell_color) { .alpha = 0 };
579       return true;
580     }
581
582   return false;
583 }
584
585 /* Parses and returns color information from O. */
586 struct cell_color
587 parse_color (struct driver_option *o)
588 {
589   struct cell_color color = CELL_COLOR_BLACK;
590   parse_color__ (o->default_value, &color);
591   if (o->value)
592     {
593       if (!parse_color__ (o->value, &color))
594         msg (MW, _("%s: `%s' is `%s', which could not be parsed as a color"),
595              o->driver_name, o->name, o->value);
596     }
597   driver_option_destroy (o);
598   return color;
599 }
600