From 0d5b05c704d1e15de2ce075f70d0de9f29ba1386 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 18 Nov 2020 20:26:58 -0500 Subject: lib: Add kconfiglib code Signed-off-by: Kevin O'Connor --- lib/kconfiglib/menuconfig.py | 3278 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3278 insertions(+) create mode 100755 lib/kconfiglib/menuconfig.py (limited to 'lib/kconfiglib/menuconfig.py') diff --git a/lib/kconfiglib/menuconfig.py b/lib/kconfiglib/menuconfig.py new file mode 100755 index 00000000..7e765d36 --- /dev/null +++ b/lib/kconfiglib/menuconfig.py @@ -0,0 +1,3278 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson +# SPDX-License-Identifier: ISC + +""" +Overview +======== + +A curses-based Python 2/3 menuconfig implementation. The interface should feel +familiar to people used to mconf ('make menuconfig'). + +Supports the same keys as mconf, and also supports a set of keybindings +inspired by Vi: + + J/K : Down/Up + L : Enter menu/Toggle item + H : Leave menu + Ctrl-D/U: Page Down/Page Up + G/End : Jump to end of list + g/Home : Jump to beginning of list + +[Space] toggles values if possible, and enters menus otherwise. [Enter] works +the other way around. + +The mconf feature where pressing a key jumps to a menu entry with that +character in it in the current menu isn't supported. A jump-to feature for +jumping directly to any symbol (including invisible symbols), choice, menu or +comment (as in a Kconfig 'comment "Foo"') is available instead. + +A few different modes are available: + + F: Toggle show-help mode, which shows the help text of the currently selected + item in the window at the bottom of the menu display. This is handy when + browsing through options. + + C: Toggle show-name mode, which shows the symbol name before each symbol menu + entry + + A: Toggle show-all mode, which shows all items, including currently invisible + items and items that lack a prompt. Invisible items are drawn in a different + style to make them stand out. + + +Running +======= + +menuconfig.py can be run either as a standalone executable or by calling the +menuconfig() function with an existing Kconfig instance. The second option is a +bit inflexible in that it will still load and save .config, etc. + +When run in standalone mode, the top-level Kconfig file to load can be passed +as a command-line argument. With no argument, it defaults to "Kconfig". + +The KCONFIG_CONFIG environment variable specifies the .config file to load (if +it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used. + +When overwriting a configuration file, the old version is saved to +.old (e.g. .config.old). + +$srctree is supported through Kconfiglib. + + +Color schemes +============= + +It is possible to customize the color scheme by setting the MENUCONFIG_STYLE +environment variable. For example, setting it to 'aquatic' will enable an +alternative, less yellow, more 'make menuconfig'-like color scheme, contributed +by Mitja Horvat (pinkfluid). + +This is the current list of built-in styles: + - default classic Kconfiglib theme with a yellow accent + - monochrome colorless theme (uses only bold and standout) attributes, + this style is used if the terminal doesn't support colors + - aquatic blue-tinted style loosely resembling the lxdialog theme + +It is possible to customize the current style by changing colors of UI +elements on the screen. This is the list of elements that can be stylized: + + - path Top row in the main display, with the menu path + - separator Separator lines between windows. Also used for the top line + in the symbol information display. + - list List of items, e.g. the main display + - selection Style for the selected item + - inv-list Like list, but for invisible items. Used in show-all mode. + - inv-selection Like selection, but for invisible items. Used in show-all + mode. + - help Help text windows at the bottom of various fullscreen + dialogs + - show-help Window showing the help text in show-help mode + - frame Frame around dialog boxes + - body Body of dialog boxes + - edit Edit box in pop-up dialogs + - jump-edit Edit box in jump-to dialog + - text Symbol information text + +The color definition is a comma separated list of attributes: + + - fg:COLOR Set the foreground/background colors. COLOR can be one of + * or * the basic 16 colors (black, red, green, yellow, blue, + - bg:COLOR magenta, cyan, white and brighter versions, for example, + brightred). On terminals that support more than 8 colors, + you can also directly put in a color number, e.g. fg:123 + (hexadecimal and octal constants are accepted as well). + Colors outside the range -1..curses.COLORS-1 (which is + terminal-dependent) are ignored (with a warning). The COLOR + can be also specified using a RGB value in the HTML + notation, for example #RRGGBB. If the terminal supports + color changing, the color is rendered accurately. + Otherwise, the visually nearest color is used. + + If the background or foreground color of an element is not + specified, it defaults to -1, representing the default + terminal foreground or background color. + + Note: On some terminals a bright version of the color + implies bold. + - bold Use bold text + - underline Use underline text + - standout Standout text attribute (reverse color) + +More often than not, some UI elements share the same color definition. In such +cases the right value may specify an UI element from which the color definition +will be copied. For example, "separator=help" will apply the current color +definition for "help" to "separator". + +A keyword without the '=' is assumed to be a style template. The template name +is looked up in the built-in styles list and the style definition is expanded +in-place. With this, built-in styles can be used as basis for new styles. + +For example, take the aquatic theme and give it a red selection bar: + +MENUCONFIG_STYLE="aquatic selection=fg:white,bg:red" + +If there's an error in the style definition or if a missing style is assigned +to, the assignment will be ignored, along with a warning being printed on +stderr. + +The 'default' theme is always implicitly parsed first, so the following two +settings have the same effect: + + MENUCONFIG_STYLE="selection=fg:white,bg:red" + MENUCONFIG_STYLE="default selection=fg:white,bg:red" + +If the terminal doesn't support colors, the 'monochrome' theme is used, and +MENUCONFIG_STYLE is ignored. The assumption is that the environment is broken +somehow, and that the important thing is to get something usable. + + +Other features +============== + + - Seamless terminal resizing + + - No dependencies on *nix, as the 'curses' module is in the Python standard + library + + - Unicode text entry + + - Improved information screen compared to mconf: + + * Expressions are split up by their top-level &&/|| operands to improve + readability + + * Undefined symbols in expressions are pointed out + + * Menus and comments have information displays + + * Kconfig definitions are printed + + * The include path is shown, listing the locations of the 'source' + statements that included the Kconfig file of the symbol (or other + item) + + +Limitations +=========== + +Doesn't work out of the box on Windows, but can be made to work with + + pip install windows-curses + +See the https://github.com/zephyrproject-rtos/windows-curses repository. +""" +from __future__ import print_function + +import os +import sys + +_IS_WINDOWS = os.name == "nt" # Are we running on Windows? + +try: + import curses +except ImportError as e: + if not _IS_WINDOWS: + raise + sys.exit("""\ +menuconfig failed to import the standard Python 'curses' library. Try +installing a package like windows-curses +(https://github.com/zephyrproject-rtos/windows-curses) by running this command +in cmd.exe: + + pip install windows-curses + +Starting with Kconfiglib 13.0.0, windows-curses is no longer automatically +installed when installing Kconfiglib via pip on Windows (because it breaks +installation on MSYS2). + +Exception: +{}: {}""".format(type(e).__name__, e)) + +import errno +import locale +import re +import textwrap + +from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \ + BOOL, TRISTATE, STRING, INT, HEX, \ + AND, OR, \ + expr_str, expr_value, split_expr, \ + standard_sc_expr_str, \ + TRI_TO_STR, TYPE_TO_STR, \ + standard_kconfig, standard_config_filename + + +# +# Configuration variables +# + +# If True, try to change LC_CTYPE to a UTF-8 locale if it is set to the C +# locale (which implies ASCII). This fixes curses Unicode I/O issues on systems +# with bad defaults. ncurses configures itself from the locale settings. +# +# Related PEP: https://www.python.org/dev/peps/pep-0538/ +_CHANGE_C_LC_CTYPE_TO_UTF8 = True + +# How many steps an implicit submenu will be indented. Implicit submenus are +# created when an item depends on the symbol before it. Note that symbols +# defined with 'menuconfig' create a separate menu instead of indenting. +_SUBMENU_INDENT = 4 + +# Number of steps for Page Up/Down to jump +_PG_JUMP = 6 + +# Height of the help window in show-help mode +_SHOW_HELP_HEIGHT = 8 + +# How far the cursor needs to be from the edge of the window before it starts +# to scroll. Used for the main menu display, the information display, the +# search display, and for text boxes. +_SCROLL_OFFSET = 5 + +# Minimum width of dialogs that ask for text input +_INPUT_DIALOG_MIN_WIDTH = 30 + +# Number of arrows pointing up/down to draw when a window is scrolled +_N_SCROLL_ARROWS = 14 + +# Lines of help text shown at the bottom of the "main" display +_MAIN_HELP_LINES = """ +[Space/Enter] Toggle/enter [ESC] Leave menu [S] Save +[O] Load [?] Symbol info [/] Jump to symbol +[F] Toggle show-help mode [C] Toggle show-name mode [A] Toggle show-all mode +[Q] Quit (prompts for save) [D] Save minimal config (advanced) +"""[1:-1].split("\n") + +# Lines of help text shown at the bottom of the information dialog +_INFO_HELP_LINES = """ +[ESC/q] Return to menu [/] Jump to symbol +"""[1:-1].split("\n") + +# Lines of help text shown at the bottom of the search dialog +_JUMP_TO_HELP_LINES = """ +Type text to narrow the search. Regexes are supported (via Python's 're' +module). The up/down cursor keys step in the list. [Enter] jumps to the +selected symbol. [ESC] aborts the search. Type multiple space-separated +strings/regexes to find entries that match all of them. Type Ctrl-F to +view the help of the selected item without leaving the dialog. +"""[1:-1].split("\n") + +# +# Styling +# + +_STYLES = { + "default": """ + path=fg:black,bg:white,bold + separator=fg:black,bg:yellow,bold + list=fg:black,bg:white + selection=fg:white,bg:blue,bold + inv-list=fg:red,bg:white + inv-selection=fg:red,bg:blue + help=path + show-help=list + frame=fg:black,bg:yellow,bold + body=fg:white,bg:black + edit=fg:white,bg:blue + jump-edit=edit + text=list + """, + + # This style is forced on terminals that do no support colors + "monochrome": """ + path=bold + separator=bold,standout + list= + selection=bold,standout + inv-list=bold + inv-selection=bold,standout + help=bold + show-help= + frame=bold,standout + body= + edit=standout + jump-edit= + text= + """, + + # Blue-tinted style loosely resembling lxdialog + "aquatic": """ + path=fg:white,bg:blue + separator=fg:white,bg:cyan + help=path + frame=fg:white,bg:cyan + body=fg:white,bg:blue + edit=fg:black,bg:white + """ +} + +_NAMED_COLORS = { + # Basic colors + "black": curses.COLOR_BLACK, + "red": curses.COLOR_RED, + "green": curses.COLOR_GREEN, + "yellow": curses.COLOR_YELLOW, + "blue": curses.COLOR_BLUE, + "magenta": curses.COLOR_MAGENTA, + "cyan": curses.COLOR_CYAN, + "white": curses.COLOR_WHITE, + + # Bright versions + "brightblack": curses.COLOR_BLACK + 8, + "brightred": curses.COLOR_RED + 8, + "brightgreen": curses.COLOR_GREEN + 8, + "brightyellow": curses.COLOR_YELLOW + 8, + "brightblue": curses.COLOR_BLUE + 8, + "brightmagenta": curses.COLOR_MAGENTA + 8, + "brightcyan": curses.COLOR_CYAN + 8, + "brightwhite": curses.COLOR_WHITE + 8, + + # Aliases + "purple": curses.COLOR_MAGENTA, + "brightpurple": curses.COLOR_MAGENTA + 8, +} + + +def _rgb_to_6cube(rgb): + # Converts an 888 RGB color to a 3-tuple (nice in that it's hashable) + # representing the closest xterm 256-color 6x6x6 color cube color. + # + # The xterm 256-color extension uses a RGB color palette with components in + # the range 0-5 (a 6x6x6 cube). The catch is that the mapping is nonlinear. + # Index 0 in the 6x6x6 cube is mapped to 0, index 1 to 95, then 135, 175, + # etc., in increments of 40. See the links below: + # + # https://commons.wikimedia.org/wiki/File:Xterm_256color_chart.svg + # https://github.com/tmux/tmux/blob/master/colour.c + + # 48 is the middle ground between 0 and 95. + return tuple(0 if x < 48 else int(round(max(1, (x - 55)/40))) for x in rgb) + + +def _6cube_to_rgb(r6g6b6): + # Returns the 888 RGB color for a 666 xterm color cube index + + return tuple(0 if x == 0 else 40*x + 55 for x in r6g6b6) + + +def _rgb_to_gray(rgb): + # Converts an 888 RGB color to the index of an xterm 256-color grayscale + # color with approx. the same perceived brightness + + # Calculate the luminance (gray intensity) of the color. See + # https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color + # and + # https://www.w3.org/TR/AERT/#color-contrast + luma = 0.299*rgb[0] + 0.587*rgb[1] + 0.114*rgb[2] + + # Closest index in the grayscale palette, which starts at RGB 0x080808, + # with stepping 0x0A0A0A + index = int(round((luma - 8)/10)) + + # Clamp the index to 0-23, corresponding to 232-255 + return max(0, min(index, 23)) + + +def _gray_to_rgb(index): + # Convert a grayscale index to its closet single RGB component + + return 3*(10*index + 8,) # Returns a 3-tuple + + +# Obscure Python: We never pass a value for rgb2index, and it keeps pointing to +# the same dict. This avoids a global. +def _alloc_rgb(rgb, rgb2index={}): + # Initialize a new entry in the xterm palette to the given RGB color, + # returning its index. If the color has already been initialized, the index + # of the existing entry is returned. + # + # ncurses is palette-based, so we need to overwrite palette entries to make + # new colors. + # + # The colors from 0 to 15 are user-defined, and there's no way to query + # their RGB values, so we better leave them untouched. Also leave any + # hypothetical colors above 255 untouched (though we're unlikely to + # allocate that many colors anyway). + + if rgb in rgb2index: + return rgb2index[rgb] + + # Many terminals allow the user to customize the first 16 colors. Avoid + # changing their values. + color_index = 16 + len(rgb2index) + if color_index >= 256: + _warn("Unable to allocate new RGB color ", rgb, ". Too many colors " + "allocated.") + return 0 + + # Map each RGB component from the range 0-255 to the range 0-1000, which is + # what curses uses + curses.init_color(color_index, *(int(round(1000*x/255)) for x in rgb)) + rgb2index[rgb] = color_index + + return color_index + + +def _color_from_num(num): + # Returns the index of a color that looks like color 'num' in the xterm + # 256-color palette (but that might not be 'num', if we're redefining + # colors) + + # - _alloc_rgb() won't touch the first 16 colors or any (hypothetical) + # colors above 255, so we can always return them as-is + # + # - If the terminal doesn't support changing color definitions, or if + # curses.COLORS < 256, _alloc_rgb() won't touch any color, and all colors + # can be returned as-is + if num < 16 or num > 255 or not curses.can_change_color() or \ + curses.COLORS < 256: + return num + + # _alloc_rgb() might redefine colors, so emulate the xterm 256-color + # palette by allocating new colors instead of returning color numbers + # directly + + if num < 232: + num -= 16 + return _alloc_rgb(_6cube_to_rgb(((num//36)%6, (num//6)%6, num%6))) + + return _alloc_rgb(_gray_to_rgb(num - 232)) + + +def _color_from_rgb(rgb): + # Returns the index of a color matching the 888 RGB color 'rgb'. The + # returned color might be an ~exact match or an approximation, depending on + # terminal capabilities. + + # Calculates the Euclidean distance between two RGB colors + def dist(r1, r2): return sum((x - y)**2 for x, y in zip(r1, r2)) + + if curses.COLORS >= 256: + # Assume we're dealing with xterm's 256-color extension + + if curses.can_change_color(): + # Best case -- the terminal supports changing palette entries via + # curses.init_color(). Initialize an unused palette entry and + # return it. + return _alloc_rgb(rgb) + + # Second best case -- pick between the xterm 256-color extension colors + + # Closest 6-cube "color" color + c6 = _rgb_to_6cube(rgb) + # Closest gray color + gray = _rgb_to_gray(rgb) + + if dist(rgb, _6cube_to_rgb(c6)) < dist(rgb, _gray_to_rgb(gray)): + # Use the "color" color from the 6x6x6 color palette. Calculate the + # color number from the 6-cube index triplet. + return 16 + 36*c6[0] + 6*c6[1] + c6[2] + + # Use the color from the gray palette + return 232 + gray + + # Terminal not in xterm 256-color mode. This is probably the best we can + # do, or is it? Submit patches. :) + min_dist = float('inf') + best = -1 + for color in range(curses.COLORS): + # ncurses uses the range 0..1000. Scale that down to 0..255. + d = dist(rgb, tuple(int(round(255*c/1000)) + for c in curses.color_content(color))) + if d < min_dist: + min_dist = d + best = color + + return best + + +def _parse_style(style_str, parsing_default): + # Parses a string with '=