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 <niels@thykier.net>
pre-rebase-2016-10-25
Niels Thykier 9 years ago
parent 13417c18e4
commit 948d15d536

@ -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, '<stdin>', [' '.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, '<stdin>', [' '.join(user_input)])
self.write_excuses()
try:
readline.write_history_file(histfile)
except IOError as e:

@ -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")

Loading…
Cancel
Save