Skip to content

ControlsTable

yex.control.ControlsTable(**kwargs) #

A set of named controls, which live inside a document.

Initially the set is empty; you can add to it either using the insert method, or the |= operator.

Three ways to store a value#

Each value in a ControlsTable is the control named by the key.

However, to avoid the performance hit of instantiating hundreds of control objects on startup, some of the values in a ControlsTable may be classes. They will be instantiated on first use. Keyword args passed to ControlsTable's constructor are passed on into these instances' constructors.

Values may also be dicts; these will be deserialised to macros on first use.

Source code in yex/control/table.py
35
36
37
38
39
40
41
42
43
44
45
def __init__(self, **kwargs):
    self.contents: Mapping[str, Any] = {}
    """
    Everything in this table.
    """

    self.kwargs: Mapping[str, Any] = kwargs
    """
    A copy of the constructor's kwargs, to pass on to the
    constructors of any controls we instantiate.
    """

contents = {} instance-attribute #

Everything in this table.

kwargs = kwargs instance-attribute #

A copy of the constructor's kwargs, to pass on to the constructors of any controls we instantiate.

__contains__(field) #

Checks whether there's a control with a particular name.

Source code in yex/control/table.py
267
268
269
270
271
def __contains__(self, field):
    """
    Checks whether there's a control with a particular name.
    """
    return field in self.contents

__ior__(to_merge) #

The |= operator. It merges us with another ControlsTable, or a dict mapping strings to commands.

Source code in yex/control/table.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def __ior__(self, to_merge):
    """
    The |= operator. It merges us with
    another ControlsTable, or a dict mapping strings to commands.
    """
    if isinstance(to_merge, yex.style.Style):
        self.contents |= to_merge.CONTROLS

    elif isinstance(to_merge, dict):
        self.contents |= to_merge

    elif isinstance(to_merge, ControlsTable):
        self.contents |= to_merge.contents

    else:
        raise TypeError()

    return self

get(field, param_control=False) #

Returns the control with the given name.

Parameters:

Name Type Description Default
field str

the name of the control to find.

required
param_control bool

if True, requests for parameter controls return the control object itself, as with any other control. If False, which is the default, they return the value stored in the control object; this is probably what you wanted.

False

Raises:

Type Description
KeyError

if there's no such control

Source code in yex/control/table.py
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
def get(self,
        field: str,
        param_control: bool = False,
        ):
    r"""
    Returns the control with the given name.

    Args:
        field: the name of the control to find.
        param_control: if True, requests for
            [parameter controls](yex.control.Parameter.md)
            return the control object itself, as with any other control.
            If False, which is the default, they return the value
            stored in the control object; this is probably what
            you wanted.

    Raises:
        KeyError: if there's no such control
    """

    if field not in self.contents:
        raise KeyError(field)

    result = self._get_and_maybe_instantiate(field)

    if isinstance(result, Parameter):

        if param_control:
            logger.debug(
                    "get parameter control %s==%s (rather than its value)",
                    field, result)
            return result
        else:
            logger.debug(
                    "get value of parameter %s==%s",
                    field, result)
            return result.value
    else:
        logger.debug(
                "get control %s==%s",
                field, str(result))
        return result

set(field, value, param_control=False) #

Give something a name.

This is more complicated than may seem necessary. It's designed like this so that deserialisation can involve nothing but calls to __setitem__.

Parameters:

Name Type Description Default
field str

the name to give

required
value typing.Any

our behaviour depends on the type: - if Control, and param_control is True, and field is the name of an existing control: and that control is a parameter, overwrite that parameter with value. - if Control, give that control the name field. - if None, delete the name field from the list. - if dict, set the value of the control named "field". Possible fields in this dict are described below. - otherwise, if field is the name of an existing control, and that control is a parameter, set the value of the parameter to value. - otherwise, raise ValueError.

required

Deserialising controls#

Possible fields in value, hereinafter "v", if it's a dict:

  • If v['control'] exists, it's the name of the class to be instantiated.
  • If that's a parameter, v['value'] can optionally be used to set its value at the same time.
  • Otherwise, if v['font'] exists, this is a FontSetter, and v['font'] is the name of the font.
  • Otherwise, if v['macro'] exists, this is a Macro, and v['macro'] is the macro definition.
  • v['flags'] is an optional string, a space-separated list of one or more of ("long", "outer").
  • v['starts_at'] is the position of the start of the macro definition and is optional.
  • v['parameters'] is optional and describes the parameters. If it's an integer, it's the number of parameters. Otherwise, it's a list of the strings which delimit the arguments on a call; there's one more string than there are parameters, because there may be delimiters between the macro name and its first parameter.

