From 948d15d5360abd7c0ed988affd2d47b8cf358d69 Mon Sep 17 00:00:00 2001 From: Niels Thykier Date: Sat, 2 Jul 2016 20:28:07 +0000 Subject: [PATCH] HintParser: Support adding new hints to the parser This includes refining "HINTS_ALL" to cover all hints added at runtime. Currently, it is not very useful. However, a later commit will allow policies to use this feature. Signed-off-by: Niels Thykier --- britney.py | 25 ++++++++++++++----------- hints.py | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/britney.py b/britney.py index 55630cb..8350111 100755 --- a/britney.py +++ b/britney.py @@ -69,7 +69,7 @@ instead explained in the chapter "Excuses Generation". = Excuses = An excuse is a detailed explanation of why a package can or cannot -be updated in the testing distribution from a newer package in +be updated in the testing distribution from a newer package in another distribution (like for example unstable). The main purpose of the excuses is to be written in an HTML file which will be published over HTTP. The maintainers will be able to parse it manually @@ -259,9 +259,10 @@ class Britney(object): For more documentation on this script, please read the Developers Reference. """ - HINTS_HELPERS = ("easy", "hint", "remove", "block", "block-udeb", "unblock", "unblock-udeb", "approve") + HINTS_HELPERS = ("easy", "hint", "remove", "block", "block-udeb", "unblock", "unblock-udeb", "approve", "remark") HINTS_STANDARD = ("urgent", "age-days") + HINTS_HELPERS - HINTS_ALL = ("force", "force-hint", "block-all") + HINTS_STANDARD + # ALL = {"force", "force-hint", "block-all"} | HINTS_STANDARD | registered policy hints (not covered above) + HINTS_ALL = ('ALL') def __init__(self): """Class constructor @@ -272,6 +273,7 @@ class Britney(object): # parse the command line arguments self.policies = [] + self._hint_parser = HintParser(self) self.__parse_arguments() MigrationItem.set_architectures(self.options.architectures) @@ -281,7 +283,6 @@ class Britney(object): self.binaries = {} self.all_selected = [] self.excuses = {} - self._hint_parser = HintParser(self) try: self.read_hints(self.options.hintsdir) @@ -442,6 +443,7 @@ class Britney(object): # minimum days for unstable-testing transition and the list of hints # are handled as an ad-hoc case MINDAYS = {} + self.HINTS = {'command-line': self.HINTS_ALL} with open(self.options.config, encoding='utf-8') as config: for line in config: @@ -2790,6 +2792,8 @@ class Britney(object): # so ensure readline does not split on these characters. readline.set_completer_delims(readline.get_completer_delims().replace('-', '').replace('/', '')) + known_hints = self._hint_parser.registered_hints + while True: # read the command from the command line try: @@ -2803,19 +2807,18 @@ class Britney(object): # quit the hint tester if user_input and user_input[0] in ('quit', 'exit'): break - elif user_input and user_input[0] in ('remove', 'approve', 'urgent', 'age-days', - 'block', 'block-udeb', 'unblock', 'unblock-udeb', - 'block-all', 'force'): - self._hint_parser.parse_hints('hint-tester', Britney.HINTS_ALL, '', [' '.join(user_input)]) - self.write_excuses() - # run a hint + # run a hint elif user_input and user_input[0] in ('easy', 'hint', 'force-hint'): try: self.do_hint(user_input[0], 'hint-tester', - [k.rsplit("/", 1) for k in user_input[1:] if "/" in k]) + [k.rsplit("/", 1) for k in user_input[1:] if "/" in k]) self.printuninstchange() except KeyboardInterrupt: continue + elif user_input and user_input[0] in known_hints: + self._hint_parser.parse_hints('hint-tester', self.HINTS_ALL, '', [' '.join(user_input)]) + self.write_excuses() + try: readline.write_history_file(histfile) except IOError as e: diff --git a/hints.py b/hints.py index c15986b..57f940f 100644 --- a/hints.py +++ b/hints.py @@ -14,6 +14,8 @@ from __future__ import print_function +from itertools import chain + from migrationitem import MigrationItem @@ -178,6 +180,46 @@ class HintParser(object): 'approve': 'unblock', } + @property + def registered_hints(self): + """A set of all known hints (and aliases thereof)""" + return set(chain(self._hint_table.keys(), self._aliases.keys())) + + def register_hint_type(self, hint_name, parser_function, *, min_args=1, aliases=None): + """Register a new hint that is supported by the parser + + This registers a new hint that can be parsed by the hint parser. All hints are single words with a + space-separated list of arguments (on a single line). The hint parser will do some basic processing, + the permission checking and minor validation on the hint before passing it on to the parser function + given. + + The parser_function will receive the following arguments: + * A hint collection + * Identifier of the entity providing the hint + * The hint_name (aliases will be mapped to the hint_name) + * Zero or more string arguments for the hint (so the function needs to use *args) + + The parser_function will then have to process the arguments and call the hint collection's "add_hint" + as needed. Example implementations include "split_into_one_hint_per_package", which is used by almost + all policy hints. + + :param hint_name: The name of the hint + :param parser_function: A function to add the hint + :param min_args: An optional positive integer (or 0) denoting the number of arguments the hint takes. + :param aliases: An optional iterable of aliases to the hint (use only for backwards compatibility) + """ + if min_args < 1: + raise ValueError("min_args must be at least 1") + if hint_name in self._hint_table: + raise ValueError("The hint type %s is already registered" % hint_name) + if hint_name in self._aliases: + raise ValueError("The hint type %s is already registered as an alias of %s" % ( + hint_name, self._aliases[hint_name])) + self._hint_table[hint_name] = (min_args, parser_function) + if aliases: + for alias in aliases: + self._aliases[alias] = hint_name + def parse_hints(self, who, permitted_hints, filename, lines): hint_table = self._hint_table line_no = 0 @@ -198,7 +240,7 @@ class HintParser(object): if hint_name not in hint_table: self.log("Unknown hint found in %s (line %d): '%s'" % (filename, line_no, line), type="W") continue - if hint_name not in permitted_hints: + if hint_name not in permitted_hints and 'ALL' not in permitted_hints: reason = 'The hint is not a part of the permitted hints for ' + who self.log("Ignoring \"%s\" hint from %s found in %s (line %d): %s" % ( hint_name, who, filename, line_no, reason), type="I")