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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
infra / config / lib / args.star [blame]
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Library containing utilities for handling args in libraries."""
def _sentinel(tag):
return struct(**{tag: tag})
# A sentinel value indicating a value that will be computed based off of some
# other values
COMPUTE = _sentinel("__compute__")
# A sentinel value to be used as a default so that it can be distinguished
# between an unspecified value and an explicit value of None
DEFAULT = _sentinel("__default__")
# A sentinel value that can be passed as the merge argument to
# get_value/get_value_from_kwargs to have the provided value merged as a list.
MERGE_LIST = _sentinel("__merge_list__")
# A sentinel value that can be passed as the merge argument to
# get_value/get_value_from_kwargs to have the provided value merged as a dict.
MERGE_DICT = _sentinel("__merge_dict__")
_IGNORE_DEFAULT_ATTR = "_ignore_default"
def defaults(extends = None, **vars):
"""Define a structure provides a group of module-level defaults.
Args:
extends: A struct containing `lucicfg.var` attributes to add to the
resulting struct.
**vars: Defaults to define. Each entry results in a `lucicfg.var` attribute
being added to the resulting struct. The name of the attribute is the
keyword and the default value of the `lucicfg.var` is the keyword's value.
Returns:
A struct containing `lucicfg.var` attributes providing the module level
defaults and the following methods:
* get_value(name, value, merge=None) - Gets the value of an argument. The
behavior of the function depends on the value of the `merge` argument:
* None (default) - If `value` is not `DEFAULT`, `value` is returned.
Otherwise, the module-level default for `name` is returned.
* MERGE_LIST - If `value` is wrapped using `ignore_defaults`, the result
of calling `listify(value.value)` is returned. Otherwise, the result
of calling `listify` with the module-level default for `name` and
`value` is returned.
* MERGE_DICT - If `value` is wrapped using `ignore_defaults`,
`value.value` is returned. Otherwise, the returned value will be the
module-level default for `name` updated with `value`.
* get_value_from_kwargs(name, kwargs, merge=None) - Gets the value of a keyword
argument.
* set(**kwargs) - Sets module-level defaults. For each keyword, sets the
module-level default with the keyword as the name to the value of the
keyword.
"""
methods = ["get_value", "get_value_from_kwargs", "set"]
for m in methods:
if m in vars:
fail("{!r} can't be used as the name of a default: it is a method"
.format(a))
vars = {k: lucicfg.var(default = v) for k, v in vars.items()}
for a in dir(extends):
if a not in methods:
vars[a] = getattr(extends, a)
def get_value(name, value, merge = None):
default = vars[name].get()
ignore_default, value = _should_ignore_default(value)
if not merge:
if ignore_default:
fail("attribute {!r} does not merge with the default, ignore_defaults cannot be used".format(name))
if value != DEFAULT:
return value
return default
if merge == MERGE_DICT:
if value == DEFAULT:
value = {}
if value and type(value) != type({}):
fail("attribute {!r} requires a dict value or None, got {!r}".format(name, value))
value = value or {}
if ignore_default:
return value
new_value = default or {}
new_value.update(value)
return new_value
if merge == MERGE_LIST:
if value == DEFAULT:
value = []
if ignore_default:
return listify(value)
return listify(default, value)
fail("unknown merge value: {}".format(merge))
def get_value_from_kwargs(name, kwargs, merge = None):
return get_value(name, kwargs.get(name, DEFAULT), merge = merge)
def set(**kwargs):
for k, v in kwargs.items():
vars[k].set(v)
return struct(
get_value = get_value,
get_value_from_kwargs = get_value_from_kwargs,
set = set,
**vars
)
def ignore_default(value):
"""Wraps a value to ignore defaults for the argument.
For arguments that merge the provided value with a module-level default,
this provides a way to explicitly set an exact value. It is an error to use
this for attributes that don't merge with the module-level default.
"""
return struct(
_ignore_default = True,
value = value,
)
def _should_ignore_default(value):
ignore_default = getattr(value, _IGNORE_DEFAULT_ATTR, False)
if ignore_default:
value = value.value
return ignore_default, value
def listify(*args):
"""Create a single list from multiple arguments.
Each argument can be either a single element or a list of elements. A single
element will appear as an element in the resulting list iff it is non-None.
A list of elements will have all non-None elements appear in the resulting
list.
Args:
*args: The arguments to merge into a list.
Returns:
A list composed of the pased in arguments.
"""
wrap_in_ignore_default = False
l = []
for a in args:
should_ignore_default, a = _should_ignore_default(a)
if should_ignore_default:
wrap_in_ignore_default = True
if type(a) != type([]):
a = [a]
for e in a:
should_ignore_default, val = _should_ignore_default(e)
if should_ignore_default:
wrap_in_ignore_default = True
if val != None:
l.append(val)
if wrap_in_ignore_default:
return ignore_default(l)
return l
args = struct(
COMPUTE = COMPUTE,
DEFAULT = DEFAULT,
MERGE_LIST = MERGE_LIST,
MERGE_DICT = MERGE_DICT,
defaults = defaults,
ignore_default = ignore_default,
listify = listify,
)