3. Documentation Database

3.1. doc_db.cc


3.1.1
#include "table.hh"
#include <vector>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <popt.h>
using namespace libdoc;

3.1.2
void decode_options (int argc, const char **argv);
bool get_record_args (const Table &tab, vector <TableField> &vals);
bool parse_number (const string &s, unsigned int &n);
void print_record (FILE *f, const TableRecord &rec, const Table::Iterator *it);
int command_create (const string &filename);
int command_exists (const string &filename);
int command_find (const string &filename);
int command_header (const string &filename);
int command_insert (const string &filename);
int command_list (const string &filename);
int command_tree (const string &filename);
int command_update (const string &filename);

3.1.3

The name of the porgram, taken from argv[0].

const char *progname;

3.1.4
static int option_create = 0;
static int option_exists = 0;
static const char *option_fields = 0;
static int option_find = 0;
static int option_header = 0;
static Key option_id = 0;
static const char *option_id_str = 0;
static const char *option_primkey = 0;
static const char *option_seckey = 0;
static int option_insert = 0;
static int option_list = 0;
static const char *option_secondary = 0;
static int option_tree = 0;
static int option_update = 0;
static vector <string> leftovers;

3.1.5

The table of command line options.

poptOption options_table[] =
{
   POPT_AUTOHELP
   { "create", 'c', POPT_ARG_NONE, &option_create, 0,
      "Create a new table in the specified file", 0 },
   { "exists", 'e', POPT_ARG_NONE, &option_exists, 0,
      "Exit code is 127 if the given ID is found in the table", 0 },
   { "find", 'f', POPT_ARG_NONE, &option_find, 0,
      "Print the record associated with the given key ID", 0 },
   { "fields", 'F', POPT_ARG_STRING, &option_fields, 0,
      "Specify the types of the fields, used with -c", "FIELDTYPES" },
   { "header", 'H', POPT_ARG_NONE, &option_header, 0,
      "Print information from the header of the table file", 0 },
   { "key", 'k', POPT_ARG_STRING, &option_id_str, 'k',
      "Specify an ID number for use as the primary key", "ID" },
   { "primary-key", 'P', POPT_ARG_STRING, &option_primkey, 0,
      "Specify the type of primary key to use (AUTO, INT or STRING)",
      "TYPENAME" },
   { "secondary-key", 'K', POPT_ARG_STRING, &option_seckey, 0,
      "Specify a string ID to look up as the secondary key", "ID" },
   { "insert", 'i', POPT_ARG_NONE, &option_insert, 0,
      "Add a new record to a table", 0 },
   { "list", 'l', POPT_ARG_NONE, &option_list, 0,
      "Print all the records from a table to stdout", 0 },
   { "secondary", 's', POPT_ARG_STRING, &option_secondary, 0,
      "Specify the field number and name of a secondary index", "FIELD:NAME" },
   { "tree", 't', POPT_ARG_NONE, &option_tree, 0,
      "Print the contents of the table file as a binary search tree", 0 },
   { "update", 'u', POPT_ARG_NONE, &option_update, 0,
      "Alter the specified record", 0 },
   { NULL, 0, 0, NULL, 0, NULL, NULL }
};

3.1.6

Strings used as the names of the types of primary keys. The array indices match the enum codes.

const char *key_type_names[3] =
{
   "integer",
   "string",
   "automaticly generated integer"
};

3.1.7
int
main (int argc, const char **argv)
{
   decode_options (argc, argv);
   int exitcode = 0;

   try
   {
      string filename;
      if (leftovers.size() ≥ 1 and strcmp (leftovers[0].c_str(), "-") ≠ 0)
         filename = leftovers[0];

      if (option_create)
         exitcode = command_create (filename);
      else if (option_exists)
         exitcode = command_exists (filename);
      else if (option_find)
         exitcode = command_find (filename);
      else if (option_header)
         exitcode = command_header (filename);
      else if (option_insert)
         exitcode = command_insert (filename);
      else if (option_list)
         exitcode = command_list (filename);
      else if (option_tree)
         exitcode = command_tree (filename);
      else if (option_update)
         exitcode = command_update (filename);
      else
      {
         fprintf (stderr, "%s: a command option must be specified.\n",
                  progname);
         exit (1);
      }
   }
   catch (Exception &e)
   {
      fprintf (stderr, "%s: %s\n", progname, e.explain().c_str());
      return 1;
   }

   return exitcode;
}