Raises:

Type Description
ValueError

if value doesn't follow the rules given above

KeyError

if field needs to name an existing control, but there's no control with that name.

RemovingNonexistentControlError

if value was None, but field doesn't name an existing control

Source code in yex/control/table.py
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def set(self,
        field: str,
        value: Any,
        param_control: bool = False,
        ):
    """
    Give something a name.

    This is more complicated than may seem necessary.
    It's designed like this so that deserialisation can
    involve nothing but calls to `__setitem__`.

    Args:
        field: the name to give
        value: our behaviour depends on the type:
            - if Control,
                and param_control is True,
                and `field` is the name of an existing control:
                and that control is a [parameter](yex.control.Parameter.md),
                overwrite that parameter with `value`.
            - if Control, give that control the name `field`.
            - if None, delete the name `field` from the list.
            - if dict, set the value of the control
                named "field". Possible fields in this dict
                are described below.
            - otherwise, if `field` is the name of an existing control,
                and that control is a [parameter](yex.control.Parameter.md),
                set the value of the parameter to `value`.
            - otherwise, raise ValueError.

    # Deserialising controls

    Possible fields in `value`, hereinafter "v", if it's a dict:

    - If v['control'] exists, it's the name of the class to be
      instantiated.
      -  If that's a parameter, v['value'] can optionally
      be used to set its value at the same time.
      - Otherwise, if v['font'] exists, this is a FontSetter, and
      v['font'] is the name of the font.
      - Otherwise, if v['macro'] exists, this is a Macro, and
      v['macro'] is the macro definition.
    - v['flags'] is an optional string, a space-separated list
       of one or more of ("long", "outer").
    - v['starts_at'] is the position of the start of the macro definition
       and is optional.
    - v['parameters'] is optional and describes the parameters. If it's
       an integer, it's the number of parameters. Otherwise, it's a list
       of the strings which delimit the arguments on a call; there's
       one more string than there are parameters, because there may
       be delimiters between the macro name and its first parameter.

    Raises:
        ValueError: if `value` doesn't follow the rules given above
        KeyError: if `field` needs to name an existing control,
            but there's no control with that name.
        RemovingNonexistentControlError: if `value` was None, but
            `field` doesn't name an existing control
    """

    if isinstance(value, dict):

        if 'control' in value:
            item = yex.control.Control.from_serial(value)
        elif 'font' in value:
            item = yex.control.Font.from_serial(value)
        elif 'macro' in value:
            item = yex.control.Macro.from_serial(value)
        else:
            raise ValueError(
                    "Don't know how to deserialise this: %s" % (
                        value,))

        logger.debug("setting control %s=%s, from: %s",
            field, item, value,
            )

        self.contents[field] = item

        return

    elif value is None:
        try:
            del self.contents[field]
        except KeyError:
            raise yex.exception.RemovingNonexistentControlError(
                    field = field,
                    )
        return

    if field in self.contents:
        current = self._get_and_maybe_instantiate(field)
    else:
        current = None

    if isinstance(current, Parameter) and not param_control:

        logger.debug("setting parameter %s=%s",
                field, value)
        current.value = value
        return

    try:
        value.__call__ # just lookup, don't call it
    except AttributeError:
        raise ValueError(
                f"Can't set control {field} to {value} "
                f"because that's not a control but a {type(value)}.")

    logger.debug("setting control %s=%s",
            field, value)

    if current is None:
        logger.debug("  -- overwriting %s",
                current)

    self.contents[field] = value

value() #

All the items in this table which don't have the default value.

This can be used to recreate the table.

Issues

Well, it could be used that way, if it was implemented. But it isn't.

Source code in yex/control/table.py
285
286
287
288
289
290
291
292
293
294
295
def value(self):
    """
    All the items in this table which don't have the default value.

    This can be used to recreate the table.

    Issues:
        Well, it _could_ be used that way, if it was implemented.
        But it isn't.
    """
    raise NotImplementedError()