diff options
| author | Ulf Magnusson <ulfalizer@gmail.com> | 2018-08-10 00:04:12 +0200 |
|---|---|---|
| committer | Ulf Magnusson <ulfalizer@gmail.com> | 2018-08-10 04:57:13 +0200 |
| commit | 1f864b104a1f64b1c72ab13422070f6ad7cad225 (patch) | |
| tree | bdf511b4080b9b7085a3f5a0db2101434c05367c | |
| parent | 9f2c55dab1d966d5a759722b4939f1f2c30e5e5f (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.py | 190 | ||||
| -rw-r--r-- | testsuite.py | 81 |
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__()") |
