Update to Kconfiglib version 12.4.0. This removes the need to use
".configuration.old"
This commit is contained in:
parent
619b313adb
commit
cf2f109b89
4 changed files with 943 additions and 599 deletions
4
Makefile
4
Makefile
|
@ -12,12 +12,12 @@ all: menuconfig
|
||||||
@$$(grep RUN_ME configuration 2>/dev/null | sed -e 's@RUN_ME=\"@@' -e 's@\"@@')
|
@$$(grep RUN_ME configuration 2>/dev/null | sed -e 's@RUN_ME=\"@@' -e 's@\"@@')
|
||||||
|
|
||||||
menuconfig:
|
menuconfig:
|
||||||
@cp -a configuration .configuration.old 2>/dev/null || true
|
@cp -a configuration configuration.old 2>/dev/null || true
|
||||||
@CONFIG_="" KCONFIG_CONFIG=configuration $(CONFIG)/menuconfig.py $(CONFIG_CONFIG_IN)
|
@CONFIG_="" KCONFIG_CONFIG=configuration $(CONFIG)/menuconfig.py $(CONFIG_CONFIG_IN)
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f configuration .configuration.old error
|
rm -f configuration configuration.old error
|
||||||
|
|
||||||
.PHONY: all menuconfig clean
|
.PHONY: all menuconfig clean
|
||||||
|
|
2
jhalfs
2
jhalfs
|
@ -131,7 +131,7 @@ esac
|
||||||
# If the user has not saved his configuration file, let's ask
|
# If the user has not saved his configuration file, let's ask
|
||||||
# if he or she really wants to run this stuff
|
# if he or she really wants to run this stuff
|
||||||
time_current=$(stat -c '%Y' configuration 2>/dev/null || date +%s)
|
time_current=$(stat -c '%Y' configuration 2>/dev/null || date +%s)
|
||||||
time_old=$(stat -c '%Y' .configuration.old 2>/dev/null || printf '%s' "$time_current")
|
time_old=$(stat -c '%Y' configuration.old 2>/dev/null || printf '%s' "$time_current")
|
||||||
if [ "$(printf '%d' "$time_old")" -ge "$(printf '%d' "$time_current")" ] ; then
|
if [ "$(printf '%d' "$time_old")" -ge "$(printf '%d' "$time_current")" ] ; then
|
||||||
printf 'Do you want to run jhalfs? yes/no (yes): '
|
printf 'Do you want to run jhalfs? yes/no (yes): '
|
||||||
read -r ANSWER
|
read -r ANSWER
|
||||||
|
|
1055
menu/kconfiglib.py
1055
menu/kconfiglib.py
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python
|
||||||
|
|
||||||
# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson
|
# Copyright (c) 2018-2019, Nordic Semiconductor ASA and Ulf Magnusson
|
||||||
# SPDX-License-Identifier: ISC
|
# SPDX-License-Identifier: ISC
|
||||||
|
@ -7,8 +7,8 @@
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
A curses-based menuconfig implementation. The interface should feel familiar to
|
A curses-based Python 2/3 menuconfig implementation. The interface should feel
|
||||||
people used to mconf ('make menuconfig').
|
familiar to people used to mconf ('make menuconfig').
|
||||||
|
|
||||||
Supports the same keys as mconf, and also supports a set of keybindings
|
Supports the same keys as mconf, and also supports a set of keybindings
|
||||||
inspired by Vi:
|
inspired by Vi:
|
||||||
|
@ -20,14 +20,14 @@ inspired by Vi:
|
||||||
G/End : Jump to end of list
|
G/End : Jump to end of list
|
||||||
g/Home : Jump to beginning 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
|
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
|
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
|
jumping directly to any symbol (including invisible symbols), choice, menu or
|
||||||
comment (as in a Kconfig 'comment "Foo"') is available instead.
|
comment (as in a Kconfig 'comment "Foo"') is available instead.
|
||||||
|
|
||||||
Space and Enter are "smart" and try to do what you'd expect for the given menu
|
|
||||||
entry.
|
|
||||||
|
|
||||||
A few different modes are available:
|
A few different modes are available:
|
||||||
|
|
||||||
F: Toggle show-help mode, which shows the help text of the currently selected
|
F: Toggle show-help mode, which shows the help text of the currently selected
|
||||||
|
@ -55,6 +55,9 @@ as a command-line argument. With no argument, it defaults to "Kconfig".
|
||||||
The KCONFIG_CONFIG environment variable specifies the .config file to load (if
|
The KCONFIG_CONFIG environment variable specifies the .config file to load (if
|
||||||
it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used.
|
it exists) and save. If KCONFIG_CONFIG is unset, ".config" is used.
|
||||||
|
|
||||||
|
When overwriting a configuration file, the old version is saved to
|
||||||
|
<filename>.old (e.g. .config.old).
|
||||||
|
|
||||||
$srctree is supported through Kconfiglib.
|
$srctree is supported through Kconfiglib.
|
||||||
|
|
||||||
|
|
||||||
|
@ -171,19 +174,15 @@ Other features
|
||||||
Limitations
|
Limitations
|
||||||
===========
|
===========
|
||||||
|
|
||||||
- Python 3 only
|
Doesn't work out of the box on Windows, but can be made to work with 'pip
|
||||||
|
install windows-curses'. See the
|
||||||
This is mostly due to Python 2 not having curses.get_wch(), which is needed
|
|
||||||
for Unicode support.
|
|
||||||
|
|
||||||
- Doesn't work out of the box on Windows
|
|
||||||
|
|
||||||
Can be made to work with 'pip install windows-curses' though. See the
|
|
||||||
https://github.com/zephyrproject-rtos/windows-curses repository.
|
https://github.com/zephyrproject-rtos/windows-curses repository.
|
||||||
|
|
||||||
'pip install kconfiglib' on Windows automatically installs windows-curses
|
'pip install kconfiglib' on Windows automatically installs windows-curses
|
||||||
to make the menuconfig usable.
|
to make the menuconfig usable.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import errno
|
import errno
|
||||||
import locale
|
import locale
|
||||||
|
@ -193,7 +192,7 @@ import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \
|
from kconfiglib import Symbol, Choice, MENU, COMMENT, MenuNode, \
|
||||||
BOOL, TRISTATE, STRING, INT, HEX, UNKNOWN, \
|
BOOL, TRISTATE, STRING, INT, HEX, \
|
||||||
AND, OR, \
|
AND, OR, \
|
||||||
expr_str, expr_value, split_expr, \
|
expr_str, expr_value, split_expr, \
|
||||||
standard_sc_expr_str, \
|
standard_sc_expr_str, \
|
||||||
|
@ -628,7 +627,6 @@ def _style_attr(fg_color, bg_color, attribs, color_attribs={}):
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
# Used as the entry point in setup.py
|
|
||||||
def _main():
|
def _main():
|
||||||
menuconfig(standard_kconfig())
|
menuconfig(standard_kconfig())
|
||||||
|
|
||||||
|
@ -648,12 +646,12 @@ def menuconfig(kconf):
|
||||||
|
|
||||||
_kconf = kconf
|
_kconf = kconf
|
||||||
|
|
||||||
# Load existing configuration and set _conf_changed True if it is outdated
|
|
||||||
_conf_changed = _load_config()
|
|
||||||
|
|
||||||
# Filename to save configuration to
|
# Filename to save configuration to
|
||||||
_conf_filename = standard_config_filename()
|
_conf_filename = standard_config_filename()
|
||||||
|
|
||||||
|
# Load existing configuration and set _conf_changed True if it is outdated
|
||||||
|
_conf_changed = _load_config()
|
||||||
|
|
||||||
# Filename to save minimal configuration to
|
# Filename to save minimal configuration to
|
||||||
_minconf_filename = "defconfig"
|
_minconf_filename = "defconfig"
|
||||||
|
|
||||||
|
@ -671,7 +669,7 @@ def menuconfig(kconf):
|
||||||
|
|
||||||
# Disable warnings. They get mangled in curses mode, and we deal with
|
# Disable warnings. They get mangled in curses mode, and we deal with
|
||||||
# errors ourselves.
|
# errors ourselves.
|
||||||
kconf.disable_warnings()
|
kconf.warn = False
|
||||||
|
|
||||||
# Make curses use the locale settings specified in the environment
|
# Make curses use the locale settings specified in the environment
|
||||||
locale.setlocale(locale.LC_ALL, "")
|
locale.setlocale(locale.LC_ALL, "")
|
||||||
|
@ -708,7 +706,8 @@ def _load_config():
|
||||||
# Returns True if .config is missing or outdated. We always prompt for
|
# Returns True if .config is missing or outdated. We always prompt for
|
||||||
# saving the configuration in that case.
|
# saving the configuration in that case.
|
||||||
|
|
||||||
if not _kconf.load_config():
|
print(_kconf.load_config())
|
||||||
|
if not os.path.exists(_conf_filename):
|
||||||
# No .config
|
# No .config
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -728,7 +727,7 @@ def _needs_save():
|
||||||
if sym.config_string:
|
if sym.config_string:
|
||||||
# Unwritten symbol
|
# Unwritten symbol
|
||||||
return True
|
return True
|
||||||
elif sym.type in (BOOL, TRISTATE):
|
elif sym.orig_type in (BOOL, TRISTATE):
|
||||||
if sym.tri_value != sym.user_value:
|
if sym.tri_value != sym.user_value:
|
||||||
# Written bool/tristate symbol, new value
|
# Written bool/tristate symbol, new value
|
||||||
return True
|
return True
|
||||||
|
@ -769,7 +768,7 @@ def _needs_save():
|
||||||
# If True, the corresponding mode is on. See the module docstring.
|
# If True, the corresponding mode is on. See the module docstring.
|
||||||
#
|
#
|
||||||
# _conf_filename:
|
# _conf_filename:
|
||||||
# .config file to save the configuration to
|
# File to save the configuration to
|
||||||
#
|
#
|
||||||
# _minconf_filename:
|
# _minconf_filename:
|
||||||
# File to save minimal configurations to
|
# File to save minimal configurations to
|
||||||
|
@ -801,7 +800,7 @@ def _menuconfig(stdscr):
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
|
|
||||||
c = _get_wch_compat(_menu_win)
|
c = _getch_compat(_menu_win)
|
||||||
|
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
_resize_main()
|
_resize_main()
|
||||||
|
@ -828,26 +827,17 @@ def _menuconfig(stdscr):
|
||||||
elif c in (curses.KEY_HOME, "g"):
|
elif c in (curses.KEY_HOME, "g"):
|
||||||
_select_first_menu_entry()
|
_select_first_menu_entry()
|
||||||
|
|
||||||
elif c in (curses.KEY_RIGHT, " ", "\n", "l", "L"):
|
elif c == " ":
|
||||||
# Do appropriate node action. Only Space is treated specially,
|
# Toggle the node if possible
|
||||||
# preferring to toggle nodes rather than enter menus.
|
|
||||||
|
|
||||||
sel_node = _shown[_sel_node_i]
|
sel_node = _shown[_sel_node_i]
|
||||||
|
if not _change_node(sel_node):
|
||||||
if sel_node.is_menuconfig and not \
|
|
||||||
(c == " " and _prefer_toggle(sel_node.item)):
|
|
||||||
|
|
||||||
_enter_menu(sel_node)
|
_enter_menu(sel_node)
|
||||||
|
|
||||||
else:
|
elif c in (curses.KEY_RIGHT, "\n", "l", "L"):
|
||||||
|
# Enter the node if possible
|
||||||
|
sel_node = _shown[_sel_node_i]
|
||||||
|
if not _enter_menu(sel_node):
|
||||||
_change_node(sel_node)
|
_change_node(sel_node)
|
||||||
if _is_y_mode_choice_sym(sel_node.item) and not sel_node.list:
|
|
||||||
# Immediately jump to the parent menu after making a choice
|
|
||||||
# selection, like 'make menuconfig' does, except if the
|
|
||||||
# menu node has children (which can happen if a symbol
|
|
||||||
# 'depends on' a choice symbol that immediately precedes
|
|
||||||
# it).
|
|
||||||
_leave_menu()
|
|
||||||
|
|
||||||
elif c in ("n", "N"):
|
elif c in ("n", "N"):
|
||||||
_set_sel_node_tri_val(0)
|
_set_sel_node_tri_val(0)
|
||||||
|
@ -929,8 +919,10 @@ def _quit_dialog():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if c == "y":
|
if c == "y":
|
||||||
if _try_save(_kconf.write_config, _conf_filename, "configuration"):
|
# Returns a message to print
|
||||||
return "Configuration saved to '{}'".format(_conf_filename)
|
msg = _try_save(_kconf.write_config, _conf_filename, "configuration")
|
||||||
|
if msg:
|
||||||
|
return msg
|
||||||
|
|
||||||
elif c == "n":
|
elif c == "n":
|
||||||
return "Configuration ({}) was not saved".format(_conf_filename)
|
return "Configuration ({}) was not saved".format(_conf_filename)
|
||||||
|
@ -961,10 +953,12 @@ def _init():
|
||||||
# Looking for this in addition to KEY_BACKSPACE (which is unreliable) makes
|
# Looking for this in addition to KEY_BACKSPACE (which is unreliable) makes
|
||||||
# backspace work with TERM=vt100. That makes it likely to work in sane
|
# backspace work with TERM=vt100. That makes it likely to work in sane
|
||||||
# environments.
|
# environments.
|
||||||
#
|
_ERASE_CHAR = curses.erasechar()
|
||||||
# erasechar() returns a 'bytes' object. Since we use get_wch(), we need to
|
if sys.version_info[0] >= 3:
|
||||||
# decode it. Just give up and avoid crashing if it can't be decoded.
|
# erasechar() returns a one-byte bytes object on Python 3. This sets
|
||||||
_ERASE_CHAR = curses.erasechar().decode("utf-8", "ignore")
|
# _ERASE_CHAR to a blank string if it can't be decoded, which should be
|
||||||
|
# harmless.
|
||||||
|
_ERASE_CHAR = _ERASE_CHAR.decode("utf-8", "ignore")
|
||||||
|
|
||||||
_init_styles()
|
_init_styles()
|
||||||
|
|
||||||
|
@ -1059,28 +1053,27 @@ def _width(win):
|
||||||
return win.getmaxyx()[1]
|
return win.getmaxyx()[1]
|
||||||
|
|
||||||
|
|
||||||
def _prefer_toggle(item):
|
|
||||||
# For nodes with menus, determines whether Space should change the value of
|
|
||||||
# the node's item or enter its menu. We toggle symbols (which have menus
|
|
||||||
# when they're defined with 'menuconfig') and choices that can be in more
|
|
||||||
# than one mode (e.g. optional choices). In other cases, we enter the menu.
|
|
||||||
|
|
||||||
return isinstance(item, Symbol) or \
|
|
||||||
(isinstance(item, Choice) and len(item.assignable) > 1)
|
|
||||||
|
|
||||||
|
|
||||||
def _enter_menu(menu):
|
def _enter_menu(menu):
|
||||||
# Makes 'menu' the currently displayed menu. "Menu" here includes choices
|
# Makes 'menu' the currently displayed menu. In addition to actual 'menu's,
|
||||||
# and symbols defined with the 'menuconfig' keyword.
|
# "menu" here includes choices and symbols defined with the 'menuconfig'
|
||||||
|
# keyword.
|
||||||
|
#
|
||||||
|
# Returns False if 'menu' can't be entered.
|
||||||
|
|
||||||
global _cur_menu
|
global _cur_menu
|
||||||
global _shown
|
global _shown
|
||||||
global _sel_node_i
|
global _sel_node_i
|
||||||
global _menu_scroll
|
global _menu_scroll
|
||||||
|
|
||||||
|
if not menu.is_menuconfig:
|
||||||
|
# Not a menu
|
||||||
|
return False
|
||||||
|
|
||||||
shown_sub = _shown_nodes(menu)
|
shown_sub = _shown_nodes(menu)
|
||||||
# Never enter empty menus. We depend on having a current node.
|
# Never enter empty menus. We depend on having a current node.
|
||||||
if shown_sub:
|
if not shown_sub:
|
||||||
|
return False
|
||||||
|
|
||||||
# Remember where the current node appears on the screen, so we can try
|
# Remember where the current node appears on the screen, so we can try
|
||||||
# to get it to appear in the same place when we leave the menu
|
# to get it to appear in the same place when we leave the menu
|
||||||
_parent_screen_rows.append(_sel_node_i - _menu_scroll)
|
_parent_screen_rows.append(_sel_node_i - _menu_scroll)
|
||||||
|
@ -1093,6 +1086,8 @@ def _enter_menu(menu):
|
||||||
if isinstance(menu.item, Choice):
|
if isinstance(menu.item, Choice):
|
||||||
_select_selected_choice_sym()
|
_select_selected_choice_sym()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _select_selected_choice_sym():
|
def _select_selected_choice_sym():
|
||||||
# Puts the cursor on the currently selected (y-valued) choice symbol, if
|
# Puts the cursor on the currently selected (y-valued) choice symbol, if
|
||||||
|
@ -1225,7 +1220,7 @@ def _select_prev_menu_entry():
|
||||||
_sel_node_i -= 1
|
_sel_node_i -= 1
|
||||||
|
|
||||||
# See _select_next_menu_entry()
|
# See _select_next_menu_entry()
|
||||||
if _sel_node_i <= _menu_scroll + _SCROLL_OFFSET:
|
if _sel_node_i < _menu_scroll + _SCROLL_OFFSET:
|
||||||
_menu_scroll = max(_menu_scroll - 1, 0)
|
_menu_scroll = max(_menu_scroll - 1, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1418,7 +1413,7 @@ def _draw_main():
|
||||||
|
|
||||||
_path_win.erase()
|
_path_win.erase()
|
||||||
|
|
||||||
# Draw the menu path ("(top menu) -> menu -> submenu -> ...")
|
# Draw the menu path ("(Top) -> Menu -> Submenu -> ...")
|
||||||
|
|
||||||
menu_prompts = []
|
menu_prompts = []
|
||||||
|
|
||||||
|
@ -1430,7 +1425,7 @@ def _draw_main():
|
||||||
menu_prompts.append(menu.prompt[0] if menu.prompt else
|
menu_prompts.append(menu.prompt[0] if menu.prompt else
|
||||||
standard_sc_expr_str(menu.item))
|
standard_sc_expr_str(menu.item))
|
||||||
menu = menu.parent
|
menu = menu.parent
|
||||||
menu_prompts.append("(top menu)")
|
menu_prompts.append("(Top)")
|
||||||
menu_prompts.reverse()
|
menu_prompts.reverse()
|
||||||
|
|
||||||
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
|
# Hack: We can't put ACS_RARROW directly in the string. Temporarily
|
||||||
|
@ -1471,10 +1466,6 @@ def _shown_nodes(menu):
|
||||||
res = []
|
res = []
|
||||||
|
|
||||||
while node:
|
while node:
|
||||||
# This code is minorly performance-sensitive. Make it too slow
|
|
||||||
# (e.g., by always recursing the entire tree), and going in and out
|
|
||||||
# of menus no longer feels instant.
|
|
||||||
|
|
||||||
if _visible(node) or _show_all:
|
if _visible(node) or _show_all:
|
||||||
res.append(node)
|
res.append(node)
|
||||||
if node.list and not node.is_menuconfig:
|
if node.list and not node.is_menuconfig:
|
||||||
|
@ -1483,14 +1474,11 @@ def _shown_nodes(menu):
|
||||||
# menus and choices as well as 'menuconfig' symbols.
|
# menus and choices as well as 'menuconfig' symbols.
|
||||||
res += rec(node.list)
|
res += rec(node.list)
|
||||||
|
|
||||||
elif node.list and isinstance(node.item, Symbol) and \
|
elif node.list and isinstance(node.item, Symbol):
|
||||||
expr_value(node.dep):
|
|
||||||
# Show invisible symbols if they have visible children. This
|
# Show invisible symbols if they have visible children. This
|
||||||
# can happen for an m/y-valued symbol with an optional prompt
|
# can happen for an m/y-valued symbol with an optional prompt
|
||||||
# ('prompt "foo" is COND') that is currently disabled. The
|
# ('prompt "foo" is COND') that is currently disabled. Note
|
||||||
# expr_value(node.dep) check safely prunes the search: A node
|
# that it applies to both 'config' and 'menuconfig' symbols.
|
||||||
# with unsatisfied direct dependencies can never have visible
|
|
||||||
# children.
|
|
||||||
shown_children = rec(node.list)
|
shown_children = rec(node.list)
|
||||||
if shown_children:
|
if shown_children:
|
||||||
res.append(node)
|
res.append(node)
|
||||||
|
@ -1553,36 +1541,32 @@ def _change_node(node):
|
||||||
# Changes the value of the menu node 'node' if it is a symbol. Bools and
|
# Changes the value of the menu node 'node' if it is a symbol. Bools and
|
||||||
# tristates are toggled, while other symbol types pop up a text entry
|
# tristates are toggled, while other symbol types pop up a text entry
|
||||||
# dialog.
|
# dialog.
|
||||||
|
#
|
||||||
|
# Returns False if the value of 'node' can't be changed.
|
||||||
|
|
||||||
if not isinstance(node.item, (Symbol, Choice)):
|
if not _changeable(node):
|
||||||
return
|
return False
|
||||||
|
|
||||||
# This will hit for invisible symbols, which appear in show-all mode and
|
|
||||||
# when an invisible symbol has visible children (which can happen e.g. for
|
|
||||||
# symbols with optional prompts)
|
|
||||||
if not (node.prompt and expr_value(node.prompt[1])):
|
|
||||||
return
|
|
||||||
|
|
||||||
# sc = symbol/choice
|
# sc = symbol/choice
|
||||||
sc = node.item
|
sc = node.item
|
||||||
|
|
||||||
if sc.type in (INT, HEX, STRING):
|
if sc.orig_type in (INT, HEX, STRING):
|
||||||
s = sc.str_value
|
s = sc.str_value
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
s = _input_dialog(
|
s = _input_dialog(
|
||||||
"{} ({})".format(node.prompt[0], TYPE_TO_STR[sc.type]),
|
"{} ({})".format(node.prompt[0], TYPE_TO_STR[sc.orig_type]),
|
||||||
s, _range_info(sc))
|
s, _range_info(sc))
|
||||||
|
|
||||||
if s is None:
|
if s is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
if sc.type in (INT, HEX):
|
if sc.orig_type in (INT, HEX):
|
||||||
s = s.strip()
|
s = s.strip()
|
||||||
|
|
||||||
# 'make menuconfig' does this too. Hex values not starting with
|
# 'make menuconfig' does this too. Hex values not starting with
|
||||||
# '0x' are accepted when loading .config files though.
|
# '0x' are accepted when loading .config files though.
|
||||||
if sc.type == HEX and not s.startswith(("0x", "0X")):
|
if sc.orig_type == HEX and not s.startswith(("0x", "0X")):
|
||||||
s = "0x" + s
|
s = "0x" + s
|
||||||
|
|
||||||
if _check_valid(sc, s):
|
if _check_valid(sc, s):
|
||||||
|
@ -1594,13 +1578,42 @@ def _change_node(node):
|
||||||
# case: .assignable can be (2,) while .tri_value is 0.
|
# case: .assignable can be (2,) while .tri_value is 0.
|
||||||
_set_val(sc, sc.assignable[0])
|
_set_val(sc, sc.assignable[0])
|
||||||
|
|
||||||
elif sc.assignable:
|
else:
|
||||||
# Set the symbol to the value after the current value in
|
# Set the symbol to the value after the current value in
|
||||||
# sc.assignable, with wrapping
|
# sc.assignable, with wrapping
|
||||||
val_index = sc.assignable.index(sc.tri_value)
|
val_index = sc.assignable.index(sc.tri_value)
|
||||||
_set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)])
|
_set_val(sc, sc.assignable[(val_index + 1) % len(sc.assignable)])
|
||||||
|
|
||||||
|
|
||||||
|
if _is_y_mode_choice_sym(sc) and not node.list:
|
||||||
|
# Immediately jump to the parent menu after making a choice selection,
|
||||||
|
# like 'make menuconfig' does, except if the menu node has children
|
||||||
|
# (which can happen if a symbol 'depends on' a choice symbol that
|
||||||
|
# immediately precedes it).
|
||||||
|
_leave_menu()
|
||||||
|
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _changeable(node):
|
||||||
|
# Returns True if the value if 'node' can be changed
|
||||||
|
|
||||||
|
sc = node.item
|
||||||
|
|
||||||
|
if not isinstance(sc, (Symbol, Choice)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# This will hit for invisible symbols, which appear in show-all mode and
|
||||||
|
# when an invisible symbol has visible children (which can happen e.g. for
|
||||||
|
# symbols with optional prompts)
|
||||||
|
if not (node.prompt and expr_value(node.prompt[1])):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return sc.orig_type in (STRING, INT, HEX) or len(sc.assignable) > 1 \
|
||||||
|
or _is_y_mode_choice_sym(sc)
|
||||||
|
|
||||||
|
|
||||||
def _set_sel_node_tri_val(tri_val):
|
def _set_sel_node_tri_val(tri_val):
|
||||||
# Sets the value of the currently selected menu entry to 'tri_val', if that
|
# Sets the value of the currently selected menu entry to 'tri_val', if that
|
||||||
# value can be assigned
|
# value can be assigned
|
||||||
|
@ -1702,7 +1715,7 @@ def _input_dialog(title, initial_text, info_text=None):
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
|
|
||||||
c = _get_wch_compat(win)
|
c = _getch_compat(win)
|
||||||
|
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
# Resize the main display too. The dialog floats above it.
|
# Resize the main display too. The dialog floats above it.
|
||||||
|
@ -1846,29 +1859,33 @@ def _save_dialog(save_fn, default_filename, description):
|
||||||
|
|
||||||
filename = os.path.expanduser(filename)
|
filename = os.path.expanduser(filename)
|
||||||
|
|
||||||
if _try_save(save_fn, filename, description):
|
msg = _try_save(save_fn, filename, description)
|
||||||
_msg("Success", "{} saved to {}".format(description, filename))
|
if msg:
|
||||||
|
_msg("Success", msg)
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def _try_save(save_fn, filename, description):
|
def _try_save(save_fn, filename, description):
|
||||||
# Tries to save a configuration file. Pops up an error and returns False on
|
# Tries to save a configuration file. Returns a message to print on
|
||||||
# failure.
|
# success.
|
||||||
#
|
#
|
||||||
# save_fn:
|
# save_fn:
|
||||||
# Function to call with 'filename' to save the file
|
# Function to call with 'filename' to save the file
|
||||||
#
|
#
|
||||||
# description:
|
# description:
|
||||||
# String describing the thing being saved
|
# String describing the thing being saved
|
||||||
|
#
|
||||||
|
# Return value:
|
||||||
|
# A message to print on success, and None on failure
|
||||||
|
|
||||||
try:
|
try:
|
||||||
save_fn(filename)
|
# save_fn() returns a message to print
|
||||||
return True
|
return save_fn(filename)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
_error("Error saving {} to '{}'\n\n{} (errno: {})"
|
_error("Error saving {} to '{}'\n\n{} (errno: {})"
|
||||||
.format(description, e.filename, e.strerror,
|
.format(description, e.filename, e.strerror,
|
||||||
errno.errorcode[e.errno]))
|
errno.errorcode[e.errno]))
|
||||||
return False
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _key_dialog(title, text, keys):
|
def _key_dialog(title, text, keys):
|
||||||
|
@ -1902,7 +1919,7 @@ def _key_dialog(title, text, keys):
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
|
|
||||||
c = _get_wch_compat(win)
|
c = _getch_compat(win)
|
||||||
|
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
# Resize the main display too. The dialog floats above it.
|
# Resize the main display too. The dialog floats above it.
|
||||||
|
@ -2000,30 +2017,29 @@ def _jump_to_dialog():
|
||||||
|
|
||||||
_safe_curs_set(2)
|
_safe_curs_set(2)
|
||||||
|
|
||||||
# TODO: Code duplication with _select_{next,prev}_menu_entry(). Can this be
|
# Logic duplication with _select_{next,prev}_menu_entry(), except we do a
|
||||||
# factored out in some nice way?
|
# functional variant that returns the new (sel_node_i, scroll) values to
|
||||||
|
# avoid 'nonlocal'. TODO: Can this be factored out in some nice way?
|
||||||
|
|
||||||
def select_next_match():
|
def select_next_match():
|
||||||
nonlocal sel_node_i
|
if sel_node_i == len(matches) - 1:
|
||||||
nonlocal scroll
|
return sel_node_i, scroll
|
||||||
|
|
||||||
if sel_node_i < len(matches) - 1:
|
if sel_node_i + 1 >= scroll + _height(matches_win) - _SCROLL_OFFSET \
|
||||||
sel_node_i += 1
|
|
||||||
|
|
||||||
if sel_node_i >= scroll + _height(matches_win) - _SCROLL_OFFSET \
|
|
||||||
and scroll < _max_scroll(matches, matches_win):
|
and scroll < _max_scroll(matches, matches_win):
|
||||||
|
|
||||||
scroll += 1
|
return sel_node_i + 1, scroll + 1
|
||||||
|
|
||||||
|
return sel_node_i + 1, scroll
|
||||||
|
|
||||||
def select_prev_match():
|
def select_prev_match():
|
||||||
nonlocal sel_node_i
|
if sel_node_i == 0:
|
||||||
nonlocal scroll
|
return sel_node_i, scroll
|
||||||
|
|
||||||
if sel_node_i > 0:
|
if sel_node_i - 1 < scroll + _SCROLL_OFFSET:
|
||||||
sel_node_i -= 1
|
return sel_node_i - 1, max(scroll - 1, 0)
|
||||||
|
|
||||||
if sel_node_i <= scroll + _SCROLL_OFFSET:
|
return sel_node_i - 1, scroll
|
||||||
scroll = max(scroll - 1, 0)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if s != prev_s:
|
if s != prev_s:
|
||||||
|
@ -2099,7 +2115,7 @@ def _jump_to_dialog():
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
|
|
||||||
c = _get_wch_compat(edit_box)
|
c = _getch_compat(edit_box)
|
||||||
|
|
||||||
if c == "\n":
|
if c == "\n":
|
||||||
if matches:
|
if matches:
|
||||||
|
@ -2130,21 +2146,21 @@ def _jump_to_dialog():
|
||||||
sel_node_i, scroll)
|
sel_node_i, scroll)
|
||||||
|
|
||||||
elif c == curses.KEY_DOWN:
|
elif c == curses.KEY_DOWN:
|
||||||
select_next_match()
|
sel_node_i, scroll = select_next_match()
|
||||||
|
|
||||||
elif c == curses.KEY_UP:
|
elif c == curses.KEY_UP:
|
||||||
select_prev_match()
|
sel_node_i, scroll = select_prev_match()
|
||||||
|
|
||||||
elif c in (curses.KEY_NPAGE, "\x04"): # Page Down/Ctrl-D
|
elif c in (curses.KEY_NPAGE, "\x04"): # Page Down/Ctrl-D
|
||||||
# Keep it simple. This way we get sane behavior for small windows,
|
# Keep it simple. This way we get sane behavior for small windows,
|
||||||
# etc., for free.
|
# etc., for free.
|
||||||
for _ in range(_PG_JUMP):
|
for _ in range(_PG_JUMP):
|
||||||
select_next_match()
|
sel_node_i, scroll = select_next_match()
|
||||||
|
|
||||||
# Page Up (no Ctrl-U, as it's already used by the edit box)
|
# Page Up (no Ctrl-U, as it's already used by the edit box)
|
||||||
elif c == curses.KEY_PPAGE:
|
elif c == curses.KEY_PPAGE:
|
||||||
for _ in range(_PG_JUMP):
|
for _ in range(_PG_JUMP):
|
||||||
select_prev_match()
|
sel_node_i, scroll = select_prev_match()
|
||||||
|
|
||||||
elif c == curses.KEY_END:
|
elif c == curses.KEY_END:
|
||||||
sel_node_i = len(matches) - 1
|
sel_node_i = len(matches) - 1
|
||||||
|
@ -2364,7 +2380,7 @@ def _info_dialog(node, from_jump_to_dialog):
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
|
|
||||||
c = _get_wch_compat(text_win)
|
c = _getch_compat(text_win)
|
||||||
|
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
_resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
|
_resize_info_dialog(top_line_win, text_win, bot_sep_win, help_win)
|
||||||
|
@ -2600,8 +2616,7 @@ def _help_info(sc):
|
||||||
|
|
||||||
for node in sc.nodes:
|
for node in sc.nodes:
|
||||||
if node.help is not None:
|
if node.help is not None:
|
||||||
s += "Help:\n\n{}\n\n" \
|
s += "Help:\n\n{}\n\n".format(_indent(node.help, 2))
|
||||||
.format(textwrap.indent(node.help, " "))
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -2670,7 +2685,7 @@ def _split_expr_info(expr, indent):
|
||||||
|
|
||||||
s = ""
|
s = ""
|
||||||
for i, term in enumerate(split_expr(expr, split_op)):
|
for i, term in enumerate(split_expr(expr, split_op)):
|
||||||
s += "{}{} {}".format(" "*indent,
|
s += "{}{} {}".format(indent*" ",
|
||||||
" " if i == 0 else op_str,
|
" " if i == 0 else op_str,
|
||||||
_expr_str(term))
|
_expr_str(term))
|
||||||
|
|
||||||
|
@ -2689,33 +2704,33 @@ def _select_imply_info(sym):
|
||||||
# 'sym'. The selecting/implying symbols are grouped according to which
|
# 'sym'. The selecting/implying symbols are grouped according to which
|
||||||
# value they select/imply 'sym' to (n/m/y).
|
# value they select/imply 'sym' to (n/m/y).
|
||||||
|
|
||||||
s = ""
|
def sis(expr, val, title):
|
||||||
|
|
||||||
def add_sis(expr, val, title):
|
|
||||||
nonlocal s
|
|
||||||
|
|
||||||
# sis = selects/implies
|
# sis = selects/implies
|
||||||
sis = [si for si in split_expr(expr, OR) if expr_value(si) == val]
|
sis = [si for si in split_expr(expr, OR) if expr_value(si) == val]
|
||||||
if sis:
|
if not sis:
|
||||||
s += title
|
return ""
|
||||||
|
|
||||||
|
res = title
|
||||||
for si in sis:
|
for si in sis:
|
||||||
s += " - {}\n".format(split_expr(si, AND)[0].name)
|
res += " - {}\n".format(split_expr(si, AND)[0].name)
|
||||||
s += "\n"
|
return res + "\n"
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
|
||||||
if sym.rev_dep is not _kconf.n:
|
if sym.rev_dep is not _kconf.n:
|
||||||
add_sis(sym.rev_dep, 2,
|
s += sis(sym.rev_dep, 2,
|
||||||
"Symbols currently y-selecting this symbol:\n")
|
"Symbols currently y-selecting this symbol:\n")
|
||||||
add_sis(sym.rev_dep, 1,
|
s += sis(sym.rev_dep, 1,
|
||||||
"Symbols currently m-selecting this symbol:\n")
|
"Symbols currently m-selecting this symbol:\n")
|
||||||
add_sis(sym.rev_dep, 0,
|
s += sis(sym.rev_dep, 0,
|
||||||
"Symbols currently n-selecting this symbol (no effect):\n")
|
"Symbols currently n-selecting this symbol (no effect):\n")
|
||||||
|
|
||||||
if sym.weak_rev_dep is not _kconf.n:
|
if sym.weak_rev_dep is not _kconf.n:
|
||||||
add_sis(sym.weak_rev_dep, 2,
|
s += sis(sym.weak_rev_dep, 2,
|
||||||
"Symbols currently y-implying this symbol:\n")
|
"Symbols currently y-implying this symbol:\n")
|
||||||
add_sis(sym.weak_rev_dep, 1,
|
s += sis(sym.weak_rev_dep, 1,
|
||||||
"Symbols currently m-implying this symbol:\n")
|
"Symbols currently m-implying this symbol:\n")
|
||||||
add_sis(sym.weak_rev_dep, 0,
|
s += sis(sym.weak_rev_dep, 0,
|
||||||
"Symbols currently n-implying this symbol (no effect):\n")
|
"Symbols currently n-implying this symbol (no effect):\n")
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@ -2727,7 +2742,7 @@ def _kconfig_def_info(item):
|
||||||
|
|
||||||
nodes = [item] if isinstance(item, MenuNode) else item.nodes
|
nodes = [item] if isinstance(item, MenuNode) else item.nodes
|
||||||
|
|
||||||
s = "Kconfig definition{}, with propagated dependencies\n" \
|
s = "Kconfig definition{}, with parent deps. propagated to 'depends on'\n" \
|
||||||
.format("s" if len(nodes) > 1 else "")
|
.format("s" if len(nodes) > 1 else "")
|
||||||
s += (len(s) - 1)*"="
|
s += (len(s) - 1)*"="
|
||||||
|
|
||||||
|
@ -2740,7 +2755,7 @@ def _kconfig_def_info(item):
|
||||||
.format(node.filename, node.linenr,
|
.format(node.filename, node.linenr,
|
||||||
_include_path_info(node),
|
_include_path_info(node),
|
||||||
_menu_path_info(node),
|
_menu_path_info(node),
|
||||||
textwrap.indent(node.custom_str(_name_and_val_str), " "))
|
_indent(node.custom_str(_name_and_val_str), 2))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -2769,7 +2784,14 @@ def _menu_path_info(node):
|
||||||
path = " -> " + (node.prompt[0] if node.prompt else
|
path = " -> " + (node.prompt[0] if node.prompt else
|
||||||
standard_sc_expr_str(node.item)) + path
|
standard_sc_expr_str(node.item)) + path
|
||||||
|
|
||||||
return "(top menu)" + path
|
return "(Top)" + path
|
||||||
|
|
||||||
|
|
||||||
|
def _indent(s, n):
|
||||||
|
# Returns 's' with each line indented 'n' spaces. textwrap.indent() is not
|
||||||
|
# available in Python 2 (it's 3.3+).
|
||||||
|
|
||||||
|
return "\n".join(n*" " + line for line in s.split("\n"))
|
||||||
|
|
||||||
|
|
||||||
def _name_and_val_str(sc):
|
def _name_and_val_str(sc):
|
||||||
|
@ -2952,8 +2974,7 @@ def _node_str(node):
|
||||||
# Print "(NEW)" next to symbols without a user value (from e.g. a
|
# Print "(NEW)" next to symbols without a user value (from e.g. a
|
||||||
# .config), but skip it for choice symbols in choices in y mode,
|
# .config), but skip it for choice symbols in choices in y mode,
|
||||||
# and for symbols of UNKNOWN type (which generate a warning though)
|
# and for symbols of UNKNOWN type (which generate a warning though)
|
||||||
if sym.user_value is None and \
|
if sym.user_value is None and sym.orig_type and \
|
||||||
sym.type != UNKNOWN and \
|
|
||||||
not (sym.choice and sym.choice.tri_value == 2):
|
not (sym.choice and sym.choice.tri_value == 2):
|
||||||
|
|
||||||
s += " (NEW)"
|
s += " (NEW)"
|
||||||
|
@ -3005,10 +3026,10 @@ def _value_str(node):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# Wouldn't normally happen, and generates a warning
|
# Wouldn't normally happen, and generates a warning
|
||||||
if item.type == UNKNOWN:
|
if not item.orig_type:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if item.type in (STRING, INT, HEX):
|
if item.orig_type in (STRING, INT, HEX):
|
||||||
return "({})".format(item.str_value)
|
return "({})".format(item.str_value)
|
||||||
|
|
||||||
# BOOL or TRISTATE
|
# BOOL or TRISTATE
|
||||||
|
@ -3042,28 +3063,26 @@ def _check_valid(sym, s):
|
||||||
# Returns True if the string 's' is a well-formed value for 'sym'.
|
# Returns True if the string 's' is a well-formed value for 'sym'.
|
||||||
# Otherwise, displays an error and returns False.
|
# Otherwise, displays an error and returns False.
|
||||||
|
|
||||||
if sym.type not in (INT, HEX):
|
if sym.orig_type not in (INT, HEX):
|
||||||
# Anything goes for non-int/hex symbols
|
# Anything goes for non-int/hex symbols
|
||||||
return True
|
return True
|
||||||
|
|
||||||
base = 10 if sym.type == INT else 16
|
base = 10 if sym.orig_type == INT else 16
|
||||||
try:
|
try:
|
||||||
int(s, base)
|
int(s, base)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
_error("'{}' is a malformed {} value"
|
_error("'{}' is a malformed {} value"
|
||||||
.format(s, TYPE_TO_STR[sym.type]))
|
.format(s, TYPE_TO_STR[sym.orig_type]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for low_sym, high_sym, cond in sym.ranges:
|
for low_sym, high_sym, cond in sym.ranges:
|
||||||
if expr_value(cond):
|
if expr_value(cond):
|
||||||
low = int(low_sym.str_value, base)
|
low_s = low_sym.str_value
|
||||||
val = int(s, base)
|
high_s = high_sym.str_value
|
||||||
high = int(high_sym.str_value, base)
|
|
||||||
|
|
||||||
if not low <= val <= high:
|
if not int(low_s, base) <= int(s, base) <= int(high_s, base):
|
||||||
_error("{} is outside the range {}-{}"
|
_error("{} is outside the range {}-{}"
|
||||||
.format(s, low_sym.str_value, high_sym.str_value))
|
.format(s, low_s, high_s))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -3075,7 +3094,7 @@ def _range_info(sym):
|
||||||
# Returns a string with information about the valid range for the symbol
|
# Returns a string with information about the valid range for the symbol
|
||||||
# 'sym', or None if 'sym' doesn't have a range
|
# 'sym', or None if 'sym' doesn't have a range
|
||||||
|
|
||||||
if sym.type in (INT, HEX):
|
if sym.orig_type in (INT, HEX):
|
||||||
for low, high, cond in sym.ranges:
|
for low, high, cond in sym.ranges:
|
||||||
if expr_value(cond):
|
if expr_value(cond):
|
||||||
return "Range: {}-{}".format(low.str_value, high.str_value)
|
return "Range: {}-{}".format(low.str_value, high.str_value)
|
||||||
|
@ -3102,7 +3121,17 @@ def _is_num(name):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _get_wch_compat(win):
|
def _getch_compat(win):
|
||||||
|
# Uses get_wch() if available (Python 3.3+) and getch() otherwise. Also
|
||||||
|
# handles a PDCurses resizing quirk.
|
||||||
|
|
||||||
|
if hasattr(win, "get_wch"):
|
||||||
|
c = win.get_wch()
|
||||||
|
else:
|
||||||
|
c = win.getch()
|
||||||
|
if 0 <= c <= 255:
|
||||||
|
c = chr(c)
|
||||||
|
|
||||||
# Decent resizing behavior on PDCurses requires calling resize_term(0, 0)
|
# Decent resizing behavior on PDCurses requires calling resize_term(0, 0)
|
||||||
# after receiving KEY_RESIZE, while ncurses (usually) handles terminal
|
# after receiving KEY_RESIZE, while ncurses (usually) handles terminal
|
||||||
# resizing automatically in get(_w)ch() (see the end of the
|
# resizing automatically in get(_w)ch() (see the end of the
|
||||||
|
@ -3111,8 +3140,6 @@ def _get_wch_compat(win):
|
||||||
# resize_term(0, 0) reliably fails and does nothing on ncurses, so this
|
# resize_term(0, 0) reliably fails and does nothing on ncurses, so this
|
||||||
# hack gives ncurses/PDCurses compatibility for resizing. I don't know
|
# hack gives ncurses/PDCurses compatibility for resizing. I don't know
|
||||||
# whether it would cause trouble for other implementations.
|
# whether it would cause trouble for other implementations.
|
||||||
|
|
||||||
c = win.get_wch()
|
|
||||||
if c == curses.KEY_RESIZE:
|
if c == curses.KEY_RESIZE:
|
||||||
try:
|
try:
|
||||||
curses.resize_term(0, 0)
|
curses.resize_term(0, 0)
|
||||||
|
|
Reference in a new issue