Some improvements to random_word
[cmccabe-bin] / random_word.c
diff --git a/random_word.c b/random_word.c
new file mode 100644 (file)
index 0000000..f29bc7d
--- /dev/null
@@ -0,0 +1,149 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define DEFAULT_DICT_PATH "/usr/share/dict/linux.words"
+#define STARTING_SZ 8192
+#define MAX_ARRAY_SZ 1073741824
+
+struct dict {
+       int num_words;
+       int array_sz;
+       char *words[0];
+};
+
+static int alloc_dict(struct dict **d, int array_sz)
+{
+       struct dict *nd;
+       int num_bytes;
+       if (array_sz > MAX_ARRAY_SZ) {
+               fprintf(stderr, "can't allocate a words array bigger "
+                       "than %d\n", MAX_ARRAY_SZ);
+               return EINVAL;
+       }
+
+       num_bytes = sizeof(struct dict) + (array_sz * sizeof(char*));
+       nd = realloc(*d, num_bytes);
+       if (!nd) {
+               fprintf(stderr, "failed to realloc to size %d\n",
+                       num_bytes);
+               return ENOSPC;
+       }
+       *d = nd;
+
+       nd->array_sz = array_sz;
+       return 0;
+}
+
+static struct dict* read_dict(FILE *fp)
+{
+       char word[500];
+       struct dict *d =
+               malloc(sizeof(struct dict) + (STARTING_SZ * sizeof(char*)));
+       if (! d) {
+               fprintf(stderr, "failed to allocate dict\n");
+               return NULL;
+       }
+       d->num_words = 0;
+       d->array_sz = STARTING_SZ;
+
+       while (1) {
+               if (! fgets(word, sizeof(word), fp)) {
+                       if (ferror(fp)) {
+                               int err = errno;
+                               fprintf(stderr, "%s: error reading line %d: "
+                                       "%s (%d)\n",
+                                       __func__, d->num_words + 1,
+                                       strerror(err), err);
+                       }
+                       else {
+                               return d;
+                       }
+               }
+               d->words[d->num_words] = strdup(word);
+               if (!d->words[d->num_words]) {
+                       fprintf(stderr, "failed to allocate word %d\n",
+                               d->num_words);
+                       free(d);
+                       return NULL;
+               }
+               d->num_words++;
+               if (d->num_words >= d->array_sz) {
+                       if (alloc_dict(&d, d->array_sz * 2)) {
+                               free(d);
+                               return NULL;
+                       }
+               }
+       }
+}
+
+static const char* choose_random_word(struct dict *dict)
+{
+       int choice = random() % dict->num_words;
+
+       return dict->words[choice];
+}
+
+static void print_usage(char *program_name)
+{
+       printf("%s: prints a random word from a linebreak-delimited file.\n",
+              program_name);
+       printf("options:\n");
+       printf("\t-d: the dictionary file to use.  Default: %s.\n",
+              DEFAULT_DICT_PATH);
+       printf("\t-h: this help message.\n");
+}
+
+static void parse_args(char **argv, int argc, char **dict_path)
+{
+       char c;
+       *dict_path = DEFAULT_DICT_PATH;
+       opterr = 0;
+       while ((c = getopt(argc, argv, "d:h")) != -1) {
+               switch (c) {
+               case 'd':
+                       *dict_path = optarg;
+                       break;
+               case 'h':
+                       print_usage(argv[0]);
+                       exit(0);
+                       break;
+               default:
+                       fprintf (stderr, "Argument parsing error.\n");
+                       exit(1);
+               }
+       }
+}
+
+int main(int argc, char **argv)
+{
+       FILE *fp;
+       char *dict_path;
+       const char *word;
+       struct dict *dict;
+       struct timeval tv;
+
+       parse_args(argv, argc, &dict_path);
+       gettimeofday(&tv, NULL);
+       srandom(tv.tv_usec * tv.tv_sec);
+
+       fp = fopen(dict_path, "r");
+       if (! fp) {
+               int err = errno;
+               fprintf(stderr, "failed to open %s: %s (%d).\n",
+                       dict_path, strerror(err), err);
+               return 1;
+       }
+       dict = read_dict(fp);
+       if (! dict)
+               return 1;
+       fclose(fp);
+
+       word = choose_random_word(dict);
+       fputs(word, stdout); 
+
+       return 0;
+}