3.1.8

Decode the command line options, using the popt library.

void
decode_options (int argc, const char **argv)
{
   progname = argv[0];
   poptContext context = poptGetContext ("doc_db", argc, argv,
                                         options_table, 0);

   int rc;
   while ((rc = poptGetNextOpt (context)) > 0)
   {
      if (rc == 'k')
      {
         if (!parse_number (option_id_str, option_id))
         {
            fprintf (stderr, "%s: bad number `%s' as argument to `-k'.\n",
                     progname, option_id_str);
            exit (1);
         }
      }
      else
         assert (false);
   }

   if (rc < -1)
   {
      fprintf (stderr, "%s: error in command line options.\n", progname);
      exit (1);
   }

   while (poptPeekArg (context))
      leftovers.push_back (poptGetArg (context));

   poptFreeContext (context);
}

3.1.9
bool
get_record_args (const Table &tab, vector <TableField> &vals)
{
   const string &flds = tab.get_field_types();

   for (size_t i = 0; i < flds.size(); ++i)
   {
      TableField fl;

      if (leftovers.size() ≤ i + 1)
      {
         fprintf (stderr, "%s: not enough fields given on command line.\n",
                  progname);
         return false;
      }

      fl.type = flds[i];
      if (fl.type == 'i')
      {
         if (!parse_number (leftovers[i + 1].c_str(), fl.v_int))
         {
            fprintf (stderr, "%s: field number %d must be an integer.\n",
                     progname, (int) i + 1);
            return false;
         }
      }
      else if (fl.type == 's')
         fl.v_string = new string (leftovers[i + 1]);
      else if (fl.type == 'I')
      {
         const char *s = leftovers[i + 1].c_str();
         if (*s == '{')
            ++s;

         fl.v_int_a = new vector <unsigned int>;
         unsigned int val = 0;
         bool val_used = false;
         do
         {
            if (isdigit (*s))
               val = val * 10 + (*s - '0'), val_used = true;
            else if ((*s == ',' || *s == '\0') && val_used)
            {
               fl.v_int_a->push_back (val);
               val = 0;
               val_used = false;
            }
            else
            {
               fprintf (stderr, "%s: field number %d is bad syntax for a list"
                        " of integers.\n", progname, (int) i + 1);
               return false;
            }
         }
         while (*s++);
      }
      else
         assert (false);

      vals.push_back (fl);
   }

   return true;
}

3.1.10
bool
parse_number (const string &s, unsigned int &n)
{
   char *endptr;
   unsigned long i = strtoul (s.c_str(), &endptr, 0);

   if (s.c_str() == endptr or *endptr ≠ '\0')
      return false;

   // TODO: check to see whether it fits in an int.
   n = i;
   return true;
}

3.1.11
void
print_record (FILE *f, const TableRecord &rec, const Table::Iterator *it)
{
   if (rec.id)
      fprintf (f, "%u", rec.id);
   else
   {
      assert (it);
      char *p = new char[it->dat.dsize + 1];
      strncpy (p, it->dat.dptr, it->dat.dsize);
      p[it->dat.dsize] = '\0';

      fprintf (f, "%s", p);

      delete p;
   }

   for (size_t i = 0; i < rec.fields.size(); ++i)
   {
      switch (rec.fields[i].type)
      {
       case 'i':
         fprintf (f, ":%u", rec.fields[i].v_int);
         break;

       case 's':
         fprintf (f, ":%s", rec.fields[i].v_string->c_str());
         break;

       case 'I':
         fputs (":{", f);

         for (size_t j = 0; j < rec.fields[i].v_int_a->size(); ++j)
            fprintf (f, (j == 0 ? "%u" : ",%u"), (*rec.fields[i].v_int_a)[j]);

         fputc ('}', f);
         break;

       default:
         assert (false);
      }
   }

   fputc ('\n', f);
}

3.1.12
int
command_create (const string &filename)
{
   PrimKeyType primkey;

   if (option_primkey == 0)
      primkey = PRIMKEY_AUTO;
   else if (strncasecmp (option_primkey, "auto", 4) == 0)
      primkey = PRIMKEY_AUTO;
   else if (strncasecmp (option_primkey, "int", 3) == 0)
      primkey = PRIMKEY_INTEGER;
   else if (strncasecmp (option_primkey, "str", 3) == 0)
      primkey = PRIMKEY_STRING;
   else
   {
      fprintf (stderr, "%s: bad primary key type `%s'.\n", progname,
               option_primkey);
      return 1;
   }

   Table::create (filename, option_fields ? option_fields : "s",
                  primkey, option_secondary ? option_secondary : "");

   return 0;
}

