X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=utilities%2Fpspp-output.c;fp=utilities%2Fpspp-output.c;h=009ae535f2d575960ba9f59ac6d1018a7e2eef51;hb=bcaaee5f0bd21f443c8dcb5f67114e63d43673af;hp=0000000000000000000000000000000000000000;hpb=1abd7f599dd0d773add0a98fa3b612bc15aaf422;p=pspp diff --git a/utilities/pspp-output.c b/utilities/pspp-output.c new file mode 100644 index 0000000000..009ae535f2 --- /dev/null +++ b/utilities/pspp-output.c @@ -0,0 +1,979 @@ +/* PSPP - a program for statistical analysis. + Copyright (C) 2017, 2018 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include +#include +#include +#include + +#include "data/file-handle-def.h" +#include "data/settings.h" +#include "libpspp/i18n.h" +#include "libpspp/message.h" +#include "libpspp/string-map.h" +#include "libpspp/string-set.h" +#include "output/driver.h" +#include "output/group-item.h" +#include "output/page-setup-item.h" +#include "output/pivot-table.h" +#include "output/spv/light-binary-parser.h" +#include "output/spv/spv-legacy-data.h" +#include "output/spv/spv-output.h" +#include "output/spv/spv-select.h" +#include "output/spv/spv.h" +#include "output/table-item.h" +#include "output/text-item.h" + +#include "gl/c-ctype.h" +#include "gl/error.h" +#include "gl/progname.h" +#include "gl/version-etc.h" +#include "gl/xalloc.h" + +#include +#include +#include + +#include "gettext.h" +#define _(msgid) gettext (msgid) + +/* -O key=value: Output driver options. */ +static struct string_map output_options + = STRING_MAP_INITIALIZER (output_options); + +/* --member-name: Include .zip member name in "dir" output. */ +static bool show_member_name; + +/* --show-hidden, --select, --commands, ...: Selection criteria. */ +static struct spv_criteria criteria = SPV_CRITERIA_INITIALIZER(criteria); + +/* --sort: Sort members under dump-light-table, to make comparisons easier. */ +static bool sort; + +/* --raw: Dump raw binary data in dump-light-table. */ +static bool raw; + +/* Number of warnings issued. */ +static size_t n_warnings; + +static void usage (void); +static void parse_options (int argc, char **argv); + +static void +dump_item (const struct spv_item *item) +{ + switch (spv_item_get_type (item)) + { + case SPV_ITEM_HEADING: + break; + + case SPV_ITEM_TEXT: + spv_text_submit (item); + break; + + case SPV_ITEM_TABLE: + pivot_table_submit (pivot_table_ref (spv_item_get_table (item))); + break; + + case SPV_ITEM_GRAPH: + break; + + case SPV_ITEM_MODEL: + break; + + case SPV_ITEM_OBJECT: + break; + + default: + abort (); + } +} + +static void +print_item_directory (const struct spv_item *item) +{ + for (int i = 1; i < spv_item_get_level (item); i++) + printf (" "); + + printf ("-"); + const char *label = spv_item_get_label (item); + if (label) + printf (" %s", label); + + enum spv_item_type type = spv_item_get_type (item); + printf (" %s", spv_item_type_to_string (type)); + if (type == SPV_ITEM_TABLE) + { + const struct pivot_table *table = spv_item_get_table (item); + char *title = pivot_value_to_string (table->title, + SETTINGS_VALUE_SHOW_DEFAULT, + SETTINGS_VALUE_SHOW_DEFAULT); + if (!label || strcmp (title, label)) + printf (" \"%s\"", title); + free (title); + } + + const char *command_id = spv_item_get_command_id (item); + if (command_id) + printf (" \"%s\"", command_id); + + if (!spv_item_is_visible (item)) + printf (" (hidden)"); + if (show_member_name && (item->xml_member || item->bin_member)) + { + if (item->xml_member && item->bin_member) + printf (" in %s and %s", item->xml_member, item->bin_member); + else if (item->xml_member) + printf (" in %s", item->xml_member); + else if (item->bin_member) + printf (" in %s", item->bin_member); + } + putchar ('\n'); +} + +static void +run_detect (int argc UNUSED, char **argv) +{ + char *err = spv_detect (argv[1]); + if (err) + error (1, 0, "%s", err); +} + +static void +run_directory (int argc UNUSED, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + print_item_directory (items[i]); + free (items); + + spv_close (spv); +} + +struct item_path + { + const struct spv_item **nodes; + size_t n; + +#define N_STUB 10 + const struct spv_item *stub[N_STUB]; + }; + +static void +swap_nodes (const struct spv_item **a, const struct spv_item **b) +{ + const struct spv_item *tmp = *a; + *a = *b; + *b = tmp; +} + +static void +get_path (const struct spv_item *item, struct item_path *path) +{ + size_t allocated = 10; + path->nodes = path->stub; + path->n = 0; + + while (item) + { + if (path->n >= allocated) + { + if (path->nodes == path->stub) + path->nodes = xmemdup (path->stub, sizeof path->stub); + path->nodes = x2nrealloc (path->nodes, &allocated, + sizeof *path->nodes); + } + path->nodes[path->n++] = item; + item = item->parent; + } + + for (size_t i = 0; i < path->n / 2; i++) + swap_nodes (&path->nodes[i], &path->nodes[path->n - i - 1]); +} + +static void +free_path (struct item_path *path) +{ + if (path && path->nodes != path->stub) + free (path->nodes); +} + +static void +dump_heading_transition (const struct spv_item *old, + const struct spv_item *new) +{ + if (old == new) + return; + + struct item_path old_path, new_path; + get_path (old, &old_path); + get_path (new, &new_path); + + size_t common = 0; + for (; common < old_path.n && common < new_path.n; common++) + if (old_path.nodes[common] != new_path.nodes[common]) + break; + + for (size_t i = common; i < old_path.n; i++) + group_close_item_submit (group_close_item_create ()); + for (size_t i = common; i < new_path.n; i++) + group_open_item_submit (group_open_item_create ( + new_path.nodes[i]->command_id)); + + free_path (&old_path); + free_path (&new_path); +} + +static void +run_convert (int argc UNUSED, char **argv) +{ + output_engine_push (); + output_set_filename (argv[1]); + string_map_insert (&output_options, "output-file", argv[2]); + struct output_driver *driver = output_driver_create (&output_options); + if (!driver) + exit (EXIT_FAILURE); + output_driver_register (driver); + + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + const struct page_setup *ps = spv_get_page_setup (spv); + if (ps) + page_setup_item_submit (page_setup_item_create (ps)); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + struct spv_item *prev_heading = spv_get_root (spv); + for (size_t i = 0; i < n_items; i++) + { + struct spv_item *heading + = items[i]->type == SPV_ITEM_HEADING ? items[i] : items[i]->parent; + dump_heading_transition (prev_heading, heading); + dump_item (items[i]); + prev_heading = heading; + } + dump_heading_transition (prev_heading, spv_get_root (spv)); + free (items); + + spv_close (spv); + + output_engine_pop (); + fh_done (); +} + +static void +run_dump (int argc UNUSED, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + if (items[i]->type == SPV_ITEM_TABLE) + { + pivot_table_dump (spv_item_get_table (items[i]), 0); + putchar ('\n'); + } + free (items); + + spv_close (spv); +} + +static int +compare_borders (const void *a_, const void *b_) +{ + const struct spvlb_border *const *ap = a_; + const struct spvlb_border *const *bp = b_; + uint32_t a = (*ap)->border_type; + uint32_t b = (*bp)->border_type; + + return a < b ? -1 : a > b; +} + +static int +compare_cells (const void *a_, const void *b_) +{ + const struct spvlb_cell *const *ap = a_; + const struct spvlb_cell *const *bp = b_; + uint64_t a = (*ap)->index; + uint64_t b = (*bp)->index; + + return a < b ? -1 : a > b; +} + +static void +run_dump_light_table (int argc UNUSED, char **argv) +{ + if (raw && isatty (STDOUT_FILENO)) + error (1, 0, "not writing binary data to tty"); + + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + { + if (!spv_item_is_light_table (items[i])) + continue; + + char *error; + if (raw) + { + void *data; + size_t size; + error = spv_item_get_raw_light_table (items[i], &data, &size); + if (!error) + { + fwrite (data, size, 1, stdout); + free (data); + } + } + else + { + struct spvlb_table *table; + error = spv_item_get_light_table (items[i], &table); + if (!error) + { + if (sort) + { + qsort (table->borders->borders, table->borders->n_borders, + sizeof *table->borders->borders, compare_borders); + qsort (table->cells->cells, table->cells->n_cells, + sizeof *table->cells->cells, compare_cells); + } + spvlb_print_table (items[i]->bin_member, 0, table); + spvlb_free_table (table); + } + } + if (error) + { + msg (ME, "%s", error); + free (error); + } + } + + free (items); + + spv_close (spv); +} + +static void +run_dump_legacy_data (int argc UNUSED, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + if (spv_item_is_legacy_table (items[i])) + { + struct spv_data data; + char *error; + if (raw) + { + void *data; + size_t size; + error = spv_item_get_raw_legacy_data (items[i], &data, &size); + if (!error) + { + fwrite (data, size, 1, stdout); + free (data); + } + } + else + { + error = spv_item_get_legacy_data (items[i], &data); + if (!error) + { + printf ("%s:\n", items[i]->bin_member); + spv_data_dump (&data, stdout); + spv_data_uninit (&data); + printf ("\n"); + } + } + + if (error) + { + msg (ME, "%s", error); + free (error); + } + } + free (items); + + spv_close (spv); +} + +/* This is really bogus. + + XPath doesn't have any notion of a default XML namespace, but all of the + elements in the documents we're interested in have a namespace. Thus, we'd + need to require the XPath expressions to have a namespace on every single + element: vis:sourceVariable, vis:graph, and so on. That's a pain. So, + instead, we remove the default namespace from everyplace it occurs. XPath + does support the null namespace, so this allows sourceVariable, graph, + etc. to work. + + See http://plasmasturm.org/log/259/ and + https://mail.gnome.org/archives/xml/2003-April/msg00144.html for more + information.*/ +static void +remove_default_xml_namespace (xmlNode *node) +{ + if (node->ns && !node->ns->prefix) + node->ns = NULL; + + for (xmlNode *child = node->children; child; child = child->next) + remove_default_xml_namespace (child); +} + +static void +register_ns (xmlXPathContext *ctx, const char *prefix, const char *uri) +{ + xmlXPathRegisterNs (ctx, CHAR_CAST (xmlChar *, prefix), + CHAR_CAST (xmlChar *, uri)); +} + +static xmlXPathContext * +create_xpath_context (xmlDoc *doc) +{ + xmlXPathContext *ctx = xmlXPathNewContext (doc); + register_ns (ctx, "vgr", "http://xml.spss.com/spss/viewer/viewer-graph"); + register_ns (ctx, "vizml", "http://xml.spss.com/visualization"); + register_ns (ctx, "vmd", "http://xml.spss.com/spss/viewer/viewer-model"); + register_ns (ctx, "vps", "http://xml.spss.com/spss/viewer/viewer-pagesetup"); + register_ns (ctx, "vst", "http://xml.spss.com/spss/viewer/viewer-style"); + register_ns (ctx, "vtb", "http://xml.spss.com/spss/viewer/viewer-table"); + register_ns (ctx, "vtl", "http://xml.spss.com/spss/viewer/table-looks"); + register_ns (ctx, "vtt", "http://xml.spss.com/spss/viewer/viewer-treemodel"); + register_ns (ctx, "vtx", "http://xml.spss.com/spss/viewer/viewer-text"); + register_ns (ctx, "xsi", "http://www.w3.org/2001/XMLSchema-instance"); + return ctx; +} + +static void +dump_xml (int argc, char **argv, const char *member_name, + char *error_s, xmlDoc *doc) +{ + if (!error_s) + { + if (argc == 2) + { + printf ("\n", member_name); + xmlElemDump (stdout, NULL, xmlDocGetRootElement (doc)); + putchar ('\n'); + } + else + { + bool any_results = false; + + remove_default_xml_namespace (xmlDocGetRootElement (doc)); + for (int i = 2; i < argc; i++) + { + xmlXPathContext *xpath_ctx = create_xpath_context (doc); + xmlXPathSetContextNode (xmlDocGetRootElement (doc), + xpath_ctx); + xmlXPathObject *xpath_obj = xmlXPathEvalExpression( + CHAR_CAST (xmlChar *, argv[i]), xpath_ctx); + if (!xpath_obj) + error (1, 0, _("%s: invalid XPath expression"), argv[i]); + + const xmlNodeSet *nodes = xpath_obj->nodesetval; + if (nodes && nodes->nodeNr > 0) + { + if (!any_results) + { + printf ("\n", member_name); + any_results = true; + } + for (size_t j = 0; j < nodes->nodeNr; j++) + { + xmlElemDump (stdout, doc, nodes->nodeTab[j]); + putchar ('\n'); + } + } + + xmlXPathFreeObject (xpath_obj); + xmlXPathFreeContext (xpath_ctx); + } + if (any_results) + putchar ('\n');; + } + xmlFreeDoc (doc); + } + else + { + printf ("\n", member_name); + msg (ME, "%s", error_s); + free (error_s); + } +} + +static void +run_dump_legacy_table (int argc, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + if (spv_item_is_legacy_table (items[i])) + { + xmlDoc *doc; + char *error_s = spv_item_get_legacy_table (items[i], &doc); + dump_xml (argc, argv, items[i]->xml_member, error_s, doc); + } + free (items); + + spv_close (spv); +} + +static void +run_dump_structure (int argc, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + const char *last_structure_member = NULL; + for (size_t i = 0; i < n_items; i++) + if (!last_structure_member || strcmp (items[i]->structure_member, + last_structure_member)) + { + last_structure_member = items[i]->structure_member; + + xmlDoc *doc; + char *error_s = spv_item_get_structure (items[i], &doc); + dump_xml (argc, argv, items[i]->structure_member, error_s, doc); + } + free (items); + + spv_close (spv); +} + +static void +run_is_legacy (int argc UNUSED, char **argv) +{ + struct spv_reader *spv; + char *err = spv_open (argv[1], &spv); + if (err) + error (1, 0, "%s", err); + + bool is_legacy = false; + + struct spv_item **items; + size_t n_items; + spv_select (spv, &criteria, &items, &n_items); + for (size_t i = 0; i < n_items; i++) + if (spv_item_is_legacy_table (items[i])) + { + is_legacy = true; + break; + } + free (items); + + spv_close (spv); + + exit (is_legacy ? EXIT_SUCCESS : EXIT_FAILURE); +} + +struct command + { + const char *name; + int min_args, max_args; + void (*run) (int argc, char **argv); + }; + +static const struct command commands[] = + { + { "detect", 1, 1, run_detect }, + { "dir", 1, 1, run_directory }, + { "convert", 2, 2, run_convert }, + + /* Undocumented commands. */ + { "dump", 1, 1, run_dump }, + { "dump-light-table", 1, 1, run_dump_light_table }, + { "dump-legacy-data", 1, 1, run_dump_legacy_data }, + { "dump-legacy-table", 1, INT_MAX, run_dump_legacy_table }, + { "dump-structure", 1, INT_MAX, run_dump_structure }, + { "is-legacy", 1, 1, run_is_legacy }, + }; +static const int n_commands = sizeof commands / sizeof *commands; + +static const struct command * +find_command (const char *name) +{ + for (size_t i = 0; i < n_commands; i++) + { + const struct command *c = &commands[i]; + if (!strcmp (name, c->name)) + return c; + } + return NULL; +} + +static void +emit_msg (const struct msg *m, void *aux UNUSED) +{ + if (m->severity == MSG_S_ERROR || m->severity == MSG_S_WARNING) + n_warnings++; + + char *s = msg_to_string (m); + fprintf (stderr, "%s\n", s); + free (s); +} + +int +main (int argc, char **argv) +{ + set_program_name (argv[0]); + msg_set_handler (emit_msg, NULL); + settings_init (); + i18n_init (); + + parse_options (argc, argv); + + argc -= optind; + argv += optind; + + if (argc < 1) + error (1, 0, _("missing command name (use --help for help)")); + + const struct command *c = find_command (argv[0]); + if (!c) + error (1, 0, _("unknown command \"%s\" (use --help for help)"), argv[0]); + + int n_args = argc - 1; + if (n_args < c->min_args || n_args > c->max_args) + { + if (c->min_args == c->max_args) + error (1, 0, _("\"%s\" command takes exactly %d argument%s"), + c->name, c->min_args, c->min_args ? "s" : ""); + else if (c->max_args == INT_MAX) + error (1, 0, _("\"%s\" command requires at least %d argument%s"), + c->name, c->min_args, c->min_args ? "s" : ""); + else + error (1, 0, _("\"%s\" command requires between %d and %d arguments"), + c->name, c->min_args, c->max_args); + } + + c->run (argc, argv); + + i18n_done (); + + return n_warnings ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void +parse_select (char *arg, bool invert) +{ + unsigned classes = 0; + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + { + if (!strcmp (arg, "all")) + classes = SPV_ALL_CLASSES; + else if (!strcmp (arg, "help")) + { + puts (_("The following object classes are supported:")); + for (int class = 0; class < SPV_N_CLASSES; class++) + printf ("- %s\n", spv_item_class_to_string (class)); + exit (0); + } + else + { + int class = spv_item_class_from_string (token); + if (class == SPV_N_CLASSES) + error (1, 0, _("%s: unknown object class (use --select=help " + "for help"), arg); + classes |= 1u << class; + } + } + + criteria.classes = invert ? classes ^ SPV_ALL_CLASSES : classes; +} + +static void +parse_commands (char *arg) +{ + size_t allocated_commands = criteria.n_commands; + + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + { + char *save_ptr = NULL; + char *name = strtok_r (token, "()", &save_ptr); + char *number = strtok_r (NULL, "()", &save_ptr); + + if (criteria.n_commands >= allocated_commands) + criteria.commands = x2nrealloc (criteria.commands, &allocated_commands, + sizeof *criteria.commands); + + struct spv_command_match *cm = &criteria.commands[criteria.n_commands++]; + if (!strcmp (name, "last")) + { + cm->name = NULL; + cm->instance = -1; + } + else if (c_isdigit (name[0])) + { + cm->name = NULL; + cm->instance = atoi (name); + } + else + { + cm->name = name; + cm->instance = (!number ? 0 + : !strcmp (number, "last") ? -1 + : atoi (number)); + } + } +} + +static void +parse_subtypes (char *arg) +{ + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + string_set_insert (&criteria.subtypes, token); +} + +static void +parse_labels (char *arg, enum spv_label_match_op op) +{ + size_t allocated_labels = criteria.n_labels; + + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + { + if (criteria.n_labels >= allocated_labels) + criteria.labels = x2nrealloc (criteria.labels, &allocated_labels, + sizeof *criteria.labels); + + struct spv_label_match *lm = &criteria.labels[criteria.n_labels++]; + lm->op = op; + lm->arg = arg; + } +} + +static void +parse_instances (char *arg) +{ + size_t allocated_instances = criteria.n_instances; + + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + { + if (criteria.n_instances >= allocated_instances) + criteria.instances = x2nrealloc (criteria.instances, + &allocated_instances, + sizeof *criteria.instances); + + criteria.instances[criteria.n_instances++] + = (!strcmp (token, "last") ? -1 : atoi (token)); + } +} + +static void +parse_options (int argc, char *argv[]) +{ + for (;;) + { + enum + { + OPT_MEMBER_NAME = UCHAR_MAX + 1, + OPT_SHOW_HIDDEN, + OPT_SELECT, + OPT_SELECT_EXCEPT, + OPT_COMMANDS, + OPT_SUBTYPES, + OPT_LABELS, + OPT_LABELS_CONTAINING, + OPT_LABELS_STARTING, + OPT_LABELS_ENDING, + OPT_INSTANCES, + OPT_ERRORS, + OPT_SORT, + OPT_RAW, + }; + static const struct option long_options[] = + { + { "member-name", no_argument, NULL, OPT_MEMBER_NAME }, + { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN }, + { "select", required_argument, NULL, OPT_SELECT }, + { "select-except", required_argument, NULL, OPT_SELECT_EXCEPT }, + { "commands", required_argument, NULL, OPT_COMMANDS }, + { "subtypes", required_argument, NULL, OPT_SUBTYPES }, + { "labels", required_argument, NULL, OPT_LABELS }, + { "labels-containing", required_argument, NULL, + OPT_LABELS_CONTAINING }, + { "labels-starting", required_argument, NULL, OPT_LABELS_STARTING }, + { "labels-ending", required_argument, NULL, OPT_LABELS_ENDING }, + { "instances", required_argument, NULL, OPT_INSTANCES }, + { "errors", no_argument, NULL, OPT_ERRORS }, + { "sort", no_argument, NULL, OPT_SORT }, + { "raw", no_argument, NULL, OPT_RAW }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 }, + }; + + int c; + + c = getopt_long (argc, argv, "O:hv", long_options, NULL); + if (c == -1) + break; + + switch (c) + { + case 'O': + output_driver_parse_option (optarg, &output_options); + break; + + case OPT_MEMBER_NAME: + show_member_name = true; + break; + + case OPT_SHOW_HIDDEN: + criteria.include_hidden = true; + break; + + case OPT_SELECT: + parse_select (optarg, false); + break; + + case OPT_SELECT_EXCEPT: + parse_select (optarg, true); + break; + + case OPT_COMMANDS: + parse_commands (optarg); + break; + + case OPT_SUBTYPES: + parse_subtypes (optarg); + break; + + case OPT_LABELS: + parse_labels (optarg, SPV_LABEL_MATCH_EQUALS); + break; + + case OPT_LABELS_CONTAINING: + parse_labels (optarg, SPV_LABEL_MATCH_CONTAINS); + break; + + case OPT_LABELS_STARTING: + parse_labels (optarg, SPV_LABEL_MATCH_STARTS); + break; + + case OPT_LABELS_ENDING: + parse_labels (optarg, SPV_LABEL_MATCH_ENDS); + break; + + case OPT_INSTANCES: + parse_instances (optarg); + break; + + case OPT_ERRORS: + criteria.error = true; + break; + + case OPT_SORT: + sort = true; + break; + + case OPT_RAW: + raw = true; + break; + + case 'v': + version_etc (stdout, "pspp-output", PACKAGE_NAME, PACKAGE_VERSION, + "Ben Pfaff", "John Darrington", NULL_SENTINEL); + exit (EXIT_SUCCESS); + + case 'h': + usage (); + exit (EXIT_SUCCESS); + + default: + exit (EXIT_FAILURE); + } + } +} + +static void +usage (void) +{ + struct string s = DS_EMPTY_INITIALIZER; + struct string_set formats = STRING_SET_INITIALIZER(formats); + output_get_supported_formats (&formats); + const char *format; + const struct string_set_node *node; + STRING_SET_FOR_EACH (format, node, &formats) + { + if (!ds_is_empty (&s)) + ds_put_byte (&s, ' '); + ds_put_cstr (&s, format); + } + string_set_destroy (&formats); + + printf ("\ +%s, a utility for working with SPSS output (.spv) files.\n\ +Usage: %s [OPTION]... COMMAND ARG...\n\ +\n\ +The following commands are available:\n\ + detect INPUT Detect whether INPUT is an SPV file.\n\ + dir INPUT List tables and other items in INPUT.\n\ + convert INPUT OUTPUT Convert .spv INPUT to OUTPUT.\n\ +\n\ +The desired format of OUTPUT is by default inferred from its extension:\n\ +%s\n\ +\n\ +Options:\n\ + -O format=FORMAT override format for output\n\ + -O OPTION=VALUE set output option\n\ + --help display this help and exit\n\ + --version output version information and exit\n", + program_name, program_name, ds_cstr (&s)); + ds_destroy (&s); +}