1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
from .attributes import keymatch
def evaluate_keyed_by(value, item_name, attributes, defer=None):
"""
For values which can either accept a literal value, or be keyed by some
attributes, perform that lookup and return the result.
For example, given item::
by-test-platform:
macosx-10.11/debug: 13
win.*: 6
default: 12
a call to `evaluate_keyed_by(item, 'thing-name', {'test-platform': 'linux96')`
would return `12`.
The `item_name` parameter is used to generate useful error messages.
Items can be nested as deeply as desired::
by-test-platform:
win.*:
by-project:
ash: ..
cedar: ..
linux: 13
default: 12
The `defer` parameter allows evaluating a by-* entry at a later time. In the
example above it's possible that the project attribute hasn't been set
yet, in which case we'd want to stop before resolving that subkey and then
call this function again later. This can be accomplished by setting
`defer=["project"]` in this example.
"""
while True:
if not isinstance(value, dict) or len(value) != 1:
return value
value_key = next(iter(value))
if not value_key.startswith("by-"):
return value
keyed_by = value_key[3:] # strip off 'by-' prefix
if defer and keyed_by in defer:
return value
key = attributes.get(keyed_by)
alternatives = next(iter(value.values()))
if len(alternatives) == 1 and "default" in alternatives:
# Error out when only 'default' is specified as only alternatives,
# because we don't need to by-{keyed_by} there.
raise Exception(
"Keyed-by '{}' unnecessary with only value 'default' "
"found, when determining item {}".format(keyed_by, item_name)
)
if key is None:
if "default" in alternatives:
value = alternatives["default"]
continue
else:
raise Exception(
"No attribute {} and no value for 'default' found "
"while determining item {}".format(keyed_by, item_name)
)
matches = keymatch(alternatives, key)
if len(matches) > 1:
raise Exception(
"Multiple matching values for {} {!r} found while "
"determining item {}".format(keyed_by, key, item_name)
)
elif matches:
value = matches[0]
continue
raise Exception(
"No {} matching {!r} nor 'default' found while determining item {}".format(
keyed_by, key, item_name
)
)
|