3.1.13
int
command_exists (const string &filename)
{
   if (option_id == 0)
   {
      fprintf (stderr, "%s: ID number required for key.\n", progname);
      exit (1);
   }

   Table *tab = new Table (filename, true);
   bool found = tab->exists (option_id);

   delete tab;
   return found ? 0 : 127;
}

3.1.14
int
command_find (const string &filename)
{
   if (option_id == 0 && option_seckey == 0)
   {
      fprintf (stderr, "%s: `-k' or `-K' required to specify  key.\n",
               progname);
      exit (1);
   }
   if (option_id && option_seckey)
      fprintf (stderr, "%s: warning: ignoring `-K', using `-k' instead.\n",
               progname);

   Table *tab = new Table (filename, true);
   TableRecord rec;
   if (option_id)
   {
      rec.id = option_id;
      if (!tab->find (option_id, rec.fields))
      {
         fprintf (stderr, "No record with primary key `%u' exists.\n",
                  option_id);
         return 127;
      }
   }
   else
   {
      TableField keyfld;
      keyfld.type = 's';
      string k = option_seckey;
      keyfld.v_string = &k;

      if (!tab->find (keyfld, rec))
      {
         fprintf (stderr, "No record with secondary key `%s' exists.\n",
                  option_seckey);
         return 127;
      }
   }

   print_record (stdout, rec, 0);

   delete tab;
   return 0;
}

3.1.15
int
command_header (const string &filename)
{
   Table *tab = new Table (filename, true);

   printf ("Filename: %s\nPrimary key type: %s\nFields: %s\n"
           "Root node offset: 0x%lX\nNumber of records: %u\nLength: %u\n",
           tab->get_filename().c_str(),
           key_type_names[tab->get_primary_key_type()],
           tab->get_field_types().c_str(), tab->get_root(),
           tab->get_num_recs(), tab->get_length());

   if (!tab->get_secondary_filename().empty())
   {
      printf ("Secondary index: `%s' for field %u\n",
              tab->get_secondary_filename().c_str(),
              tab->get_secondary_field() + 1);
   }

   delete tab;
   return 0;
}

3.1.16
int
command_insert (const string &filename)
{
   int exitcode = 0;
   Table *tab = new Table (filename, false);

   TableRecord rec;
   rec.id = option_id;
   if (!get_record_args (*tab, rec.fields))
      exitcode = 1;
   else if (!tab->insert (rec))
   {
      fprintf (stderr, "%s: there is already a record with the key `%d'.\n",
               progname, (int) rec.id);
      exitcode = 7;
   }

   delete tab;
   return exitcode;
}

3.1.17
int
command_list (const string &filename)
{
   Table *tab = new Table (filename, true);

   Table::Iterator iter;
   bool notend = tab->iter_begin (iter);
   while (notend)
   {
      TableRecord rec;
      tab->iter_deref (iter, rec);

      print_record (stdout, rec, &iter);

      notend = tab->iter_next (iter);
   }

   tab->iter_delete (iter);
   delete tab;

   return 0;
}

3.1.18
int
command_tree (const string &filename)
{
   int exitcode = 0;
   Table *tab = new Table (filename, true);

   if (tab->get_primary_key_type() == PRIMKEY_STRING)
   {
      fprintf (stderr, "%s: this table isn't stored as a binary tree.\n",
               progname);
      exitcode = 23;
   }

   tab->debugging_tree (stdout);

   delete tab;
   return exitcode;
}

3.1.19
int
command_update (const string &filename)
{
   int exitcode = 0;
   Table *tab = new Table (filename, false);

   TableRecord rec;
   rec.id = option_id;
   if (!get_record_args (*tab, rec.fields))
      exitcode = 1;
   else if (!tab->update (rec))
   {
      fprintf (stderr, "%s: the record with the key `%d' couldn't be found.\n",
               progname, (int) rec.id);
      exitcode = 7;
   }

//   rec.cleanup();     // XXX
   delete tab;
   return exitcode;
}