summaryrefslogtreecommitdiff
path: root/examples/menuconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/menuconfig.py')
-rw-r--r--examples/menuconfig.py348
1 files changed, 348 insertions, 0 deletions
diff --git a/examples/menuconfig.py b/examples/menuconfig.py
new file mode 100644
index 0000000..375b8c8
--- /dev/null
+++ b/examples/menuconfig.py
@@ -0,0 +1,348 @@
+# Implements a simple configuration interface on top of Kconfiglib to
+# demonstrate concepts for building a menuconfig-like. Emulates how the
+# standard menuconfig prints menu entries.
+#
+# Always displays the entire Kconfig tree to keep things as simple as possible
+# (all symbols, choices, menus, and comments).
+#
+# Usage:
+#
+# $ python(3) Kconfiglib/examples/menuconfig.py <Kconfig file>
+#
+# A sample Kconfig is available in Kconfiglib/examples/Kmenuconfig.
+#
+# Here's a notation guide. The notation matches the one used by menuconfig
+# (scripts/kconfig/mconf):
+#
+# [ ] prompt - Bool
+# < > prompt - Tristate
+# {M} prompt - Tristate selected to m. Can only be set to m or y.
+# -*- prompt - Bool/tristate selected to y, pinning it
+# -M- prompt - Tristate selected to m that also has m visibility,
+# pinning it to m
+# (foo) prompt - String/int/hex symbol with value "foo"
+# --> prompt - The selected symbol in a choice in y mode. This
+# syntax is unique to this example.
+#
+# When modules are disabled, the .type attribute of TRISTATE symbols and
+# choices automatically changes to BOOL. This trick is used by the C
+# implementation as well, and gives the expected behavior without having to do
+# anything extra here. The original type is available in .orig_type if needed.
+#
+# The Kconfiglib/examples/Kmenuconfig example uses named choices to be able to
+# refer to choices by name. Named choices are supported in the C tools too, but
+# I don't think I've ever seen them used in the wild.
+#
+# 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
+
+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):
+ """
+ Returns the value part ("[*]", "<M>", "(foo)" etc.) of a menu entry.
+
+ sc: Symbol or Choice.
+ """
+ if sc.type in (STRING, INT, HEX):
+ return "({})".format(sc.str_value)
+
+ # BOOL or TRISTATE
+
+ # The choice mode acts as an upper bound on the visibility of choice
+ # symbols, so we can check the choice symbols' own visibility to see if the
+ # choice is in y mode
+ if isinstance(sc, Symbol) and sc.choice and sc.visibility == 2:
+ # For choices in y mode, print '-->' next to the selected symbol
+ if sc.choice.selection is sc:
+ return "-->"
+ return " "
+
+ tri_val_str = {0: " ", 1: "M", 2: "*"}[sc.tri_value]
+
+ if len(sc.assignable) == 1:
+ # Pinned to a single value
+ return "-{}-".format(tri_val_str)
+
+ if sc.type == BOOL:
+ return "[{}]".format(tri_val_str)
+
+ if sc.type == TRISTATE:
+ if sc.assignable == (1, 2):
+ # m and y available
+ return "{" + tri_val_str + "}" # Gets a bit confusing with .format()
+ return "<{}>".format(tri_val_str)
+
+def node_str(node):
+ """
+ Returns the complete menu entry text for a menu node, or "" for invisible
+ menu nodes. Invisible menu nodes are those that lack a prompt or don't have
+ a satisfied prompt condition.
+
+ Example return value: "[*] Bool symbol (BOOL)"
+
+ The symbol name is printed in parentheses to the right of the prompt. This
+ is so that symbols can easily be referred to in the configuration
+ interface.
+ """
+ if not node.prompt:
+ return ""
+
+ # Even for menu nodes for symbols and choices, it's wrong to check
+ # Symbol.visibility / Choice.visibility here. The reason is that a symbol
+ # (and a choice, in theory) can be defined in multiple locations, giving it
+ # multiple menu nodes, which do not necessarily all have the same prompt
+ # visibility. Symbol.visibility / Choice.visibility is calculated as the OR
+ # of the visibility of all the prompts.
+ 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.
+ res = "{:3} {}".format(value_str(sc), prompt)
+
+ # Don't print the name for unnamed choices (the normal kind)
+ if sc.name is not None:
+ res += " ({})".format(sc.name)
+
+ return res
+
+def print_menuconfig_nodes(node, indent):
+ """
+ Prints a tree with all the menu entries rooted at node. Child menu entries
+ are indented.
+ """
+ while node:
+ string = node_str(node)
+ if string:
+ indent_print(string, indent)
+
+ if node.list:
+ print_menuconfig_nodes(node.list, indent + 8)
+
+ node = node.next
+
+def print_menuconfig(kconf):
+ """
+ Prints all menu entries for the configuration.
+ """
+ # 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):
+ """
+ Prompts the user for a value for the symbol or choice 'sc'. For
+ bool/tristate symbols and choices, provides a list of all the assignable
+ values.
+ """
+ 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 in all ways
+ # except for 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 <Kconfig file>")
+
+ # 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))