/* menu.h */
#ifndef MENU_H
#define MENU_H
#define PROMPT_SIZ 128
#define ALT_SIZ 128
#define MENU_ALTS 11
typedef struct menuitem {
char alt[MENU_ALTS][ALT_SIZ];
char prompt[PROMPT_SIZ];
} menuitem_t;
#ifdef __cplusplus
extern "C" {
#endif
extern int menu(menuitem_t **items, char *selection, size_t limit);
extern menuitem_t **extract_menu_items(const char *options);
extern void destroy_menu_items(menuitem_t **items);
#ifdef __cplusplus
}
#endif
#endif
/* menu.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "menu.h"
/*
@description:
Displays the prompts for a collection of menu items and gathers an interactive selection.
@returns: True if a matching selection was detected, false otherwise.
*/
extern int menu(menuitem_t **items, char *selection, size_t limit)
{
size_t i, j;
if (items) {
/* Display the prompts */
for (i = 0; items[i]; i++)
puts(items[i]->prompt);
fputs("> ", stdout);
fflush(stdout);
/* Process an interactive selection */
if (fgets(selection, limit, stdin)) {
char *newline = strchr(selection, '\n');
if (newline)
*newline = '\0'; /* Trim the newline for convenience */
else {
int ch;
/* Discard extraneous line characters */
do
ch = fgetc(stdin);
while (ch != '\n' && ch != EOF);
clearerr(stdin);
}
for (i = 0; items[i]; i++) {
/* Check all of the alternatives for a matching prefix */
for (j = 0; j < MENU_ALTS && items[i]->alt[j][0]; j++) {
size_t alt_len = strlen(items[i]->alt[j]);
/* If the first part of the string matches an alternative, it's valid */
if (strncmp(selection, items[i]->alt[j], alt_len) == 0)
return 1;
}
}
}
}
return 0; /* No matches, end-of-file, or stream error */
}
/*
@description:
Parses and extracts a collection of menu items from a formatted options string.
The options string shall be in the following format:
<prompt1>;<prompt1.match1>|<prompt1.match2>;<prompt2>;<prompt2.match1>|<prompt2.match2>...
The prompt may not be an empty field, but the match strings may be empty.
Example 1 (prompts and matches): "A) Option A;A|a;B) Option B;B|b;Q) Quit;Q|q"
Example 2 (prompts and no matches): "A) Option A;;B) Option B;;Q) Quit;"
@remarks:
Memory is dynamically allocated for the item collection. destroy_menu_items
is provided as a convenience function for releasing this memory.
*/
extern menuitem_t **extract_menu_items(const char *options)
{
menuitem_t **mem;
/* Always allocate at least one spot for a trailing null pointer */
mem = (menuitem_t**)malloc(sizeof *mem);
if (mem) {
char copy[BUFSIZ] = "";
size_t args = 0;
char *tok;
strcat(copy, options);
tok = strtok(copy, ";");
while (tok) {
size_t tok_len = strlen(tok);
menuitem_t **temp;
mem[args] = (menuitem_t*)malloc(sizeof *mem[args]);
if (!mem[args])
break;
/* Assume the first hit is a prompt */
strcpy(mem[args]->prompt, tok);
/* Assume the second hit is the alt list */
if (tok[tok_len + 1] == ';') {
/*
Kind of tricky. Introspectively looked ahead because strtok won't,
and handle an empty alt list field by setting the first alt to "".
*/
mem[args]->alt[0][0] = '\0';
}
else {
char *alt_tok;
size_t n = 0;
/* Not an empty alt list, so we can grab the token with strtok */
tok = strtok(NULL, ";");
tok_len = strlen(tok);
/*
Now reuse strtok to process the alt list (awkward).
*/
alt_tok = strtok(tok, "|");
while (n < MENU_ALTS - 1 && alt_tok) {
strcpy(mem[args]->alt[n++], alt_tok);
alt_tok = strtok(NULL, "|");
}
/* Make sure to terminate the alt list with a blank string */
mem[args]->alt[n][0] = '\0';
}
/* Make room for the next menu item (or null pointer if we're done) */
temp = (menuitem_t**)realloc(mem, (++args + 1) * sizeof *temp);
if (!temp)
break;
mem = temp;
/*
Restart strtok at the next option token because we
may have used strtok to handle a non-empty alt list.
*/
tok = strtok(tok + tok_len + 1, ";");
}
mem[args] = NULL;
}
return mem;
}
/*
@description:
Releases memory for a collection of menu items returned by extract_menu_items.
*/
extern void destroy_menu_items(menuitem_t **items)
{
size_t i;
for (i = 0; items[i]; i++)
free(items[i]);
free(items);
}
/* main.c (sample usage) */
#include <ctype.h>
#include <stdio.h>
#include "menu.h"
int main(void)
{
menuitem_t **items = extract_menu_items("A) Option A;A|a;B) Option B;B|b;Q) Quit;Q|q");
int done = 0;
while (!done) {
char selection[BUFSIZ];
int rc = menu(items, selection, sizeof selection);
printf("Full selection: '%s'\n", selection);
if (!rc)
puts("Invalid selection");
else {
switch (toupper(selection[0])) {
case 'A':
puts("You chose option A");
break;
case 'B':
puts("You chose option B");
break;
case 'Q':
puts("You chose to quit");
done = 1;
break;
}
}
}
destroy_menu_items(items);
return 0;
}
Programming takes discipline. Good programming takes a lot of discipline, a large number of principles, and standard, defensive ways of doing things right. No programming technique solves all problems. No programming language produces only correct results. No programmer should start each project from scratch
Saturday, February 16, 2013
Simple interactive menu
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment