Index: /trunk/Ohana/src/opihi/cmd.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 10996)
+++ /trunk/Ohana/src/opihi/cmd.data/Makefile	(revision 10997)
@@ -14,4 +14,6 @@
 $(SDIR)/applyfit.$(ARCH).o	\
 $(SDIR)/box.$(ARCH).o		\
+$(SDIR)/book.$(ARCH).o		\
+$(SDIR)/book_commands.$(ARCH).o	\
 $(SDIR)/center.$(ARCH).o	\
 $(SDIR)/clear.$(ARCH).o		\
@@ -71,4 +73,5 @@
 $(SDIR)/queuedrop.$(ARCH).o	\
 $(SDIR)/queuelist.$(ARCH).o	\
+$(SDIR)/queueload.$(ARCH).o	\
 $(SDIR)/queuesize.$(ARCH).o	\
 $(SDIR)/queuepush.$(ARCH).o	\
Index: /trunk/Ohana/src/opihi/cmd.data/book.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/book.c	(revision 10997)
+++ /trunk/Ohana/src/opihi/cmd.data/book.c	(revision 10997)
@@ -0,0 +1,77 @@
+# include "data.h"
+
+int book_list (int argc, char **argv);
+int book_create (int argc, char **argv);
+int book_delete (int argc, char **argv);
+int book_getbook (int argc, char **argv);
+int book_listbook (int argc, char **argv);
+int book_npages (int argc, char **argv);
+int book_newpage (int argc, char **argv);
+int book_getpage (int argc, char **argv);
+int book_delpage (int argc, char **argv);
+int book_listpage (int argc, char **argv);
+int book_setword (int argc, char **argv);
+int book_getword (int argc, char **argv);
+
+static Command book_commands[] = {
+  {"list",     book_list,     "list books"},
+  {"create",   book_create,   "create a book"},
+  {"delete",   book_delete,   "delete a book"},
+  {"getbook",  book_getbook,  "get book name by location"},
+  {"listbook", book_listbook, "list pages in a book"},
+  {"npages",   book_npages,   "return number of pages in a book"},
+  {"newpage",  book_newpage,  "create a new page in a book"},
+  {"getpage",  book_getpage,  "get page name by location"},
+  {"delpage",  book_delpage,  "delete a page in a book"},
+  {"listpage", book_listpage, "list a page in a book"},
+  {"setword",  book_setword,  "set the value of a word in a page"},
+  {"getword",  book_getword,  "set the value of a word from a page"},
+};
+
+int book_command (int argc, char **argv) {
+
+  int i, N, status;
+
+  if (argc < 2) {
+    gprint (GP_ERR, "USAGE: book (command)\n");
+    gprint (GP_ERR, "    book list             			: list books\n");
+    gprint (GP_ERR, "    book create   (book)   		: create book (name)\n");
+    gprint (GP_ERR, "    book delete   (book)                   : delete book (name)\n");
+    gprint (GP_ERR, "    book getbook  (where)  		: get book name\n");
+    gprint (GP_ERR, "    book listbook (book)    		: list a book\n");
+    gprint (GP_ERR, "    book npages   (book)   		: return number of pages in a book\n");
+    gprint (GP_ERR, "    book newpage  (book) (page)  		: create a new page in a book\n");
+    gprint (GP_ERR, "    book getpage  (book) (where)  		: get page name in a book\n");
+    gprint (GP_ERR, "    book delpage  (book) (page)  		: delete a page in a book\n");
+    gprint (GP_ERR, "    book listpage (book) (page)  		: list a page in a book\n");
+    gprint (GP_ERR, "    book setword  (book) (page) (word) (value)  : set the value of a word in a page\n");
+    gprint (GP_ERR, "    book getword  (book) (page) (word) [-var var] : set the value of a word from a page\n");
+    return (FALSE);
+  }
+
+  N = sizeof (book_commands) / sizeof (Command);
+
+  /* find the book sub-command which matches */
+  for (i = 0; i < N; i++) {
+    if (!strcmp (book_commands[i].name, argv[1])) {
+      status = (*book_commands[i].func) (argc - 1, argv + 1);
+      return (status);
+    }
+  }
+
+  gprint (GP_ERR, "unknown book command %s\n", argv[1]);
+  return (FALSE);
+}
+
+/* book is called with the command "book".  
+   the command line word "book" is meant to be followed the one of several 
+   possible options:
+   
+   book create
+   book delete
+   book list
+   book edit
+   book read
+   book write
+
+*/
Index: /trunk/Ohana/src/opihi/cmd.data/book_commands.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/book_commands.c	(revision 10997)
+++ /trunk/Ohana/src/opihi/cmd.data/book_commands.c	(revision 10997)
@@ -0,0 +1,330 @@
+# include "data.h"
+
+int book_list (int argc, char **argv) {
+    ListBooks();
+}
+
+int book_create (int argc, char **argv) {
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: book create (book)\n");
+    return FALSE;
+  }
+
+  CreateBook (argv[1]);
+  return TRUE;
+}
+
+int book_delete (int argc, char **argv) {
+
+  int status;
+  Book *book;
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: book delete (book)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+
+  status = DeleteBook (book);
+  if (!status) abort ();
+  return TRUE;
+}
+
+int book_listbook (int argc, char **argv) {
+
+  int status;
+  Book *book;
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: book listbook (book)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+
+  ListPages (book);
+  return TRUE;
+}
+
+int book_npages (int argc, char **argv) {
+
+  int N, status;
+  Book *book;
+  char *varName;
+
+  varName = NULL;
+  if ((N = get_argument (argc, argv, "-var"))) {
+    remove_argument (N, &argc, argv);
+    varName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: book npages (book)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+
+  if (varName) {
+    set_int_variable (varName, book[0].Npages);
+  } else {
+    gprint (GP_ERR, "%d pages\n", book[0].Npages);
+  }
+  return TRUE;
+}
+
+int book_getbook (int argc, char **argv) {
+
+  int where, N;
+  char *varName;
+  Book *book;
+
+  varName = NULL;
+  if ((N = get_argument (argc, argv, "-var"))) {
+    remove_argument (N, &argc, argv);
+    varName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 2) {
+    gprint (GP_ERR, "USAGE: book getbook (where) [-var var]\n");
+    return FALSE;
+  }
+
+  where = atoi (argv[1]);
+  book = GetBook (where);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+
+  if (varName) {
+    set_str_variable (varName, book[0].name);
+  } else {
+    gprint (GP_ERR, "%s\n", book[0].name);
+  }
+  return TRUE;
+}
+
+int book_newpage (int argc, char **argv) {
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: book newpage (book) (page)\n");
+    return FALSE;
+  }
+
+  Book *book;
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+  
+  CreatePage (book, argv[2]);
+  return TRUE;
+}
+
+int book_delpage (int argc, char **argv) {
+
+  int i, N;
+  Page *page;
+  Book *book;
+  char *Key, *Value, *value;
+
+  Key = NULL;
+  Value = NULL;
+  if ((N = get_argument (argc, argv, "-key"))) {
+    remove_argument (N, &argc, argv);
+    Key = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+    Value = strcreate (argv[N]);
+  }
+
+  if ((argc != 3) && (argc != 2)) {
+    gprint (GP_ERR, "USAGE: book delpage (book) (page)\n");
+    gprint (GP_ERR, "USAGE: book delpage (book) -key name value\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+  
+  if (Key) {
+    /* delete by matching key */
+    for (i = 0; i < book[0].Npages; i++) {
+      value = BookGetWord (book[0].pages[i], Key);
+      if (value == NULL) continue;
+      if (!strcmp(value, Value)) {
+	DeletePage (book, book[0].pages[i]);
+	i--; /* if we delete this page, don't advance the counter */
+      }
+    }
+    return TRUE;
+  }
+
+  page = FindPage (book, argv[2]);
+  if (page == NULL) {
+    gprint (GP_ERR, "page %s in book %s not found\n", argv[2], argv[1]);
+    return FALSE;
+  }
+
+  DeletePage (book, page);
+  return TRUE;
+}
+
+int book_listpage (int argc, char **argv) {
+
+  Page *page;
+  Book *book;
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: book listpage (book) (page)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+  
+  page = FindPage (book, argv[2]);
+  if (page == NULL) {
+    gprint (GP_ERR, "page %s in book %s not found\n", argv[2], argv[1]);
+    return FALSE;
+  }
+
+  ListWords (page);
+  return TRUE;
+}
+
+int book_getpage (int argc, char **argv) {
+
+  int where, N;
+  char *varName;
+  Book *book;
+  Page *page;
+
+  varName = NULL;
+  if ((N = get_argument (argc, argv, "-var"))) {
+    remove_argument (N, &argc, argv);
+    varName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: book getpage (book) (where) [-var var]\n");
+    return FALSE;
+  }
+
+  where = atoi (argv[2]);
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+
+  page = GetPage (book, where);
+  if (page == NULL) {
+    gprint (GP_ERR, "page %s in book %s not found\n", argv[2], argv[1]);
+    return FALSE;
+  }
+
+  if (varName) {
+    set_str_variable (varName, page[0].name);
+  } else {
+    gprint (GP_ERR, "%s\n", page[0].name);
+  }
+  return TRUE;
+}
+
+int book_setword (int argc, char **argv) {
+
+  Page *page;
+  Book *book;
+
+  if (argc != 5) {
+    gprint (GP_ERR, "USAGE: book setword (book) (page) (word) (value)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+  
+  page = FindPage (book, argv[2]);
+  if (page == NULL) {
+    gprint (GP_ERR, "page %s in book %s not found\n", argv[2], argv[1]);
+    return FALSE;
+  }
+
+  BookSetWord (page, argv[3], argv[4]);
+  return TRUE;
+}
+
+int book_getword (int argc, char **argv) {
+
+  int N;
+  Page *page;
+  Book *book;
+  char *value, *varName;
+
+  varName = NULL;
+  if ((N = get_argument (argc, argv, "-var"))) {
+    remove_argument (N, &argc, argv);
+    varName = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 4) {
+    gprint (GP_ERR, "USAGE: book newword (book) (page) (word)\n");
+    return FALSE;
+  }
+
+  book = FindBook (argv[1]);
+  if (book == NULL) {
+    gprint (GP_ERR, "book %s not found\n", argv[1]);
+    return FALSE;
+  }
+  
+  page = FindPage (book, argv[2]);
+  if (page == NULL) {
+    gprint (GP_ERR, "page %s in book %s not found\n", argv[2], argv[1]);
+    return FALSE;
+  }
+
+  value = BookGetWord (page, argv[3]);
+  if (value == NULL) {
+    gprint (GP_ERR, "value %s on page %s in book %s not found\n", argv[3], argv[2], argv[1]);
+    return FALSE;
+  }
+
+  if (varName) {
+    set_str_variable (varName, value);
+  } else {
+    gprint (GP_ERR, "%s\n", value);
+  }
+    
+  return TRUE;
+}
Index: /trunk/Ohana/src/opihi/cmd.data/init.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 10996)
+++ /trunk/Ohana/src/opihi/cmd.data/init.c	(revision 10997)
@@ -5,4 +5,5 @@
 int applyfit2d       PROTO((int, char **));
 int box              PROTO((int, char **));
+int book_command     PROTO((int, char **));
 int center           PROTO((int, char **));
 int circstats        PROTO((int, char **));
@@ -63,4 +64,5 @@
 int ps               PROTO((int, char **));
 int queuelist        PROTO((int, char **));
+int queueload        PROTO((int, char **));
 int queueinit        PROTO((int, char **));
 int queuedelete      PROTO((int, char **));
@@ -117,4 +119,5 @@
   {"applyfit2d",   applyfit2d,	     "apply 2-d fit to new vector"},
   {"box",     	   box,		     "draw a box on the plot"},
+  {"book",    	   book_command,     "commands to manipulate book/page/word data"},
   {"center",       center,	     "center image on coords"},
   {"circstats",    circstats,	     "circular statistics"},
@@ -183,4 +186,5 @@
   {"queuedelete",  queuedelete,	     "delete a queue"},
   {"queuelist",	   queuelist,	     "list defined queues"},
+  {"queueload",	   queueload,	     "load queue from command"},
   {"queuesize",    queuesize,	     "show queue size"},
   {"rd",           rd,		     "load fits image"},
Index: /trunk/Ohana/src/opihi/cmd.data/queueload.c
===================================================================
--- /trunk/Ohana/src/opihi/cmd.data/queueload.c	(revision 10997)
+++ /trunk/Ohana/src/opihi/cmd.data/queueload.c	(revision 10997)
@@ -0,0 +1,69 @@
+# include "data.h"
+
+int queueload (int argc, char **argv) {
+  
+  char *A, *B, *val;
+  int i, done, status;
+  int N, Nread, Nbytes, NBYTES;
+  FILE *f;
+  Queue *queue;
+
+  if (argc != 4) goto usage;
+  if (strcmp(argv[2], "-x")) goto usage;
+  
+  /* will create a queue if none exists */
+  queue = CreateQueue (argv[1]);
+
+  /* val will hold the result of the command */
+  NBYTES = 1024;
+  ALLOCATE (val, char, NBYTES);
+    
+  /* loop until command produces no more output,  REALLOCATE as needed. */
+  f = popen (argv[3], "r");
+  Nbytes = 0;
+  Nread = 1;
+  while (Nread > 0) {
+    Nread = fread (&val[Nbytes], 1, 1023, f);
+    if (Nread < 0) { 
+      gprint (GP_ERR, "error reading from command\n");
+    }
+    if (Nread > 0) {
+      Nbytes += Nread;
+      NBYTES = 1024 + Nbytes;
+      REALLOCATE (val, char, NBYTES);
+    }
+  }
+  val[Nbytes] = 0;
+  status = pclose (f);
+    
+  if (status) {
+    gprint (GP_ERR, "warning: exit status of command %d\n", status);
+  }
+      
+  A = B = val;
+  for (i = 0; B != (char *) NULL;) {
+    while (isspace (*A) && (*A != 0)) A++;
+    B = strchr (A, '\n');
+    if (B != (char *) NULL) { *B = 0; }
+    if (*A != 0) {
+      PushQueue (queue, A);
+      A = B + 1;
+      i++;
+    }
+  }      
+  free (val);
+    
+  return (TRUE);
+
+usage:
+  gprint (GP_ERR, "USAGE: queueload (queue) -x (command)\n");
+  return (FALSE);
+}
+
+
+/* 
+ * -key only needed for replace or unique : give an error otherwise
+ * -uniq searched for a match and does NOT replace if matched
+ * -replace searches for a match and replaces if matched
+ * should trigger an error if -uniq and -replace...
+ */
Index: /trunk/Ohana/src/opihi/include/data.h
===================================================================
--- /trunk/Ohana/src/opihi/include/data.h	(revision 10996)
+++ /trunk/Ohana/src/opihi/include/data.h	(revision 10997)
@@ -16,5 +16,44 @@
 } Queue;
 
+typedef struct {
+  char *name;
+  int NWORDS;
+  int Nwords;
+  char **words;
+  char **value;
+} Page;
+
+typedef struct {
+  char *name;
+  int NPAGES;
+  int Npages;
+  Page **pages;
+  int *index;
+  char **pageIDs;
+} Book;
+
 void InitData ();
+
+/* in book.c */
+void InitBooks ();
+void InitBook (Book *book, char *name);
+void FreeBook (Book *book);
+Book *FindBook (char *name);
+Book *GetBook (int where);
+Book *CreateBook (char *name);
+int DeleteBook (Book *book);
+void ListBooks ();
+
+/* in page.c */
+void InitPage (Page *page, char *name);
+void FreePage (Page *page);
+Page *FindPage (Book *book, char *name);
+Page *GetPage (Book *book, int where);
+Page *CreatePage (Book *book, char *name);
+int DeletePage (Book *book, Page *page);
+void ListPages (Book *book);
+void ListWords (Page *page);
+int BookSetWord (Page *page, char *word, char *value);
+char *BookGetWord (Page *page, char *word);
 
 /* in queues.c */
Index: /trunk/Ohana/src/opihi/include/macro.h
===================================================================
--- /trunk/Ohana/src/opihi/include/macro.h	(revision 10996)
+++ /trunk/Ohana/src/opihi/include/macro.h	(revision 10997)
@@ -1,10 +1,0 @@
-static Command in_macro[] = {
-  {"create", macro_create, "create a macro *"},
-  {"delete", macro_delete, "delete a macro *"},
-  {"list",   macro_list_f, "list a macro *"},
-  {"edit",   macro_edit,   "edit a macro <not working yet!> *"},
-  {"read",   macro_read,   "read a macro from a file <not working yet!> *"},
-  {"write",  macro_write,  "write a macro to a file <not working yet!> *"}
-};
-
-/*** need to encapsulate the macro command lookup ***/
Index: /trunk/Ohana/src/opihi/lib.data/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/Makefile	(revision 10996)
+++ /trunk/Ohana/src/opihi/lib.data/Makefile	(revision 10997)
@@ -9,4 +9,6 @@
 # general numerical functions (libdata) #####################
 srcs = \
+$(SDIR)/book.$(ARCH).o                  \
+$(SDIR)/page.$(ARCH).o                  \
 $(SDIR)/sort.$(ARCH).o                  \
 $(SDIR)/fft.$(ARCH).o			\
Index: /trunk/Ohana/src/opihi/lib.data/book.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/book.c	(revision 10996)
+++ /trunk/Ohana/src/opihi/lib.data/book.c	(revision 10997)
@@ -1,45 +1,7 @@
 # include "data.h"
-
-typedef struct {
-  int Nvalues;
-  char *id;
-  char **word;
-  char **value;
-} Page;
-
-typedef struct {
-  int Npages;
-  char *name;
-  int *index;
-  char **sorted;
-  Page *pages;
-} Book;
-
-/* commands:
-   book list
-   book create
-   book newpage (book.page)
-   book delpage (book.page)
-   book newword (book.page.word) (value)
-*/
 
 Book **books;   /* book to store the list of all books */
 int    Nbooks;   /* number of currently defined books */
 int    NBOOKS;   /* number of currently allocated books */
-
-int CreatePage (Book *book, char *id) {
-  int N;
-
-  N = book[0].Npages;
-  book[0].page[N].id = strcreate (name);
-  book[0].page[N].Nvalues = 0;
-  book[0].Npages ++;
-
-  if (book[0].Npages == book[0].NPAGES - 1) {
-    REALLOC ();
-  }
-
-
-}
 
 void InitBooks () {
@@ -49,13 +11,38 @@
 }
 
-/* list known books */
-void ListBooks () {
+void InitBook (Book *book, char *name) {
+
+    book[0].name = strcreate (name);
+
+    book[0].Npages = 0;
+    book[0].NPAGES = 16;
+    ALLOCATE (book[0].pages, Page *, book[0].NPAGES);
+    ALLOCATE (book[0].pageIDs, char *, book[0].NPAGES);
+    ALLOCATE (book[0].index, int, book[0].NPAGES);
+}
+
+void FreeBook (Book *book) {
+
+    int i;
+
+    free (book[0].name);
+    for (i = 0; i < book[0].Npages; i++) {
+	FreePage (book[0].pages[i]);
+	free (book[0].pageIDs[i]);
+    }
+    free (book[0].pages);
+    free (book[0].pageIDs);
+    free (book[0].index);
+}
+
+/* return the given book */
+Book *GetBook (int where) {
 
   int i;
 
-  for (i = 0; i < Nbooks; i++) {
-    gprint (GP_ERR, "%-15s %3d\n", books[i][0].name, books[i][0].Nlines);
-  }
-  return;
+  if (where < 0) where += Nbooks;
+  if (where < 0) return NULL;
+  if (where >= Nbooks) return NULL;
+  return (books[where]);
 }
 
@@ -67,22 +54,8 @@
   for (i = 0; i < Nbooks; i++) {
     if (!strcmp (books[i][0].name, name)) {
-      return (&books[i][0]);
+      return (books[i]);
     }
   }
   return (NULL);
-}
-
-/* make a new named book */
-int InitBook (Book *book) {
-
-  int i;
-
-  for (i = 0; i < book[0].Nlines; i++) {
-    free (book[0].lines[i]);
-  }
-  book[0].Nlines = 0;
-  book[0].NLINES = 16;
-  REALLOCATE (book[0].lines, char *, book[0].NLINES);
-  return (TRUE);
 }
 
@@ -100,8 +73,5 @@
   CHECK_REALLOCATE (books, Book *, NBOOKS, Nbooks, 16);
   ALLOCATE (book, Book, 1);
-  book[0].Nlines = 0;
-  book[0].NLINES = 16;
-  book[0].name = strcreate (name);
-  ALLOCATE (book[0].lines, char *, book[0].NLINES);
+  InitBook (book, name);
   books[N] = book;
   return (book);
@@ -133,284 +103,16 @@
   }
 
-  free (book[0].name);
-  for (i = 0; i < book[0].Nlines; i++) {
-    free (book[0].lines[i]);
-  }
-  free (book[0].lines);
-  free (book);
+  FreeBook (book);
   return (TRUE);
 }
 
