summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Magnusson <ulfalizer@gmail.com>2018-08-10 00:04:12 +0200
committerUlf Magnusson <ulfalizer@gmail.com>2018-08-10 04:57:13 +0200
commit1f864b104a1f64b1c72ab13422070f6ad7cad225 (patch)
treebdf511b4080b9b7085a3f5a0db2101434c05367c
parent9f2c55dab1d966d5a759722b4939f1f2c30e5e5f (diff)
Support custom printing of symbols/choices in expressions
Allow custom output formats for symbols/choices when turning expressions into strings, via a user-supplied callback function (sc_str_fn). This makes things like turning symbols into links in generated documentation and displaying symbol values in the menuconfig interface less hacky to implement. Two new Symbol/Choice.custom_str() functions were added, as passing extra arguments to __str__() is awkward.
-rw-r--r--kconfiglib.py190
-rw-r--r--testsuite.py81
2 files changed, 192 insertions, 79 deletions
diff --git a/kconfiglib.py b/kconfiglib.py
index 0da2735..a3247b2 100644
--- a/kconfiglib.py
+++ b/kconfiglib.py
@@ -3786,7 +3786,14 @@ class Symbol(object):
An empty string is returned for undefined and constant symbols.
"""
- return "\n".join(str(node) for node in self.nodes)
+ return self.custom_str(standard_sc_str_fn)
+
+ def custom_str(self, sc_str_fn):
+ """
+ Works like Symbol.__str__(), but allows a custom format to be used for
+ all symbol/choice references. See expr_str().
+ """
+ return "\n".join(node.custom_str(sc_str_fn) for node in self.nodes)
#
# Private methods
@@ -4318,8 +4325,7 @@ class Choice(object):
"""
fields = []
- fields.append("choice" if self.name is None else \
- "choice " + self.name)
+ fields.append("choice " + self.name if self.name else "choice")
fields.append(TYPE_TO_STR[self.type])
for node in self.nodes:
@@ -4361,7 +4367,14 @@ class Choice(object):
See Symbol.__str__() as well.
"""
- return "\n".join(str(node) for node in self.nodes)
+ return self.custom_str(standard_sc_str_fn)
+
+ def custom_str(self, sc_str_fn):
+ """
+ Works like Choice.__str__(), but allows a custom format to be used for
+ all symbol/choice references. See expr_str().
+ """
+ return "\n".join(node.custom_str(sc_str_fn) for node in self.nodes)
#
# Private methods
@@ -4709,24 +4722,31 @@ class MenuNode(object):
node are shown on all menu nodes ('option env=...', 'optional' for
choices, etc.).
"""
+ return self.custom_str(standard_sc_str_fn)
- return self._menu_comment_node_str() \
+ def custom_str(self, sc_str_fn):
+ """
+ Works like MenuNode.__str__(), but allows a custom format to be used
+ for all symbol/choice references. See expr_str().
+ """
+ return self._menu_comment_node_str(sc_str_fn) \
if self.item in (MENU, COMMENT) else \
- self._sym_choice_node_str()
+ self._sym_choice_node_str(sc_str_fn)
- def _menu_comment_node_str(self):
+ def _menu_comment_node_str(self, sc_str_fn):
s = '{} "{}"\n'.format("menu" if self.item == MENU else "comment",
self.prompt[0])
if self.dep is not self.kconfig.y:
- s += "\tdepends on {}\n".format(expr_str(self.dep))
+ s += "\tdepends on {}\n".format(expr_str(self.dep, sc_str_fn))
if self.item == MENU and self.visibility is not self.kconfig.y:
- s += "\tvisible if {}\n".format(expr_str(self.visibility))
+ s += "\tvisible if {}\n".format(expr_str(self.visibility,
+ sc_str_fn))
return s
- def _sym_choice_node_str(self):
+ def _sym_choice_node_str(self, sc_str_fn):
lines = []
def indent_add(s):
@@ -4734,66 +4754,65 @@ class MenuNode(object):
def indent_add_cond(s, cond):
if cond is not self.kconfig.y:
- s += " if " + expr_str(cond)
+ s += " if " + expr_str(cond, sc_str_fn)
indent_add(s)
- if isinstance(self.item, (Symbol, Choice)):
- sc = self.item
+ sc = self.item
- if isinstance(sc, Symbol):
- lines.append(
- ("menuconfig " if self.is_menuconfig else "config ")
- + sc.name)
- else:
- lines.append(
- "choice" if sc.name is None else "choice " + sc.name)
+ if isinstance(sc, Symbol):
+ lines.append(
+ ("menuconfig " if self.is_menuconfig else "config ")
+ + sc.name)
+ else:
+ lines.append("choice " + sc.name if sc.name else "choice")
- if sc.orig_type != UNKNOWN:
- indent_add(TYPE_TO_STR[sc.orig_type])
+ if sc.orig_type != UNKNOWN:
+ indent_add(TYPE_TO_STR[sc.orig_type])
- if self.prompt:
- indent_add_cond(
- 'prompt "{}"'.format(escape(self.prompt[0])),
- self.prompt[1])
+ if self.prompt:
+ indent_add_cond(
+ 'prompt "{}"'.format(escape(self.prompt[0])),
+ self.prompt[1])
- if isinstance(sc, Symbol):
- if sc.is_allnoconfig_y:
- indent_add("option allnoconfig_y")
+ if isinstance(sc, Symbol):
+ if sc.is_allnoconfig_y:
+ indent_add("option allnoconfig_y")
- if sc is sc.kconfig.defconfig_list:
- indent_add("option defconfig_list")
+ if sc is sc.kconfig.defconfig_list:
+ indent_add("option defconfig_list")
- if sc.env_var is not None:
- indent_add('option env="{}"'.format(sc.env_var))
+ if sc.env_var is not None:
+ indent_add('option env="{}"'.format(sc.env_var))
- if sc is sc.kconfig.modules:
- indent_add("option modules")
+ if sc is sc.kconfig.modules:
+ indent_add("option modules")
- for low, high, cond in self.ranges:
- indent_add_cond(
- "range {} {}".format(expr_str(low), expr_str(high)),
- cond)
+ for low, high, cond in self.ranges:
+ indent_add_cond(
+ "range {} {}".format(sc_str_fn(low), sc_str_fn(high)),
+ cond)
- for default, cond in self.defaults:
- indent_add_cond("default " + expr_str(default), cond)
+ for default, cond in self.defaults:
+ indent_add_cond("default " + expr_str(default, sc_str_fn),
+ cond)
- if isinstance(sc, Choice) and sc.is_optional:
- indent_add("optional")
+ if isinstance(sc, Choice) and sc.is_optional:
+ indent_add("optional")
- if isinstance(sc, Symbol):
- for select, cond in self.selects:
- indent_add_cond("select " + expr_str(select), cond)
+ if isinstance(sc, Symbol):
+ for select, cond in self.selects:
+ indent_add_cond("select " + sc_str_fn(select), cond)
- for imply, cond in self.implies:
- indent_add_cond("imply " + expr_str(imply), cond)
+ for imply, cond in self.implies:
+ indent_add_cond("imply " + sc_str_fn(imply), cond)
- if self.dep is not sc.kconfig.y:
- indent_add("depends on " + expr_str(self.dep))
+ if self.dep is not sc.kconfig.y:
+ indent_add("depends on " + expr_str(self.dep, sc_str_fn))
- if self.help is not None:
- indent_add("help")
- for line in self.help.splitlines():
- indent_add(" " + line)
+ if self.help is not None:
+ indent_add("help")
+ for line in self.help.splitlines():
+ indent_add(" " + line)
return "\n".join(lines) + "\n"
@@ -4909,42 +4928,61 @@ def expr_value(expr):
_internal_error("Internal error while evaluating expression: "
"unknown operation {}.".format(expr[0]))
-def expr_str(expr):
+def standard_sc_str_fn(sc):
+ """
+ Standard symbol/choice printing function. Uses plain Kconfig syntax, and
+ displays choices as <choice> (or <choice NAME>, for named choices).
+
+ See expr_str().
+ """
+ if isinstance(sc, Symbol):
+ return '"{}"'.format(escape(sc.name)) if sc.is_constant else sc.name
+
+ # Choice
+ return "<choice {}>".format(sc.name) if sc.name else "<choice>"
+
+def expr_str(expr, sc_str_fn=standard_sc_str_fn):
"""
Returns the string representation of the expression 'expr', as in a Kconfig
file.
Passing subexpressions of expressions to this function works as expected.
- """
- if isinstance(expr, Symbol):
- if expr.is_constant:
- return '"{}"'.format(escape(expr.name))
- return expr.name
- if isinstance(expr, Choice):
- if expr.name is not None:
- return "<choice {}>".format(expr.name)
- return "<choice>"
+ sc_str_fn (default: standard_sc_str_fn):
+ This function is called for every symbol/choice (hence "sc") appearing in
+ the expression, with the symbol/choice as the argument. It is expected to
+ return a string to be used for the symbol/choice.
+
+ This can be used e.g. to turn symbols/choices into links when generating
+ documentation, or for printing the value of each symbol/choice after it.
+
+ Note that quoted values are represented as constants symbols
+ (Symbol.is_constant == True).
+ """
+ if isinstance(expr, (Symbol, Choice)):
+ return sc_str_fn(expr)
if expr[0] == NOT:
if isinstance(expr[1], tuple):
- return "!({})".format(expr_str(expr[1]))
- return "!" + expr_str(expr[1]) # Symbol
+ return "!({})".format(expr_str(expr[1], sc_str_fn))
+ return "!" + sc_str_fn(expr[1]) # Symbol
if expr[0] == AND:
- return "{} && {}".format(_parenthesize(expr[1], OR),
- _parenthesize(expr[2], OR))
+ return "{} && {}".format(_parenthesize(expr[1], OR, sc_str_fn),
+ _parenthesize(expr[2], OR, sc_str_fn))
if expr[0] == OR:
# This turns A && B || C && D into "(A && B) || (C && D)", which is
# redundant, but more readable
- return "{} || {}".format(_parenthesize(expr[1], AND),
- _parenthesize(expr[2], AND))
+ return "{} || {}".format(_parenthesize(expr[1], AND, sc_str_fn),
+ _parenthesize(expr[2], AND, sc_str_fn))
# Relation
- return "{} {} {}".format(expr_str(expr[1]),
- _REL_TO_STR[expr[0]],
- expr_str(expr[2]))
+ #
+ # Relation operands are always symbols (quoted strings are constant
+ # symbols)
+ return "{} {} {}".format(sc_str_fn(expr[1]), _REL_TO_STR[expr[0]],
+ sc_str_fn(expr[2]))
def expr_items(expr):
"""
@@ -5106,12 +5144,12 @@ def _make_depend_on(sc, expr):
# Non-constant symbol, or choice
expr._dependents.add(sc)
-def _parenthesize(expr, type_):
+def _parenthesize(expr, type_, sc_str_fn):
# expr_str() helper. Adds parentheses around expressions of type 'type_'.
if isinstance(expr, tuple) and expr[0] == type_:
- return "({})".format(expr_str(expr))
- return expr_str(expr)
+ return "({})".format(expr_str(expr, sc_str_fn))
+ return expr_str(expr, sc_str_fn)
def _indentation(line):
# Returns the length of the line's leading whitespace, treating tab stops
diff --git a/testsuite.py b/testsuite.py
index 6b81d5b..a521128 100644
--- a/testsuite.py
+++ b/testsuite.py
@@ -467,11 +467,14 @@ def run_selftests():
verify_eval_bad("|| X")
- print("Testing Symbol.__str__() and def_{int,hex,string}")
+ print("Testing Symbol.__str__()/custom_str() and def_{int,hex,string}")
def verify_str(item, s):
verify_equal(str(item), s[1:])
+ def verify_custom_str(item, s):
+ verify_equal(item.custom_str(lambda sc: "[{}]".format(sc.name)), s[1:])
+
c = Kconfig("Kconfiglib/tests/Kstr", warn=False)
c.modules.set_value(2)
@@ -524,6 +527,35 @@ config ADVANCED
second help text
""")
+ verify_custom_str(c.syms["ADVANCED"], """
+config ADVANCED
+ tristate
+ prompt "prompt" if [DEP]
+ default [DEFAULT_1]
+ default [DEFAULT_2] if [DEP]
+ select [SELECTED_1]
+ select [SELECTED_2] if [DEP]
+ imply [IMPLIED_1]
+ imply [IMPLIED_2] if [DEP]
+ help
+ first help text
+
+config ADVANCED
+ tristate
+ prompt "prompt 2"
+
+menuconfig ADVANCED
+ tristate
+ prompt "prompt 3"
+
+config ADVANCED
+ tristate
+ depends on ([A] || ![B] || ([C] && [D]) || !([E] && [F]) || [G] = [H] || ([I] && ![J] && ([K] || [L]) && !([M] || [N]) && [O] = [P])) && [DEP4] && [DEP3]
+ help
+ second help text
+""")
+
+
verify_str(c.syms["ONLY_DIRECT_DEPS"], """
config ONLY_DIRECT_DEPS
int
@@ -626,8 +658,23 @@ config CORRECT_PROP_LOCS_INT
depends on LOC_2
""")
+ verify_custom_str(c.syms["CORRECT_PROP_LOCS_INT"], """
+config CORRECT_PROP_LOCS_INT
+ int
+ range [1] [2] if [LOC_1]
+ range [3] [4] if [LOC_1]
+ depends on [LOC_1]
- print("Testing Choice.__str__()")
+config CORRECT_PROP_LOCS_INT
+ int
+ range [5] [6] if [LOC_2]
+ range [7] [8] if [LOC_2]
+ depends on [LOC_2]
+""")
+
+
+
+ print("Testing Choice.__str__()/custom_str()")
verify_str(c.named_choices["CHOICE"], """
choice CHOICE
@@ -661,8 +708,25 @@ choice CORRECT_PROP_LOCS_CHOICE
depends on LOC_3
""")
+ verify_custom_str(c.named_choices["CORRECT_PROP_LOCS_CHOICE"], """
+choice CORRECT_PROP_LOCS_CHOICE
+ bool
+ default [CHOICE_3] if [LOC_1]
+ depends on [LOC_1]
+
+choice CORRECT_PROP_LOCS_CHOICE
+ bool
+ default [CHOICE_4] if [LOC_2]
+ depends on [LOC_2]
+
+choice CORRECT_PROP_LOCS_CHOICE
+ bool
+ default [CHOICE_5] if [LOC_3]
+ depends on [LOC_3]
+""")
+
- print("Testing MenuNode.__str__() for menus and comments")
+ print("Testing MenuNode.__str__()/custom_str() for menus and comments")
verify_str(c.syms["SIMPLE_MENU_HOOK"].nodes[0].next, """
menu "simple menu"
@@ -674,6 +738,12 @@ menu "advanced menu"
visible if B && (C || D)
""")
+ verify_custom_str(c.syms["ADVANCED_MENU_HOOK"].nodes[0].next, """
+menu "advanced menu"
+ depends on [A]
+ visible if [B] && ([C] || [D])
+""")
+
verify_str(c.syms["SIMPLE_COMMENT_HOOK"].nodes[0].next, """
comment "simple comment"
""")
@@ -683,6 +753,11 @@ comment "advanced comment"
depends on A && B
""")
+ verify_custom_str(c.syms["ADVANCED_COMMENT_HOOK"].nodes[0].next, """
+comment "advanced comment"
+ depends on [A] && [B]
+""")
+
print("Testing Symbol.__repr__()")