Patchwork [U-Boot,v3,1/7] Add generic, reusable menu code

login
register
mail settings
Submitter Jason Hobbs
Date June 29, 2011, 4:25 p.m.
Message ID <1309364719-16219-2-git-send-email-jason.hobbs@calxeda.com>
Download mbox | patch
Permalink /patch/102625/
State Deferred
Headers show

Comments

Jason Hobbs - June 29, 2011, 4:25 p.m.
This will be used first by the pxecfg code, but is intended to be
generic and reusable for other jobs in U-boot.

Signed-off-by: Jason Hobbs <jason.hobbs@calxeda.com>
---
changes in v2:
 - new in v2

changes in v3:
 - move timeout support to later patch
 - fix NULL case bug in menu_item_key_match
 - consistently use 'item_key' instead of 'key'
 - move default/prompt logic into menu code
 - consistently return int for error propagation
 - change option setting functions to menu_create paramaters
 - add README.menu

 common/Makefile |    1 +
 common/menu.c   |  266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 doc/README.menu |  158 ++++++++++++++++++++++++++++++++
 include/menu.h  |   30 ++++++
 4 files changed, 455 insertions(+), 0 deletions(-)
 create mode 100644 common/menu.c
 create mode 100644 doc/README.menu
 create mode 100644 include/menu.h
Wolfgang Denk - July 25, 2011, 9:18 p.m.
Dear "Jason Hobbs",

In message <1309364719-16219-2-git-send-email-jason.hobbs@calxeda.com> you wrote:
> This will be used first by the pxecfg code, but is intended to be
> generic and reusable for other jobs in U-boot.
> 
> Signed-off-by: Jason Hobbs <jason.hobbs@calxeda.com>
> ---
> changes in v2:
>  - new in v2
> 
> changes in v3:
>  - move timeout support to later patch
>  - fix NULL case bug in menu_item_key_match
>  - consistently use 'item_key' instead of 'key'
>  - move default/prompt logic into menu code
>  - consistently return int for error propagation
>  - change option setting functions to menu_create paramaters
>  - add README.menu
> 
>  common/Makefile |    1 +
>  common/menu.c   |  266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  doc/README.menu |  158 ++++++++++++++++++++++++++++++++
>  include/menu.h  |   30 ++++++
>  4 files changed, 455 insertions(+), 0 deletions(-)
>  create mode 100644 common/menu.c
>  create mode 100644 doc/README.menu
>  create mode 100644 include/menu.h

I am happy that you provide documentation in doc/README.menu, but I
really dislike that the code itself is basicly uncommented. It is a
major pain to have to switch between the README and the source files
when trying to understand the code.

Please add sufficient comments to the code to make it readable.

Thanks.

Best regards,

Wolfgang Denk
Jason Hobbs - July 26, 2011, 9:43 p.m.
Dear Wolfgang,

On Mon, Jul 25, 2011 at 11:18:15PM +0200, Wolfgang Denk wrote:
> I am happy that you provide documentation in doc/README.menu, but I
> really dislike that the code itself is basicly uncommented. It is a
> major pain to have to switch between the README and the source files
> when trying to understand the code.
> 
> Please add sufficient comments to the code to make it readable.

I don't want to duplicate the interface documentation in the README and
the source file.  Maybe I'll add shorter summary descriptions to the
README and move the detailed interface doc into the source file.  I'll
also add some comments for describing the behavior of the internals.

Thanks,
Jason

Patch

diff --git a/common/Makefile b/common/Makefile
index 224b7cc..d5bd983 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -170,6 +170,7 @@  COBJS-$(CONFIG_CMD_KGDB) += kgdb.o kgdb_stubs.o
 COBJS-$(CONFIG_KALLSYMS) += kallsyms.o
 COBJS-$(CONFIG_LCD) += lcd.o
 COBJS-$(CONFIG_LYNXKDI) += lynxkdi.o
+COBJS-$(CONFIG_MENU) += menu.o
 COBJS-$(CONFIG_MODEM_SUPPORT) += modem.o
 COBJS-$(CONFIG_UPDATE_TFTP) += update.o
 COBJS-$(CONFIG_USB_KEYBOARD) += usb_kbd.o
