From: "Igor B. Poretsky" <poretsky@mlbox.ru>
Date: Wed, 15 Feb 2023 14:25:38 +0300
Subject: New lexical database holding utility

---
 lib/Makefile    |   50 ++++++----
 lib/lexholder.c |  271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 301 insertions(+), 20 deletions(-)
 create mode 100644 lib/lexholder.c

--- a/lib/Makefile
+++ b/lib/Makefile
@@ -10,41 +10,51 @@
 #
 #
 ############################################################################
+
+# Installation paths
+prefix = /usr/local
+bindir = ${prefix}/bin
+datadir = ${prefix}/share/freespeech
+docdir = ${prefix}/share/doc/enlex-data
+
+# Installed database filename
+DBF = enlex.db
+
+# Installed utility name
+UTILITY = lexholder-en
+
 # Select the appropriate Makefile options for your system. then type make.
 ############################################################################
-# Sun users
-# CC = gcc
-# CFLAGS = -O2 -s
-# LIBS = -lm
-############################################################################
 # FreeBSD users
 # CC = gcc
-# CFLAGS = -O2 -DFBSD_DATABASE -s
+# CFLAGS = -O2 -DFBSD_DATABASE
 # LIBS = -lm
 ############################################################################
 # Linux users
 CC = gcc
-CFLAGS = -O2 -DBERKELEYDB -s
-LIBS = -lm -ldb-3
+CFLAGS = -g -Wall -O2 -DBERKELEYDB
+LIBS = -ldb
 ############################################################################
 
 
-all: lexicon 
+all: lexicon
 
-lexicon: text710.edin dbm_prog
-	./dbm_prog -o lexicon -i text710.edin
+lexicon: text710.edin lexholder
+	./lexholder -qf $< $@
 
 #text710.edin: text710.dat read_ox_dict
 #	read_ox_dict text710.dat > text710.edin
 
-dbm_prog: dbm_prog.c
-	$(CC) $(CFLAGS) -o $@ $< $(LIBS)
-
-clean:
-	rm -f dbm_prog *.o core gmon.out lexicon 
+lexholder: lexholder.c
+	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS)
 
-distribution:
-	echo "This needs written."
-
-# dependencies
+.PHONY: install
+install: lexholder lexicon
+	install -d ${bindir} ${datadir} ${docdir}
+	install -m 0755 -p lexholder ${bindir}/${UTILITY}
+	install -m 0644 -p lexicon ${datadir}/${DBF}
+	install -m 0644 -p text710.doc ${docdir}
 
+.PHONY: clean
+clean:
+	rm -f lexholder *.o lexicon
--- /dev/null
+++ b/lib/lexholder.c
@@ -0,0 +1,271 @@
+/* lexholder.c
+ *
+ * Lexicon database holding utility.
+ */
+
+/*
+ * Copyright (C) 2010 Igor B. Poretsky <poretsky@mlbox.ru>
+ *
+ *  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 2 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.
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#ifdef FBSD_DATABASE
+#include <db.h>
+#elif defined(BERKELEYDB)
+#include <db_185.h>
+#else
+#include <ndbm.h>
+#endif
+
+#include <fcntl.h>
+
+
+/* Command line error codes */
+#define CL_OK 0
+#define CS_CONFLICT 1
+#define NO_DB_FILE 2
+
+
+static const char *usage =
+  "Lexical database holding utility.\n\n"
+
+  "This utility is designed for constructing, managing, testing and querying\n"
+  "lexical database providing pronunciation info for English words.\n\n"
+
+  "Usage:\t%s [options] <db_path>\n\n"
+
+  "When filling and updating the database, new records are read\n"
+  "from the standard input. When extracting data from the database\n"
+  "the result is printed to the standard output.\n"
+  "This behaviour can be changed by the \"-f\" switch.\n\n"
+
+  "If no options are specified, the program reads it's standard input\n"
+  "and stores it's content in the database.\n\n"
+
+  "The recognized options are as follows:\n\n"
+
+  "-h -- Print this help (the only option not requiring the database path)\n"
+  "-l -- List database content\n"
+  "-s <word> -- Search specified word\n"
+  "-d <word> -- Delete record for specified word\n"
+  "-f <file> -- Use specified file instead of standard input or output\n"
+  "-r -- Replace mode\n"
+  "-q -- Don't print messages about ignoring duplicates\n\n";
+
+
+int main(int argc, char *argv[])
+{
+  FILE *fp = NULL;
+  void *db;
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+  DBT key, value;
+#else
+  datum key, value;
+#endif
+  char line[256];
+  char *f = NULL, *d = NULL, *s = NULL;
+  int i, n, q = 0, ret = NO_DB_FILE;
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+  int mode = R_NOOVERWRITE;
+#else
+  int mode = GDBM_INSERT;
+#endif
+
+  /* Parse command line */
+  if(argc==1)
+    {
+      (void)fprintf(stderr, usage, argv[0]);
+      return EXIT_FAILURE;
+    }
+  while((n = getopt(argc,argv,"f:s:d:lqrh")) != -1)
+    switch(n)
+      {
+      case 'f':
+        if (strcmp(optarg, "-"))
+          f = optarg;
+        break;
+      case 'd':
+        if (d || s) ret = CS_CONFLICT;
+        else d = optarg;
+        break;
+      case 's':
+        if (d || s) ret = CS_CONFLICT;
+        else s = optarg;
+        break;
+      case 'l':
+        if (d || s) ret = CS_CONFLICT;
+        else s = line;
+        break;
+      case 'r':
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+        mode = 0;
+#else
+        mode = GDBM_REPLACE;
+#endif
+        break;
+      case 'q':
+        q = 1;
+        break;
+      case 'h':
+        (void)fprintf(stderr, usage, argv[0]);
+        return EXIT_SUCCESS;
+      default:
+        (void)fprintf(stderr, usage, argv[0]);
+        return EXIT_FAILURE;
+      }
+  if (optind && argv[optind])
+    ret = CL_OK;
+  switch (ret)
+    {
+    case CS_CONFLICT:
+      (void)fprintf(stderr,
+                    "Ambiguous options in command line\n");
+      return EXIT_FAILURE;
+    case NO_DB_FILE:
+      (void)fprintf(stderr, "DB file must be specified\n");
+      return EXIT_FAILURE;
+    default:
+      break;
+    }
+
+  /* Open file for input or output if necessary */
+  if (!d)
+    {
+      if (f)
+        fp = fopen(f, s ? "w" : "r");
+      else fp = s ? stdout : stdin;
+      if (!fp)
+        {
+          (void)fprintf(stderr, "Can't open file %s\n", f);
+          return EXIT_FAILURE;
+        }
+    }
+
+  /* Open the database in appropriate mode */
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+  db = dbopen(argv[optind], s ? O_RDONLY : (O_RDWR | O_CREAT), 0644, DB_HASH, NULL);
+#else
+  db = dbm_open(argv[optind], s ? O_RDONLY : (O_RDWR | O_CREAT), 0644);
+#endif
+  if (!db)
+    {
+      (void)fprintf(stderr, "Cannot open the database %s\n", argv[optind]);
+      return EXIT_FAILURE;
+    }
+
+  ret = EXIT_SUCCESS;
+  if (s == line) /* List database content */
+    {
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+      for (n = 0; !((DB*)db)->seq(db, &key, &value, R_NEXT); n++)
+#else
+      for (n = 0, key = dbm_firstkey(db); key.dptr; key = dbm_nextkey(db), n++)
+#endif
+        {
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+          (void)strncpy(line, key.data, key.size);
+          (void)fprintf(fp, "%s %s\n", line, (char *)value.data);
+#else
+          value = dbm_fetch(db, key);
+          (void)strncpy(line, key.dptr, key.dsize);
+          (void)fprintf(fp, "%s %s\n", line, (char *)value.dptr);
+#endif
+        }
+      (void)fprintf(stderr, "%i records extracted.\n", n);
+    }
+  else if (s) /* Lookup database for specified word */
+    {
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+      key.data = s;
+      key.size = strlen(s);
+      if (((DB*)db)->get(db, &key, &value, 0))
+        ret = EXIT_FAILURE;
+      else (void)fprintf(fp, "%s\n", (char *)value.data);
+#else
+      key.dptr = s;
+      key.dsize = strlen(s)+1;
+      value = dbm_fetch(db, key);
+      if (!value.dptr)
+        ret = EXIT_FAILURE;
+      else (void)fprintf(fp, "%s\n", (char *)value.dptr);
+#endif
+    }
+  else if (d) /* Delete record for specified word */
+    {
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+      key.data = d;
+      key.size = strlen(d);
+      ret = ((DB*)db)->del(db, &key, 0) ? EXIT_FAILURE : EXIT_SUCCESS;
+#else
+      key.dptr = d;
+      key.dsize = strlen(d)+1;
+      dbm_delete(db, key);
+#endif
+    }
+  else /* Fill the database */
+    {
+      for (i = 0, n = 1; fgets(line, 256, fp); n++)
+        {
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+          key.data = strtok(line," ");
+          key.size = strlen(key.data);
+          value.data = strtok(NULL,"\n");
+          value.size = strlen(value.data) + 1;
+          ret = ((DB*)db)->put(db, &key, &value, mode);
+#else
+          key.dptr = strtok(line," ");
+          key.dsize = strlen(key.dptr)+1;
+          value.dptr = strtok(NULL,"\n");
+          value.dsize = strlen(value.dptr) + 1;
+	  ret = dbm_store(db, key, value, mode);
+#endif
+          if (ret < 0)
+            break;
+          else if (ret)
+            {
+              if (!q)
+                (void)fprintf(stderr, "%s:%i: warning: Duplicate entry. Ignored.\n",
+                              f ? f : "stdin", n);
+              i++;
+            }
+        }
+      if (ret < 0)
+        {
+          (void)fprintf(stderr, "%s:%i: error: Database insertion failure. Exiting now.\n",
+                        f ? f : "stdin", n);
+          ret = EXIT_FAILURE;
+        }
+      else
+        {
+          (void)fprintf(stderr, "%i words processed.\n", --n);
+          (void)fprintf(stderr, "%i duplicates ignored.\n", i);
+          (void)fprintf(stderr, "%i records put into the database.\n", n - i);
+          ret = EXIT_SUCCESS;
+        }
+    }
+
+  /* All done */
+#if defined(FBSD_DATABASE) || defined(BERKELEYDB)
+  (void)((DB*)db)->close(db);
+#else
+  dbm_close(db);
+#endif
+  (void)fclose(fp);
+  return ret;
+}