-void PushNamedBook (char *name, char *line) {
-
-  Book *book;
-  
-  book = FindBook (name);
-  if (book == NULL) {
-    book = CreateBook (name);
-  }
-  PushBook (book, line);
-  return;
-}
-
-/* push line onto book.  return chars create new lines */
-void PushBook (Book *book, char *line) {
-
-  int N;
-  char *p, *q;
-
-  p = line;
-  q = strchr (line, '\n');
-  N = book[0].Nlines;
-  while (q != NULL) {
-    book[0].lines[N] = strncreate (p, q - p);
-    N++;
-    CHECK_REALLOCATE (book[0].lines, char *, book[0].NLINES, N, 16);
-    p = q + 1;
-    q = strchr (p, '\n');
-  }    
-  if (*p) {
-    book[0].lines[N] = strcreate (p);
-    N++;
-    CHECK_REALLOCATE (book[0].lines, char *, book[0].NLINES, N, 16);
-  }
-  book[0].Nlines = N;
-  return;
-}
-
-// return a newly allocated string containing the requested key value
-char *ChooseSingleKey (char *line, int Key) {
-
-  int i;
-  char *key, *p;
-
-  if (Key == -1) {
-    key = strcreate (line);
-    return (key);
-  }
-
-  key = line;
-  for (i = 0; (i < Key) && (key != NULL); i++) {
-    p = nextword (key);
-    key = p;
-  }
-  key = thisword (key);
-  return (key);
-}
-
-/* construct merged key given keylist of the form K:N:M */ 
-char *ChooseKey (char *line, char *keylist) {
-
-  char *output, *entry, *key, *p, *q;
-  int first, keynum;
-
-  if (line == NULL) return (NULL);
-  if (keylist == NULL) return (line);
-
-  ALLOCATE (output, char, strlen(line) + 1);
-  memset (output, 0, strlen(line) + 1);
-
-  first = TRUE;
-  p = q = keylist;
-  while (q != NULL) {
-    q = strchr (p, ':');
-    if (q == NULL) {
-      entry = strcreate (p);
-    } else {
-      entry = strncreate (p, q - p);
-    }
-    keynum = atoi (entry);
-    free (entry);
-
-    key = ChooseSingleKey (line, keynum);
-
-    if (!first) strcat (output, ":");
-    if (key) {
-	strcat (output, key);
-	free (key);
-    }
-
-    if (q != NULL) p = q + 1;
-    first = FALSE;
-  }
-  return (output);
-}
-
-/* push line onto book, skipping existing matches (optionally by key) */
-void PushBookUnique (Book *book, char *line, char *Key) {
-
-  int i, j, N, found;
-  char *p, *q, *key1, *key2;
-  Book tmp;
-
-  /* init tmp book */
-  tmp.Nlines = 0;
-  tmp.NLINES = 16;
-  ALLOCATE (tmp.lines, char *, tmp.NLINES);
-
-  /* push entries on tmp book */
-  p = line;
-  q = strchr (line, '\n');
-  N = tmp.Nlines;
-  while (q != NULL) {
-    tmp.lines[N] = strncreate (p, q - p);
-    N++;
-    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
-    p = q + 1;
-    q = strchr (p, '\n');
-  }    
-  if (*p) {
-    tmp.lines[N] = strcreate (p);
-    N++;
-    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
-  }
-  tmp.Nlines = N;
-
-  /* add unique entries in tmp to book */
-  for (i = 0; i < tmp.Nlines; i++) {
-    key1 = ChooseKey (tmp.lines[i], Key);
-    if (key1 == NULL) continue;
-    found = FALSE;
-    for (j = 0; !found && (j < book[0].Nlines); j++) {
-      key2 = ChooseKey (book[0].lines[j], Key);
-      if (key2 == NULL) continue;
-      found = !strcmp (key1, key2);
-      free (key2);
-    }      
-    if (!found) PushBook (book, tmp.lines[i]);
-    free (key1);
-  }
-  for (i = 0; i < tmp.Nlines; i++) {
-    free (tmp.lines[i]);
-  } 
-  free (tmp.lines);
-  return;
-}
-
-/* push line onto book, replacing matches (optionally by Key) */
-void PushBookReplace (Book *book, char *line, char *Key) {
-
-  int i, j, N, found;
-  char *p, *q, *key1, *key2;
-  Book tmp;
-
-  /* init tmp book */
-  tmp.Nlines = 0;
-  tmp.NLINES = 16;
-  ALLOCATE (tmp.lines, char *, tmp.NLINES);
-
-  /* push entries on tmp book */
-  p = line;
-  q = strchr (line, '\n');
-  N = tmp.Nlines;
-  while (q != NULL) {
-    tmp.lines[N] = strncreate (p, q - p);
-    N++;
-    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
-    p = q + 1;
-    q = strchr (p, '\n');
-  }    
-  if (*p) {
-    tmp.lines[N] = strcreate (p);
-    N++;
-    CHECK_REALLOCATE (tmp.lines, char *, tmp.NLINES, N, 16);
-  }
-  tmp.Nlines = N;
-
-  /* add unique entries in tmp to book */
-  for (i = 0; i < tmp.Nlines; i++) {
-    key1 = ChooseKey (tmp.lines[i], Key);
-    if (key1 == NULL) continue;
-    found = FALSE;
-    for (j = 0; !found && (j < book[0].Nlines); j++) {
-      key2 = ChooseKey (book[0].lines[j], Key);
-      if (key2 == NULL) continue;
-      found = !strcmp (key1, key2);
-      if (found) {
-	// XXX do I need to free book[0].lines[j]??
-	book[0].lines[j] = strcreate (tmp.lines[i]);
-      }
-      free (key2);
-    }      
-    if (!found) PushBook (book, tmp.lines[i]);
-    free (key1);
-  }
-  for (i = 0; i < tmp.Nlines; i++) {
-    free (tmp.lines[i]);
-  } 
-  free (tmp.lines);
-  return;
-}
-
-char *PopBook (Book *book) {
-
-  int i, NLINES_2;
-  char *line;
-
-  if (book[0].Nlines == 0) return (NULL);
-  line = book[0].lines[0];
-
-  for (i = 0; i < book[0].Nlines - 1; i++) {
-    book[0].lines[i] = book[0].lines[i+1];
-  }
-  book[0].Nlines --;
-
-  /* shrink book allocation if small enough */
-  NLINES_2 = MAX (16, book[0].NLINES / 2);
-  if (book[0].Nlines < NLINES_2) {
-    book[0].NLINES = NLINES_2;
-    REALLOCATE (book[0].lines, char *, book[0].NLINES);
-  }    
-  return (line);
-}
-
-/* pop the first entry which for which the key matches */
-char *PopBookMatch (Book *book, char *Key, char *value) {
-
-  int i, choice, NLINES_2;
-  char *line, *test;
-
-  if (book[0].Nlines == 0) return (NULL);
-
-  /* find the matching key */
-  choice = -1;
-  for (i = 0; (i < book[0].Nlines) && (choice == -1); i++) {
-      test = ChooseKey (book[0].lines[i], Key);
-      if (test == NULL) continue;
-      if (strcmp (value, test)) { 
-	free (test);
-	continue;
-      }
-      free (test);
-      choice = i;
-  }
-  if (choice == -1) return NULL;
-
-  line = book[0].lines[choice];
-
-  for (i = choice; i < book[0].Nlines - 1; i++) {
-    book[0].lines[i] = book[0].lines[i+1];
-  }
-  book[0].Nlines --;
-
-  /* shrink book allocation if small enough */
-  NLINES_2 = MAX (16, book[0].NLINES / 2);
-  if (book[0].Nlines < NLINES_2) {
-    book[0].NLINES = NLINES_2;
-    REALLOCATE (book[0].lines, char *, book[0].NLINES);
-  }    
-  return (line);
-}
-
-int PrintBook (Book *book) {
+/* list known books */
+void ListBooks () {
 
   int i;
 
-  if (book[0].Nlines == 0) return (TRUE);
-
-  for (i = 0; i < book[0].Nlines; i++) {
-    gprint (GP_LOG, "%s\n", book[0].lines[i]);
+  for (i = 0; i < Nbooks; i++) {
+    gprint (GP_ERR, "%-15s %3d\n", books[i][0].name, books[i][0].Npages);
   }
-  return (TRUE);
+  return;
 }
-
Index: /trunk/Ohana/src/opihi/lib.data/page.c
===================================================================
--- /trunk/Ohana/src/opihi/lib.data/page.c	(revision 10997)
+++ /trunk/Ohana/src/opihi/lib.data/page.c	(revision 10997)
@@ -0,0 +1,168 @@
+# include "data.h"
+
+void InitPage (Page *page, char *name) {
+
+    page[0].name = strcreate (name);
+
+    page[0].Nwords = 0;
+    page[0].NWORDS = 16;
+    ALLOCATE (page[0].words, char *, page[0].NWORDS);
+    ALLOCATE (page[0].value, char *, page[0].NWORDS);
+}
+
+void FreePage (Page *page) {
+
+    int i;
+
+    free (page[0].name);
+    for (i = 0; i < page[0].Nwords; i++) {
+      free (page[0].words[i]);
+      free (page[0].value[i]);
+    }
+    free (page[0].words);
+    free (page[0].value);
+}
+
+/* return the given book */
+Page *GetPage (Book *book, int where) {
+
+  int i;
+
+  if (where < 0) where += book[0].Npages;
+  if (where < 0) return NULL;
+  if (where >= book[0].Npages) return NULL;
+  return (book[0].pages[where]);
+}
+
+/* return the given page */
+/* XXX use index to find more quickly */
+Page *FindPage (Book *book, char *name) {
+
+  int i;
+
+  for (i = 0; i < book[0].Npages; i++) {
+    if (!strcmp (book[0].pages[i][0].name, name)) {
+      return (book[0].pages[i]);
+    }
+  }
+  return (NULL);
+}
+
+/* make a new named page */
+Page *CreatePage (Book *book, char *name) {
+
+  int N;
+  Page *page;
+
+  page = FindPage (book, name);
+  if (page != NULL) return (page);
+
+  N = book[0].Npages;
+  book[0].Npages ++;
+  if (book[0].Npages >= book[0].NPAGES) {
+    book[0].NPAGES += 16;
+    REALLOCATE (book[0].pages, Page *, book[0].NPAGES);
+    REALLOCATE (book[0].pageIDs, char *, book[0].NPAGES);
+    REALLOCATE (book[0].index, int, book[0].NPAGES);
+  }
+  ALLOCATE (page, Page, 1);
+  InitPage (page, name);
+  book[0].pages[N] = page;
+  book[0].pageIDs[N] = strcreate(name);
+  book[0].index[N] = N;
+  
+  /* at this point, I should sort the index */
+
+  return (page);
+}
+
+/* delete a page in a book */
+int DeletePage (Book *book, Page *page) {
+
+  char *pageID;
+  int i, N, NPAGES_2;
+
+  /* find page in page list */
+  N = -1;
+  for (i = 0; i < book[0].Npages; i++) {
+    if (book[0].pages[i] == page) {
+      N = i;
+      break;
+    }
+  }
+  if (N == -1) return (FALSE);
+
+  pageID = book[0].pageIDs[i];
+
+  for (i = N; i < book[0].Npages - 1; i++) {
+    book[0].pages[i] = book[0].pages[i + 1];
+    book[0].pageIDs[i] = book[0].pageIDs[i + 1];
+    book[0].index[i] = book[0].index[i + 1];
+  }
+  book[0].Npages --;
+  NPAGES_2 = MAX (16, book[0].NPAGES / 2);
+  if (book[0].Npages < NPAGES_2) {
+    book[0].NPAGES = NPAGES_2;
+    REALLOCATE (book[0].pages, Page *, book[0].NPAGES);
+    REALLOCATE (book[0].pageIDs, char *, book[0].NPAGES);
+    REALLOCATE (book[0].index, int, book[0].NPAGES);
+  }
+
+  FreePage (page);
+  free (pageID);
+  return (TRUE);
+}
+
+void ListPages (Book *book) {
+
+  int i;
+
+  for (i = 0; i < book[0].Npages; i++) {
+    gprint (GP_ERR, "%-15s %3d\n", book[0].pages[i][0].name, book[0].pages[i][0].Nwords);
+  }
+  return;
+}
+
+void ListWords (Page *page) {
+
+  int i;
+
+  for (i = 0; i < page[0].Nwords; i++) {
+    gprint (GP_ERR, "%-15s %15s\n", page[0].words[i], page[0].value[i]);
+  }
+  return;
+}
+
+int BookSetWord (Page *page, char *word, char *value) {
+
+  int i;
+
+  for (i = 0; i < page[0].Nwords; i++) {
+    if (!strcmp (page[0].words[i], word)) {
+      free (page[0].value[i]);
+      page[0].value[i] = strcreate (value);
+      return TRUE;
+    }
+  }
+
+  page[0].Nwords ++;
+  if (page[0].Nwords >= page[0].NWORDS) {
+    page[0].NWORDS += 16;
+    REALLOCATE (page[0].words, char *, page[0].NWORDS);
+    REALLOCATE (page[0].value, char *, page[0].NWORDS);
+  }      
+  page[0].words[i] = strcreate (word);
+  page[0].value[i] = strcreate (value);
+  return (TRUE);
+}
+
+char *BookGetWord (Page *page, char *word) {
+  int i;
+
+  for (i = 0; i < page[0].Nwords; i++) {
+    if (!strcmp (page[0].words[i], word)) {
+      return (page[0].value[i]);
+    }
+  }
+  return NULL;
+}
Index: /trunk/Ohana/src/opihi/pantasks/Makefile
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/Makefile	(revision 10996)
+++ /trunk/Ohana/src/opihi/pantasks/Makefile	(revision 10997)
@@ -57,5 +57,6 @@
 $(SDIR)/task_command.$(ARCH).o \
 $(SDIR)/task_options.$(ARCH).o \
-$(SDIR)/version.$(ARCH).o
+$(SDIR)/version.$(ARCH).o \
+$(SDIR)/ipptool2book.$(ARCH).o
 
 client = \
Index: /trunk/Ohana/src/opihi/pantasks/init.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/init.c	(revision 10996)
+++ /trunk/Ohana/src/opihi/pantasks/init.c	(revision 10997)
@@ -21,4 +21,5 @@
 int verbose         PROTO((int, char **));
 int version         PROTO((int, char **));
+int ipptool2book    PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -44,4 +45,5 @@
   {"version",    version,      "show version information"},
   {"verbose",    verbose,      "set/toggle verbose mode"},
+  {"ipptool2book", ipptool2book, "convert queue with ipptool output to book"},
 }; 
 
Index: /trunk/Ohana/src/opihi/pantasks/init_server.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/init_server.c	(revision 10996)
+++ /trunk/Ohana/src/opihi/pantasks/init_server.c	(revision 10997)
@@ -17,4 +17,5 @@
 int version         PROTO((int, char **));
 int server          PROTO((int, char **));
+int ipptool2book    PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -36,4 +37,5 @@
   {"version",    version,      	"show version information"},
   {"verbose",    verbose,      	"set/toggle verbose mode"},
+  {"ipptool2book", ipptool2book, "convert queue with ipptool output to book"},
 }; 
 
Index: /trunk/Ohana/src/opihi/pantasks/ipptool2book.c
===================================================================
--- /trunk/Ohana/src/opihi/pantasks/ipptool2book.c	(revision 10997)
+++ /trunk/Ohana/src/opihi/pantasks/ipptool2book.c	(revision 10997)
@@ -0,0 +1,209 @@
+# include "pantasks.h"
+
+# define FREEKEYS \
+  if (keys) { for (i = 0; i < Nkeys; i++) { free (keys[i]); } free (keys); }
+
+// convert the named queue with ipptool metadata output into book format
+int ipptool2book (int argc, char **argv) {
+
+  int i, N, onPage, found, Unique, Nkeys, NKEYS;
+  char *line, *tmpword, *tmpvalue;
+  char pagename[512], *bookName, **keys, *p, *q;
+  Page *page;
+  Book *book;
+  Queue *queue;
+
+  Unique = FALSE;
+  if ((N = get_argument (argc, argv, "-uniq"))) {
+    remove_argument (N, &argc, argv);
+    Unique = TRUE;
+  }
+
+  Nkeys = 0;
+  keys = NULL;
+  if ((N = get_argument (argc, argv, "-id"))) {
+    remove_argument (N, &argc, argv);
+
+    /* parse the key (word:word:word) into constituents */
+    NKEYS = 10;
+    ALLOCATE (keys, char *, NKEYS);
+    
+    p = q = argv[N];
+    while (q != NULL) {
+      q = strchr (p, ':');
+      keys[Nkeys] = (q == NULL) ? strcreate (p) : strncreate (p, q - p);
+      p = q + 1;
+      Nkeys ++;
+      CHECK_REALLOCATE (keys, char *, NKEYS, Nkeys, 10);
+    }
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: ipptool2book (queue) (book) [-uniq] [-id key[:key..]]\n");
+    FREEKEYS;
+    return (FALSE);
+  }
+
+  queue = FindQueue (argv[1]);
+  if (queue == NULL) {
+    gprint (GP_ERR, "queue %s not found\n", argv[1]);
+    FREEKEYS;
+    return (FALSE);
+  }
+    
+  book = CreateBook (argv[2]);
+
+  /* the first non-whitespace line of the queue should contain:
+     BookName MULTI
+  */
+
+  /* scan for first non-emtpy line */
+  while (TRUE) {
+    line = PopQueue (queue);
+    if (!line) {
+      FREEKEYS;
+      return (TRUE); // no output in queue - not an error?
+    }
+    if (*line) break;
+    free (line);
+  }
+  bookName = thisword (line);
+  tmpword = nextword (line);
+
+  if (strcmp(tmpword, "MULTI")) {
+    gprint (GP_ERR, "ERROR: missing metadata output name on first line\n");
+    free (bookName);
+    free (line);
+    FREEKEYS;
+    return FALSE;
+  }
+  if (strcmp(bookName, argv[2])) {
+    gprint (GP_ERR, "WARNING: metadata output name does not match expected bookname\n");
+  }
+  free (line);
+  
+  onPage = FALSE;
+  while (TRUE) {
+    line = PopQueue (queue);
+    if (!line) {
+      free (bookName);
+      if (onPage) {
+	gprint (GP_ERR, "ERROR: unterminated metadata\n");
+	FREEKEYS;
+	return (FALSE);
+      }
+      FREEKEYS;
+      return (TRUE);
+    }
+    if (!*line) {
+      free (line);
+      continue;
+    }
+    
+    tmpword = thisword (line);
+    if (tmpword == NULL) abort(); // should not happen if line exists
+
+    if (!onPage) {
+      if (strcmp(tmpword, bookName)) {
+	gprint (GP_ERR, "ERROR: unexpected metadata name %s\n", tmpword);
+	free (line);
+	free (tmpword);
+	free (bookName);
+	FREEKEYS;
+	return (FALSE);
+      }
+      free (tmpword);
+      tmpword = thisword (nextword (line));
+      if (strcmp(tmpword, "METADATA")) {
+	gprint (GP_ERR, "ERROR: missing METADATA tag\n");
+	free (line);
+	free (tmpword);
+	free (bookName);
+	FREEKEYS;
+	return FALSE;
+      }
+      onPage = TRUE;
+      // we don't know the page name until we load its data?
+      sprintf (pagename, "page.%03d", book[0].Npages);
+      page = CreatePage (book, pagename);
+      free (line);
+      free (tmpword);
+      continue;
+    } 
+
+    /* close out the page */
+    if (!strcmp(tmpword, "END")) {
+      // XXX set the page name based on the contents
+      free (tmpword);
+      free (line);
+
+      if (keys) {
+	memset (pagename, 0, 512);
+	for (i = 0; i < Nkeys; i++) {
+	  tmpword = BookGetWord (page, keys[i]);
+	  if (tmpword == NULL) {
+	    gprint (GP_ERR, "ERROR: missing key %s for ID\n", keys[i]);
+	    FREEKEYS;
+	    return (FALSE);
+	  }
+	  if (i > 0) strcat (pagename, ":");
+	  strcat (pagename, tmpword);
+	}
+	free (page[0].name);
+	page[0].name = strcreate (pagename);
+      }
+
+      if (Unique) {
+	/* search for an existing page with this name
+	   delete this one if one is found */
+	/* XXX don't attach this page to the book until this step is passed */	
+
+	found = FALSE;
+	for (i = 0; !found && (i < book[0].Npages); i++) {
+	  if (book[0].pages[i] == page) continue;
+	  if (!strcmp (book[0].pages[i][0].name, page[0].name)) {
+	    found = TRUE;
+	  }
+	}
+	if (found) {
+	  DeletePage (book, page);
+	}
+      }
+
+      onPage = FALSE;
+      continue;
+    }
+
+    tmpvalue = thisword (nextword(nextword(line)));
+    if (tmpvalue == NULL) {
+      gprint (GP_ERR, "ERROR: missing value for %s\n", tmpword);
+      free (tmpword);
+      free (line);
+      free (bookName);
+      FREEKEYS;
+      return (FALSE);
+    }
+    BookSetWord (page, tmpword, tmpvalue);
+    free (tmpword);
+    free (tmpvalue);
+    free (line);
+  }    
+  FREEKEYS;
+  return (FALSE);
+}
+
+/* scan through the queue lines.  these should look like:  
+
+bookName METADATA
+key type value
+key type value
+...
+END
+
+bookName METADATA
+key type value
+key type value
+...
+END
+*/