diff --git a/common/menu.c b/common/menu.c
new file mode 100644
index 0000000..9bcd906
--- /dev/null
+++ b/common/menu.c
@@ -0,0 +1,266 @@ 
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ *
+ * 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 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <errno.h>
+#include <linux/list.h>
+
+#include "menu.h"
+
+struct menu_item {
+	char *key;
+	void *data;
+	struct list_head list;
+};
+
+struct menu {
+	struct menu_item *default_item;
+	char *title;
+	int prompt;
+	void (*item_data_print)(void *);
+	struct list_head items;
+};
+
+static inline void *menu_items_iter(struct menu *m,
+		void *(*callback)(struct menu *, struct menu_item *, void *),
+		void *extra)
+{
+	struct list_head *pos, *n;
+	struct menu_item *item;
+	void *ret;
+
+	list_for_each_safe(pos, n, &m->items) {
+		item = list_entry(pos, struct menu_item, list);
+
+		ret = callback(m, item, extra);
+
+		if (ret)
+			return ret;
+	}
+
+	return NULL;
+}
+
+static inline void *menu_item_print(struct menu *m,
+				struct menu_item *item,
+				void *extra)
+{
+	if (!m->item_data_print)
+		printf("%s\n", item->key);
+	else
+		m->item_data_print(item->data);
+
+	return NULL;
+}
+
+static inline void *menu_item_destroy(struct menu *m,
+				struct menu_item *item,
+				void *extra)
+{
+	if (item->key)
+		free(item->key);
+
+	free(item);
+
+	return NULL;
+}
+
+static inline void menu_display(struct menu *m)
+{
+	if (m->title)
+		printf("%s:\n", m->title);
+
+	menu_items_iter(m, menu_item_print, NULL);
+}
+
+static inline void *menu_item_key_match(struct menu *m,
+				struct menu_item *item,
+				void *extra)
+{
+	char *item_key = extra;
+
+	if (!item_key || !item->key) {
+		if (item_key == item->key)
+			return item;
+
+		return NULL;
+	}
+
+	if (strcmp(item->key, item_key) == 0)
+		return item;
+
+	return NULL;
+}
+
+static inline struct menu_item *menu_item_by_key(struct menu *m,
+							char *item_key)
+{
+	return menu_items_iter(m, menu_item_key_match, item_key);
+}
+
+static inline int menu_use_default(struct menu *m)
+{
+	return !m->prompt;
+}
+
+static inline int menu_default_choice(struct menu *m, void **choice)
+{
+	if (m->default_item) {
+		*choice = m->default_item->data;
+		return 1;
+	}
+
+	return -ENOENT;
+}
+
+static inline int menu_interactive_choice(struct menu *m, void **choice)
+{
+	char cbuf[CONFIG_SYS_CBSIZE];
+	struct menu_item *choice_item = NULL;
+	int readret;
+
+	while (!choice_item) {
+		cbuf[0] = '\0';
+
+		menu_display(m);
+
+		readret = readline_into_buffer("Enter choice: ", cbuf);
+
+		if (readret >= 0) {
+			choice_item = menu_item_by_key(m, cbuf);
+
+			if (!choice_item)
+				printf("%s not found\n", cbuf);
+		} else {
+			printf("^C\n");
+			return -EINTR;
+		}
+	}
+
+	*choice = choice_item->data;
+
+	return 1;
+}
+
+int menu_default_set(struct menu *m, char *item_key)
+{
+	struct menu_item *item;
+
+	if (!m)
+		return -EINVAL;
+
+	item = menu_item_by_key(m, item_key);
+
+	if (!item)
+		return -ENOENT;
+
+	m->default_item = item;
+
+	return 1;
+}
+
+int menu_get_choice(struct menu *m, void **choice)
+{
+	if (!m || !choice)
+		return -EINVAL;
+
+	if (menu_use_default(m))
+		return menu_default_choice(m, choice);
+
+	return menu_interactive_choice(m, choice);
+}
+
+/*
+ * note that this replaces the data of an item if it already exists, but
+ * doesn't change the order of the item.
+ */
+int menu_item_add(struct menu *m, char *item_key, void *item_data)
+{
+	struct menu_item *item;
+
+	if (!m)
+		return -EINVAL;
+
+	item = menu_item_by_key(m, item_key);
+
+	if (item) {
+		item->data = item_data;
+		return 1;
+	}
+
+	item = malloc(sizeof *item);
+	if (!item)
+		return -ENOMEM;
+
+	item->key = strdup(item_key);
+
+	if (!item->key) {
+		free(item);
+		return -ENOMEM;
+	}
+
+	item->data = item_data;
+
+	list_add_tail(&item->list, &m->items);
+
+	return 1;
+}
+
+struct menu *menu_create(char *title, int prompt,
+				void (*item_data_print)(void *))
+{
+	struct menu *m;
+
+	m = malloc(sizeof *m);
+
+	if (!m)
+		return NULL;
+
+	m->default_item = NULL;
+	m->prompt = prompt;
+	m->item_data_print = item_data_print;
+
+	if (title) {
+		m->title = strdup(title);
+		if (!m->title) {
+			free(m);
+			return NULL;
+		}
+	} else
+		m->title = NULL;
+
+
+	INIT_LIST_HEAD(&m->items);
+
+	return m;
+}
+
+int menu_destroy(struct menu *m)
+{
+	if (!m)
+		return -EINVAL;
+
+	menu_items_iter(m, menu_item_destroy, NULL);
+
+	if (m->title)
+		free(m->title);
+
+	free(m);
+
+	return 1;
+}
diff --git a/doc/README.menu b/doc/README.menu
new file mode 100644
index 0000000..bca44be
--- /dev/null
+++ b/doc/README.menu
@@ -0,0 +1,158 @@ 
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ *
+ * 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 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+U-boot provides a set of interfaces for creating and using simple, text
+based menus. Menus are displayed as lists of labeled entries on the
+console, and an entry can be selected by entering its label.
+
+To use the menu code, enable CONFIG_MENU, and include "menu.h" where
+the interfaces should be available.
+
+Menus are composed of items. Each item has a key used to identify it in
+the menu, and an opaque pointer to data controlled by the consumer.
+
+Interfaces
+----------
+#include "menu.h"
+
+struct menu;
+
+struct menu *menu_create(char *title, int prompt,
+				void (*item_data_print)(void *));
+int menu_item_add(struct menu *m, char *item_key, void *item_data);
+int menu_default_set(struct menu *m, char *item_key);
+int menu_get_choice(struct menu *m, void **choice);
+int menu_destroy(struct menu *m);
+
+menu_create() - Creates a menu handle with default settings
+
+  title - If not NULL, points to a string that will be displayed before
+  the list of menu items. It will be copied to internal storage, and is
+  safe to discard after passing to menu_create().
+
+  prompt - If 0, don't ask for user input. If 1, the user will be ed for
+  promptinput.
+
+  item_data_print - If not NULL, will be called for each item when
+  the menu is displayed, with the pointer to the item's data passed
+  as the argument. If NULL, each item's key will be printed instead.
+  Since an item's key is what must be entered to select an item, the
+  item_data_print function should make it obvious what the key for each
+  entry is.
+
+  Returns a pointer to the menu if successful, or NULL if there is
+  insufficient memory available to create the menu.
+
+
+menu_item_add() - Adds or replaces a menu item
+
+  m - Points to a menu created by menu_create().
+
+  item_key - Points to a string that will uniquely identify the item.
+  The string will be copied to internal storage, and is safe to discard
+  after passing to menu_item_add.
+
+  item_data - An opaque pointer associated with an item. It is never
+  dereferenced internally, but will be passed to the item_data_print,
+  and will be returned from menu_get_choice if the menu item is
+  selected.
+
+  Returns 1 if successful, -EINVAL if m is NULL, or -ENOMEM if there is
+  insufficient memory to add the menu item.
+
+
+menu_default_set() - Sets the default choice for the menu. This is safe
+to call more than once.
+
+  m - Points to a menu created by menu_create().
+
+  item_key - Points to a string that, when compared using strcmp,
+  matches the key for an existing item in the menu.
+
+  Returns 1 if successful, -EINVAL if m is NULL, or -ENOENT if no item
+  with a key matching item_key is found.
+
+
+menu_get_choice() - Returns the user's selected menu entry, or the
+default if the menu is set to not prompt. This is safe to call more than
+once.
+
+  m - Points to a menu created by menu_create().
+
+  choice - Points to a location that will store a pointer to the
+  selected menu item. If no item is selected or there is an error, no
+  value will be written at the location it points to.
+
+  Returns 1 if successful, -EINVAL if m or choice is NULL, -ENOENT if no
+  default has been set and the menu is set to not prompt, or -EINTR if
+  the user exits the menu via ctrl+c.
+
+
+menu_destroy() - frees the memory used by a menu and its items.
+
+  m - Points to a menu created by menu_create().
+
+  Returns 1 if successful, or -EINVAL if m is NULL.
+
+
+Example
+-------
+This example creates a menu that always prompts, and allows the user
+to pick from a list of tools.  The item key and data are the same.
+
+#include "menu.h"
+
+char *tools[] = {
+	"Hammer",
+	"Screwdriver",
+	"Nail gun",
+	NULL
+};
+
+char *pick_a_tool(void)
+{
+	struct menu *m;
+	int i;
+	char *tool = NULL;
+
+	m = menu_create("Tools", 1, NULL);
+
+	for(i = 0; tools[i]; i++) {
+		if (menu_item_add(m, tools[i], tools[i]) != 1) {
+			printf("failed to add item!");
+			menu_destroy(m);
+			return NULL;
+                }
+	}
+
+	if (menu_get_choice(m, (void **)&tool) != 1)
+		printf("Problem picking tool!\n");
+
+	menu_destroy(m);
+
+	return tool;
+}
+
+void caller(void)
+{
+	char *tool = pick_a_tool();
+
+	if (tool) {
+		printf("picked a tool: %s\n", tool);
+		use_tool(tool);
+	}
+}
diff --git a/include/menu.h b/include/menu.h
new file mode 100644
index 0000000..d47e1a0
--- /dev/null
+++ b/include/menu.h
@@ -0,0 +1,30 @@ 
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ *
+ * 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 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MENU_H__
+#define __MENU_H__
+
+struct menu;
+
+struct menu *menu_create(char *title, int prompt,
+				void (*item_data_print)(void *));
+int menu_default_set(struct menu *m, char *item_key);
+int menu_get_choice(struct menu *m, void **choice);
+int menu_item_add(struct menu *m, char *item_key, void *item_data);
+int menu_destroy(struct menu *m);
+
+#endif /* __MENU_H__ */