From b3a9656937b18fae57a5ed53c8528bd1339cfd8d Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Sat, 4 Nov 2017 06:28:24 +0100 Subject: Add menuconfig example/proof of concept Still needs documentation and some cleanup. Interface deliberately kept super simple/clunky to focus on the concepts. Not something you'd actually want to use. Sample session: $ python Kconfiglib/examples/menuconfig.py Kconfiglib/examples/Kmenuconfig ======== Example Kconfig configuration ======== [*] Enable loadable module support (MODULES) Bool and tristate symbols [*] Bool symbol (BOOL) [ ] Dependent bool symbol (BOOL_DEP) < > Dependent tristate symbol (TRI_DEP) [ ] First prompt (TWO_MENU_NODES) < > Tristate symbol (TRI) [ ] Second prompt (TWO_MENU_NODES) *** These are selected by TRI_DEP *** < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) String, int, and hex symbols (foo) String symbol (STRING) (747) Int symbol (INT) (0xABC) Hex symbol (HEX) Various choices -*- Bool choice (BOOL_CHOICE) --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) Bool choice sym 2 (BOOL_CHOICE_SYM_2) {M} Tristate choice (TRI_CHOICE) < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) [ ] Optional bool choice (OPT_BOOL_CHOICE) Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): BOOL Value for BOOL (available: n, y): n ======== Example Kconfig configuration ======== [*] Enable loadable module support (MODULES) Bool and tristate symbols [ ] Bool symbol (BOOL) < > Tristate symbol (TRI) [ ] Second prompt (TWO_MENU_NODES) *** These are selected by TRI_DEP *** < > Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) < > Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) String, int, and hex symbols (foo) String symbol (STRING) (747) Int symbol (INT) (0xABC) Hex symbol (HEX) Various choices -*- Bool choice (BOOL_CHOICE) --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) Bool choice sym 2 (BOOL_CHOICE_SYM_2) {M} Tristate choice (TRI_CHOICE) < > Tristate choice sym 1 (TRI_CHOICE_SYM_1) < > Tristate choice sym 2 (TRI_CHOICE_SYM_2) [ ] Optional bool choice (OPT_BOOL_CHOICE) Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): MODULES Value for MODULES (available: n, y): n ======== Example Kconfig configuration ======== [ ] Enable loadable module support (MODULES) Bool and tristate symbols [ ] Bool symbol (BOOL) [ ] Tristate symbol (TRI) [ ] Second prompt (TWO_MENU_NODES) *** These are selected by TRI_DEP *** [ ] Tristate selected by TRI_DEP (SELECTED_BY_TRI_DEP) [ ] Tristate implied by TRI_DEP (IMPLIED_BY_TRI_DEP) String, int, and hex symbols (foo) String symbol (STRING) (747) Int symbol (INT) (0xABC) Hex symbol (HEX) Various choices -*- Bool choice (BOOL_CHOICE) --> Bool choice sym 1 (BOOL_CHOICE_SYM_1) Bool choice sym 2 (BOOL_CHOICE_SYM_2) -*- Tristate choice (TRI_CHOICE) --> Tristate choice sym 1 (TRI_CHOICE_SYM_1) Tristate choice sym 2 (TRI_CHOICE_SYM_2) [ ] Optional bool choice (OPT_BOOL_CHOICE) Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ^D Unsetting modules demonstrates one reason why it makes sense to have .type be magic and change from TRISTATE to BOOL without modules (<> = tristate, [] = bool). The C implementation uses the same trick. (The original type is still available in .orig_type though.) Piggyback printing of tristates as n, m, y in the warning for invalid values in set_value(). --- examples/Kmenuconfig | 102 +++++++++++++++++++++++++++ examples/menuconfig.py | 186 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 examples/Kmenuconfig create mode 100644 examples/menuconfig.py (limited to 'examples') diff --git a/examples/Kmenuconfig b/examples/Kmenuconfig new file mode 100644 index 0000000..f1cb67b --- /dev/null +++ b/examples/Kmenuconfig @@ -0,0 +1,102 @@ +mainmenu "Example Kconfig configuration" + +config MODULES + bool "Enable loadable module support" + option modules + default y + +menu "Bool and tristate symbols" + +config BOOL + bool "Bool symbol" + default y + +config BOOL_DEP + bool "Dependent bool symbol" + depends on BOOL + +# Mix it up a bit with an 'if' instead of a 'depends on' +if BOOL + +config TRI_DEP + tristate "Dependent tristate symbol" + select SELECTED_BY_TRI_DEP + imply IMPLIED_BY_TRI_DEP + +endif + +config TWO_MENU_NODES + bool "First prompt" + depends on BOOL + +config TRI + tristate "Tristate symbol" + +config TWO_MENU_NODES + bool "Second prompt" + +comment "These are selected by TRI_DEP" + +config SELECTED_BY_TRI_DEP + tristate "Tristate selected by TRI_DEP" + +config IMPLIED_BY_TRI_DEP + tristate "Tristate implied by TRI_DEP" + +endmenu + + +menu "String, int, and hex symbols" + +config STRING + string "String symbol" + default "foo" + +config INT + int "Int symbol" + default 747 + +config HEX + hex "Hex symbol" + default 0xABC + +endmenu + + +menu "Various choices" + +choice BOOL_CHOICE + bool "Bool choice" + +config BOOL_CHOICE_SYM_1 + bool "Bool choice sym 1" + +config BOOL_CHOICE_SYM_2 + bool "Bool choice sym 2" + +endchoice + +choice TRI_CHOICE + tristate "Tristate choice" + +config TRI_CHOICE_SYM_1 + tristate "Tristate choice sym 1" + +config TRI_CHOICE_SYM_2 + tristate "Tristate choice sym 2" + +endchoice + +choice OPT_BOOL_CHOICE + bool "Optional bool choice" + optional + +config OPT_BOOL_CHOICE_SYM_1 + bool "Optional bool choice sym 1" + +config OPT_BOOL_CHOICE_SYM_2 + bool "Optional bool choice sym 2" + +endchoice + +endmenu diff --git a/examples/menuconfig.py b/examples/menuconfig.py new file mode 100644 index 0000000..a77ae2d --- /dev/null +++ b/examples/menuconfig.py @@ -0,0 +1,186 @@ +# TODO: explain, screenshot + +from kconfiglib import Kconfig, \ + Symbol, Choice, MENU, COMMENT, \ + BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \ + expr_value, \ + TRI_TO_STR, STR_TO_TRI +import readline +import sys + +# Python 2/3 compatibility hack +if sys.version_info[0] < 3: + input = raw_input + +def indent_print(s, indent): + print((" " * indent) + s) + +def value_str(sc): + if sc.type in (STRING, INT, HEX): + return "({})".format(sc.str_value) + + # BOOL or TRISTATE + + if isinstance(sc, Symbol) and sc.choice and sc.choice.tri_value == 2: + # For choices in y mode, print '-->' next to the selected symbol + if sc.choice.selection is sc: + return "-->" + return " " + + val_str = {0: " ", 1: "M", 2: "*"}[sc.tri_value] + + if len(sc.assignable) == 1: + return "-{}-".format(val_str) + + if sc.type == BOOL: + return "[{}]".format(val_str) + + if sc.type == TRISTATE: + if sc.assignable[0] == 1: + return "{" + val_str + "}" # Gets a bit confusing with .format() + return "<{}>".format(val_str) + +def node_str(node): + if not node.prompt: + return "" + + prompt, prompt_cond = node.prompt + if not expr_value(prompt_cond): + return "" + + if node.item == MENU: + return " " + prompt + + if node.item == COMMENT: + return " *** {} ***".format(prompt) + + # Symbol or Choice + + sc = node.item + + if sc.type == UNKNOWN: + # Skip symbols defined without a type + return "" + + # {:3} sets the field width to three. Gives nice alignment for empty string + # values. + return "{:3} {} ({})".format(value_str(sc), prompt, sc.name) + +def print_menuconfig_nodes(node, indent): + while node is not None: + string = node_str(node) + if string: + indent_print(string, indent) + + if node.list is not None: + print_menuconfig_nodes(node.list, indent + 8) + + node = node.next + +def print_menuconfig(kconf): + # Print the expanded mainmenu text at the top. This is the same as + # kconf.top_node.prompt[0], but with variable references expanded. + print("\n======== {} ========\n".format(kconf.mainmenu_text)) + + print_menuconfig_nodes(kconf.top_node.list, 0) + print("") + +def get_value_from_user(sc): + if not sc.visibility: + print(sc.name + " is not currently visible") + return False + + prompt = "Value for {}".format(sc.name) + if sc.type in (BOOL, TRISTATE): + prompt += " (available: {})" \ + .format(", ".join([TRI_TO_STR[val] for val in sc.assignable])) + prompt += ": " + + val_str = input(prompt).strip() + if sc.type in (BOOL, TRISTATE): + if val_str not in STR_TO_TRI: + print("'{}' is not a valid tristate value".format(val_str)) + return False + + # I was thinking of having set_value() accept "n", "m", "y" as well as + # a convenience for BOOL / TRISTATE symbols. Consistently using 0, 1, 2 + # makes the format clearer though. That's the best format for + # everything except readability (where it isn't horrible either). + val = STR_TO_TRI[val_str] + else: + val = val_str + + # Automatically add a "0x" prefix for hex symbols, like the menuconfig + # interface does. This isn't done when loading .config files, hence why + # set_value() doesn't do it automatically. + if sc.type == HEX and not val.startswith(("0x", "0X")): + val = "0x" + val + + # Let Kconfiglib itself print a warning here if the value is invalid. We + # could also disable warnings temporarily with + # kconf.disable_warnings() / kconf.enable_warnings() and print our own + # warning. + return sc.set_value(val) + +if __name__ == "__main__": + if len(sys.argv) != 2: + sys.exit("usage: menuconfig.py ") + + # Load Kconfig configuration files + kconf = Kconfig(sys.argv[1]) + + # Print the initial configuration tree + print_menuconfig(kconf) + + while True: + try: + cmd = input('Enter a symbol/choice name, "load_config", or "write_config" (or press CTRL+D to exit): ') \ + .strip() + except EOFError: + print("") + break + + if cmd == "load_config": + config_filename = input(".config file to load: ") + + try: + kconf.load_config(config_filename) + except IOError as e: + # Print the (spammy) error from Kconfiglib itself + print(e.message + "\n") + else: + print("Configuration loaded from " + config_filename) + + print_menuconfig(kconf) + continue + + if cmd == "write_config": + config_filename = input("To this file: ") + + try: + kconf.write_config(config_filename) + except IOError as e: + print(e.message) + else: + print("Configuration written to " + config_filename) + + continue + + # Assume 'cmd' is the name of a symbol or choice if it isn't one of the + # commands above, prompt the user for a value for it, and print the new + # configuration tree + + if cmd in kconf.syms: + if get_value_from_user(kconf.syms[cmd]): + print_menuconfig(kconf) + + continue + + if cmd in kconf.named_choices: + if get_value_from_user(kconf.named_choices[cmd]): + print_menuconfig(kconf) + + continue + + print("No symbol/choice named '{}' in the configuration" + .format(cmd)) -- cgit v1.2.3