pspp-output: Add new --nth-commands option.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 21:28:55 +0000 (14:28 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 21:29:59 +0000 (14:29 -0700)
NEWS
doc/pspp-output.texi
src/output/spv/spv-select.c
src/output/spv/spv-select.h
tests/utilities/pspp-output.at
utilities/pspp-output.1
utilities/pspp-output.c

diff --git a/NEWS b/NEWS
index 2b5cfd4ef7698e3b56119bbd2a482dd7ec40d67e..00b3c38e8c3dbab8f07acb51e8b80038e00fe061 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,8 @@ Changes from 1.4.1 to 1.5.2:
    The new interface provides the user with a preview of the data to be imported
    and interactive methods to select the desired ranges.
 
+ * The pspp-output utility has a new --nth-commands option.
+
 Changes from 1.4.0 to 1.4.1:
 
  * Bug fixes.
index 4b9aeb203e4e944b80b524d66c55456688ca262c..29c625e0cd6cf1e0e5f15f2881dc395113130e08 100644 (file)
@@ -159,6 +159,12 @@ The @option{--labels} option matches the labels in table output (that
 is, the table titles).  Labels are affected by the output language,
 variable names and labels, split file settings, and other factors.
 
+@item --nth-commands=@var{n}@dots{}
+Include only objects from the @var{n}th command that matches
+@option{--command} (or the @var{n}th command overall if
+@option{--command} is not specified), where @var{n} is 1 for the first
+command, 2 for the second, and so on.
+
 @item --instances=@var{instance}@dots{}
 Include the specified @var{instance} of an object that matches the
 other criteria within a single command.  The @var{instance} may be a
index dacd53ec91b34d4a0375a1a45d030707f8486fae..6be9742795311a225be2b00df14d927238b1fa79 100644 (file)
 #include "gl/c-ctype.h"
 #include "gl/xalloc.h"
 
+static bool
+is_command_item (const struct spv_item *item)
+{
+  return !item->parent || !item->parent->parent;
+}
+
 static struct spv_item *
 find_command_item (struct spv_item *item)
 {
-  /* A command item itself does not have a command item. */
-  if (!item->parent || !item->parent->parent)
-    return NULL;
-
-  do
-    {
-      item = item->parent;
-    }
-  while (item->parent && item->parent->parent);
+  while (!is_command_item (item))
+    item = item->parent;
   return item;
 }
 
@@ -97,21 +96,36 @@ match_instance (const int *instances, size_t n_instances,
   return retval;
 }
 
+static bool
+match_command (size_t nth_command, size_t *commands, size_t n_commands)
+{
+  for (size_t i = 0; i < n_commands; i++)
+    if (nth_command == commands[i])
+      return true;
+  return false;
+}
+
 static void
 select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
                 unsigned long int *include)
 {
-  struct spv_item *item;
-  struct spv_item *command_item = NULL;
+  /* Counting instances within a command. */
+  struct spv_item *instance_command_item = NULL;
   int instance_within_command = 0;
   int last_instance = -1;
+
+  /* Counting commands. */
+  struct spv_item *command_command_item = NULL;
+  size_t nth_command = 0;
+
+  struct spv_item *item;
   ssize_t index = -1;
   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
     {
       index++;
 
       struct spv_item *new_command_item = find_command_item (item);
-      if (new_command_item != command_item)
+      if (new_command_item != instance_command_item)
         {
           if (last_instance >= 0)
             {
@@ -119,7 +133,7 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
               last_instance = -1;
             }
 
-          command_item = new_command_item;
+          instance_command_item = new_command_item;
           instance_within_command = 0;
         }
 
@@ -140,6 +154,18 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
                   &c->include.commands, &c->exclude.commands))
         continue;
 
+      if (c->n_commands)
+        {
+          if (new_command_item != command_command_item)
+            {
+              command_command_item = new_command_item;
+              nth_command++;
+            }
+
+          if (!match_command (nth_command, c->commands, c->n_commands))
+            continue;
+        }
+
       if (!match (spv_item_get_subtype (item),
                   &c->include.subtypes, &c->exclude.subtypes))
         continue;
@@ -157,7 +183,7 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
 
       if (c->n_instances)
         {
-          if (!command_item)
+          if (is_command_item (item))
             continue;
           instance_within_command++;
 
index b8bcd98e4e03d0743bfd3125b9e4b8cb8fcc0c72..b2a1e0e7c8a149284ceab825a2d718d0bed21586 100644 (file)
@@ -52,6 +52,11 @@ struct spv_criteria
     struct spv_criteria_match include;
     struct spv_criteria_match exclude;
 
+    /* Include objects under commands with indexes listed in COMMANDS.  Indexes
+       are 1-based.  Everything is included if N_COMMANDS is 0. */
+    size_t *commands;
+    size_t n_commands;
+
     /* Include XML and binary member names that match (except that everything
        is included by default if empty). */
     struct string_array members;
@@ -69,5 +74,4 @@ void spv_select (const struct spv_reader *,
                  const struct spv_criteria[], size_t n_criteria,
                  struct spv_item ***items, size_t *n_items);
 
-
 #endif /* output/spv/spv-select.h */
index a577867f76269c38b8b8e21859cc879fcced30cb..014a7830a406e2f90f438b17f9d0799bb3023fb7 100644 (file)
@@ -80,6 +80,20 @@ AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='^reg*'],
 ])
 AT_CLEANUP
 
+AT_SETUP([pspp-output --nth-commands])
+AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --nth-commands=2,4,6],
+  [0], [dnl
+- heading "Title" command "Title"
+    - text "Page Title" command "Title"
+- heading "Begin Data" command "Begin Data"
+- heading "Frequencies" command "Frequencies"
+    - table "Statistics" command "Frequencies"
+    - table "v0" command "Frequencies" subtype "Frequencies"
+    - table "v1" command "Frequencies" subtype "Frequencies"
+    - table "v2" command "Frequencies" subtype "Frequencies"
+])
+AT_CLEANUP
+
 AT_SETUP([pspp-output --subtypes equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='freq*'],
   [0], [dnl
index ac92efcb1cc19449a428a4449627457212696a8d..4de1e294dbe0a3cd6a9ebb6fd4e114fe97f1f3b6 100644 (file)
@@ -121,6 +121,11 @@ Subtypes are always in English and \fBdir\fR will print them.
 The \fB\-\-labels\fR option matches the labels in table output (that
 is, the table titles).  Labels are affected by the output language,
 variable names and labels, split file settings, and other factors.
+.IP "\-\-nth-commands=\fIn\fR..."
+Include only objects from the \fIn\fRth command that matches
+\fB\-\-commands\fR (or the \fIn\fRth command overall if
+\fB\-\-commands\fR is not specified), where \fIn\fR is 1 for the first
+command, 2 for the second, and so on.
 .IP "\fB\-\-instances=\fIinstance\fR..."
 Include the specified \fIinstance\fR of an object that matches the
 other criteria within a single command.  The \fIinstance\fR may be a
index bd4e895f7a0840a5dc1ae0c8979ed7c8728d0bfb..58547196bec6451e2b7a1f5b755d4ca1109887a5 100644 (file)
@@ -849,6 +849,22 @@ parse_instances (char *arg)
     }
 }
 
+static void
+parse_nth_commands (char *arg)
+{
+  struct spv_criteria *c = get_criteria ();
+  size_t allocated_commands = c->n_commands;
+
+  for (char *token = strtok (arg, ","); token; token = strtok (NULL, ","))
+    {
+      if (c->n_commands >= allocated_commands)
+        c->commands = x2nrealloc (c->commands, &allocated_commands,
+                                   sizeof *c->commands);
+
+      c->commands[c->n_commands++] = atoi (token);
+    }
+}
+
 static void
 parse_members (const char *arg)
 {
@@ -867,6 +883,7 @@ parse_options (int argc, char *argv[])
           OPT_SHOW_HIDDEN,
           OPT_SELECT,
           OPT_COMMANDS,
+          OPT_NTH_COMMANDS,
           OPT_SUBTYPES,
           OPT_LABELS,
           OPT_INSTANCES,
@@ -882,6 +899,7 @@ parse_options (int argc, char *argv[])
           { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN },
           { "select", required_argument, NULL, OPT_SELECT },
           { "commands", required_argument, NULL, OPT_COMMANDS },
+          { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS },
           { "subtypes", required_argument, NULL, OPT_SUBTYPES },
           { "labels", required_argument, NULL, OPT_LABELS },
           { "instances", required_argument, NULL, OPT_INSTANCES },
@@ -933,6 +951,10 @@ parse_options (int argc, char *argv[])
           parse_commands (optarg);
           break;
 
+        case OPT_NTH_COMMANDS:
+          parse_nth_commands (optarg);
+          break;
+
         case OPT_SUBTYPES:
           parse_subtypes (optarg);
           break;
@@ -1013,6 +1035,7 @@ Input selection options for \"dir\" and \"convert\":\n\
   --select=CLASS...   include only some kinds of objects\n\
   --select=help       print known object classes\n\
   --commands=COMMAND...  include only specified COMMANDs\n\
+  --nth-commands=N...  include only the Nth instance of selected commands\n\
   --subtypes=SUBTYPE...  include only specified SUBTYPEs of output\n\
   --labels=LABEL...   include only output objects with the given LABELs\n\
   --instances=INSTANCE...  include only the given object INSTANCEs\n\