Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/structure/global_options.py
8814 views
1
r"""
2
Global options
3
4
The :class:`GlobalOptions` class provides a generic mechanism for
5
setting and accessing **global** options for parents in one or several
6
related classes, typically for customizing the representation of their
7
elements. This class will eventually also support setting options on a
8
parent by parent basis.
9
10
.. SEEALSO::
11
12
For better examples of :class:`GlobalOptions` in action see
13
:meth:`sage.combinat.partition.Partitions.global_options` and
14
:meth:`sage.combinat.tableau.Tableaux.global_options`.
15
16
.. _construction_section:
17
18
Construction of options classes
19
-------------------------------
20
21
The general setup for creating a set of global options is:
22
23
.. code-block:: python
24
25
MyOptions=GlobalOptions('option name',
26
doc='Nice options',
27
first_option=dict(default='default value',
28
description='Changes the functionality of _repr_',
29
values=dict(with_bells='causes _repr_ to print with bells',
30
with_whistles='causes _repr_ to print with whistles',
31
...),
32
aliases=dict(bells='option1', whistles='option2', ...),
33
second_option=dict(...),
34
third_option=dict(),
35
end_doc='end of options documentation'
36
)
37
38
Each option is specified as a dictionary which describes the possible
39
values for the option and its documentation. The possible entries in this
40
dictionary are:
41
42
- ``alias`` -- Allows for several option values to do the same thing.
43
44
- ``alt_name`` -- An alternative name for this option.
45
46
- ``checker`` -- A validation function which returns whether a user
47
supplied value is valid or not. This is typically useful for large
48
lists of legal values such as `\NN`.
49
50
- ``default`` -- Gives the default value for the option.
51
52
- ``description`` -- A one line description of the option.
53
54
- ``link_to`` -- Links this option to another one in another set of
55
global options. This is used for example to allow
56
:class:`Partitions` and :class:`Tableaux` to share the same
57
``convention`` option.
58
59
- ``setter`` -- A function which is called **after** the value of the
60
option is changed.
61
62
- ``values`` -- A dictionary assigning each valid value for the option
63
to a short description of what it does.
64
65
- ``case_sensitive`` -- (Default: ``True``) ``True`` or ``False`` depending on
66
whether the values of the option are case sensitive.
67
68
For each option, either a complete list of possible values, via ``values``, or a
69
validation function, via ``checker``, must be given. The values can be quite
70
arbitrary, including user-defined functions which customize the default
71
behaviour of the classes such as the output of ``_repr_`` or :func:`latex`. See
72
:ref:`dispatcher` below, and :meth:`~GlobalOptions.dispatcher`, for more
73
information.
74
75
The documentation for the options is automatically constructed by combining the
76
description of each option with a header and footer which are given by the
77
following optional, but recommended, arguments:
78
79
- ``doc`` -- The top half of the documentation which appears before the
80
automatically generated list of options and their possible values.
81
82
- ``end_doc`` -- The second half of the documentation which appears
83
after the list of options and their values.
84
85
86
The basic structure for defining a :class:`GlobalOptions` class is best
87
illustrated by an example::
88
89
sage: from sage.structure.global_options import GlobalOptions
90
sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='The END!',
91
... entree=dict(default='soup',
92
... description='The first course of a meal',
93
... values=dict(soup='soup of the day', bread='oven baked'),
94
... alias=dict(rye='bread')),
95
... appetizer=dict(alt_name='entree'),
96
... main=dict(default='pizza', description='Main meal',
97
... values=dict(pizza='thick crust', pasta='penne arrabiata'),
98
... case_sensitive=False),
99
... dessert=dict(default='espresso', description='Dessert',
100
... values=dict(espresso='life begins again',
101
... cake='waist begins again',
102
... cream='fluffy, white stuff')),
103
... tip=dict(default=10, description='Reward for good service',
104
... checker=lambda tip: tip in range(0,20))
105
... )
106
sage: menu
107
options for menu
108
109
For more details see :class:`GlobalOptions`.
110
111
Accessing and setting option values
112
-----------------------------------
113
114
All options and their values, when they are strings, are forced to be lower
115
case. The values of an options class can be set and accessed by calling the
116
class or by treating the class as an array.
117
118
Continuing the example from :ref:`construction_section`::
119
120
sage: menu()
121
Current options for menu
122
- dessert: espresso
123
- entree: soup
124
- main: pizza
125
- tip: 10
126
sage: menu('dessert')
127
'espresso'
128
sage: menu['dessert']
129
'espresso'
130
131
Note that, provided there is no ambiguity, options and their values can be
132
abbreviated::
133
134
sage: menu['d']
135
'espresso'
136
sage: menu('m','t',des='esp', ent='sou') # get and set several values at once
137
['pizza', 10]
138
sage: menu(t=15); menu['tip']
139
15
140
sage: menu(e='s', m='Pi'); menu()
141
Current options for menu
142
- dessert: espresso
143
- entree: soup
144
- main: pizza
145
- tip: 15
146
sage: menu(m='P')
147
Traceback (most recent call last):
148
...
149
ValueError: P is not a valid value for main in the options for menu
150
151
152
Setter functions
153
----------------
154
155
Each option of a :class:`GlobalOptions` can be equipped with an optional setter
156
function which is called **after** the value of the option is changed. In the
157
following example, setting the option 'add' changes the state of the class by
158
setting an attribute in this class using a :func:`classmethod`. Note that the
159
options object is inserted after the creation of the class in order to access
160
the :func:`classmethod` as ``A.setter``::
161
162
sage: from sage.structure.global_options import GlobalOptions
163
sage: class A(SageObject):
164
... state = 0
165
... @classmethod
166
... def setter(cls, option, val):
167
... cls.state += int(val)
168
...
169
sage: A.options=GlobalOptions('A',
170
... add=dict(default=1,
171
... checker=lambda v: int(v)>0,
172
... description='An option with a setter',
173
... setter=A.setter))
174
sage: a = A(2); a.state
175
1
176
sage: a.options()
177
Current options for A
178
- add: 1
179
sage: a.options(add=4)
180
sage: a.state
181
5
182
sage: a.options()
183
Current options for A
184
- add: 4
185
186
Another alternative is to construct the options class inside the ``__init__``
187
method of the class ``A``.
188
189
Documentation for options
190
-------------------------
191
192
The documentation for a :class:`GlobalOptions` is automatically generated from
193
the supplied options. For example, the generated documentation for the options
194
``menu`` defined in :ref:`construction_section` is the following::
195
196
Fancy documentation
197
-------------------
198
199
OPTIONS:
200
201
- ``appetizer`` -- alternative name for ``entree``
202
203
- ``dessert`` -- (default: ``espresso``)
204
Dessert
205
206
- ``cake`` -- waist begins again
207
- ``cream`` -- fluffy, white stuff
208
- ``espresso`` -- life begins again
209
210
- ``entree`` -- (default: ``soup``)
211
The first course of a meal
212
213
- ``bread`` -- oven baked
214
- ``rye`` -- alias for bread
215
- ``soup`` -- soup of the day
216
217
- ``main`` -- (default: ``pizza``)
218
Main meal
219
220
- ``pasta`` -- penne arrabiata
221
- ``pizza`` -- thick crust
222
223
- tip -- (default: 10)
224
Reward for good service
225
226
The END!
227
228
See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.
229
230
In addition, help on each option, and its list of possible values, can be
231
obtained by (trying to) set the option equal to '?'::
232
233
sage: menu(des='?')
234
- ``dessert`` -- (default: ``espresso``)
235
Dessert
236
<BLANKLINE>
237
- ``cake`` -- waist begins again
238
- ``cream`` -- fluffy, white stuff
239
- ``espresso`` -- life begins again
240
<BLANKLINE>
241
Current value: espresso
242
243
.. _dispatcher:
244
245
Dispatchers
246
-----------
247
248
The whole idea of a :class:`GlobalOptions` class is that the options change the
249
default behaviour of the associated classes. This can be done either by simply
250
checking what the current value of the relevant option is. Another possibility
251
is to use the options class as a dispatcher to associated methods. To use the
252
dispatcher feature of a :class:`GlobalOptions` class it is necessary to implement
253
separate methods for each value of the option where the naming convention for
254
these methods is that they start with a common prefix and finish with the value
255
of the option.
256
257
If the value of a dispatchable option is set equal to a (user defined) function
258
then this function is called instead of a class method.
259
260
For example, the options ``MyOptions`` can be used to dispatch the ``_repr_``
261
method of the associated class ``MyClass`` as follows:
262
263
.. code-block:: python
264
265
class MyClass(...):
266
global_options=MyOptions
267
def _repr_(self):
268
return self.global_options.dispatch(self,'_repr_','first_option')
269
def _repr_with_bells(self):
270
print 'Bell!'
271
def _repr_with_whistles(self):
272
print 'Whistles!'
273
274
In this example, ``first_option`` is an option of ``MyOptions`` which takes
275
values ``bells``, ``whistles``, and so on. Note that it is necessary to make
276
``self``, which is an instance of ``MyClass``, an argument of the dispatcher
277
because :meth:`~GlobalOptions.dispatch()` is a method of :class:`GlobalOptions`
278
and not a method of ``MyClass``. Apart from ``MyOptions``, as it is a method of
279
this class, the arguments are the attached class (here ``MyClass``), the prefix
280
of the method of ``MyClass`` being dispatched, the option of ``MyOptions``
281
which controls the dispatching. All other arguments are passed through to the
282
corresponding methods of ``MyClass``. In general, a dispatcher is invoked as::
283
284
self.options.dispatch(self, dispatch_to, option, *args, **kargs)
285
286
Usually this will result in the method
287
``dispatch_to + '_' + MyOptions(options)`` of ``self`` being called with
288
arguments ``*args`` and ``**kargs`` (if ``dispatch_to[-1] == '_'`` then the
289
method ``dispatch_to + MyOptions(options)`` is called).
290
291
If ``MyOptions(options)`` is itself a function then the dispatcher will call
292
this function instead. In this way, it is possible to allow the user to
293
customise the default behaviour of this method. See
294
:meth:`~GlobalOptions.dispatch` for an example of how this can be achieved.
295
296
The dispatching capabilities of :class:`GlobalOptions` allows options to be
297
applied automatically without needing to parse different values of the option
298
(the cost is that there must be a method for each value). The dispatching
299
capabilities can also be used to make one option control several methods:
300
301
.. code-block:: python
302
303
def __le__(self, other):
304
return self.options.dispatch(self, '_le_','cmp', other)
305
def __ge__(self, other):
306
return self.options.dispatch(self, '_ge_','cmp', other)
307
def _le_option_a(self, other):
308
return ...
309
def _ge_option_a(self, other):
310
return ...
311
def _le_option_b(self, other):
312
return ...
313
def _ge_option_b(self, other):
314
return ...
315
316
See :meth:`~GlobalOptions.dispatch` for more details.
317
318
Doc testing
319
-----------
320
321
All of the options and their effects should be doc-tested. However, in order
322
not to break other tests, all options should be returned to their default state
323
at the end of each test. To make this easier, every :class:`GlobalOptions` class has
324
a :meth:`~GlobalOptions.reset()` method for doing exactly this.
325
326
327
Tests
328
-----
329
330
TESTS:
331
332
As options classes to not know how they are created they cannot be
333
pickled::
334
335
sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='The END!',
336
... entree=dict(default='soup',
337
... description='The first course of a meal',
338
... values=dict(soup='soup of the day', bread='oven baked'),
339
... alias=dict(rye='bread')),
340
... appetizer=dict(alt_name='entree'),
341
... main=dict(default='pizza', description='Main meal',
342
... values=dict(pizza='thick crust', pasta='penne arrabiata'),
343
... case_sensitive=False),
344
... dessert=dict(default='espresso', description='Dessert',
345
... values=dict(espresso='life begins again',
346
... cake='waist begins again',
347
... cream='fluffy, white stuff')),
348
... tip=dict(default=10, description='Reward for good service',
349
... checker=lambda tip: tip in range(0,20))
350
... )
351
sage: TestSuite(menu).run(skip='_test_pickling')
352
353
.. WARNING::
354
355
Default values for :class:`GlobalOptions` can be automatically overridden by
356
calling the individual instances of the :class:`GlobalOptions` class inside
357
``$HOME/.sage/init.sage``. However, this needs to be disabled by developers
358
when they are writing or checking doc-tests. Another possibly would be to
359
:meth:`~GlobalOptions.reset` all options before and after all doct-tests
360
which are dependent on particular values of options.
361
362
AUTHORS:
363
364
- Andrew Mathas (2013): initial version
365
"""
366
#*****************************************************************************
367
# Copyright (C) 2013 Andrew Mathas <andrew dot mathas at sydney dot edu dot au>
368
#
369
# Distributed under the terms of the GNU General Public License (GPL)
370
# http://www.gnu.org/licenses/
371
#*****************************************************************************
372
373
from __builtin__ import str
374
from sage.structure.sage_object import SageObject
375
376
class GlobalOptions(SageObject):
377
r"""
378
The :class:`GlobalOptions` class is a generic class for setting and
379
accessing global options for ``sage`` objects. It takes as inputs a
380
``name`` for the collection of options and a dictionary of dictionaries
381
which specifies the individual options. The allowed/expected keys in the
382
dictionary are the following:
383
384
INPUTS:
385
386
- ``name`` -- Specifies a name for the options class (required)
387
388
- ``doc`` -- Initial documentation string
389
390
- ``end_doc`` -- Final documentation string
391
392
- ``<options>=dict(...)`` -- Dictionary specifying an option
393
394
The options are specified by keyword arguments with their values
395
being a dictionary which describes the option. The
396
allowed/expected keys in the dictionary are:
397
398
- ``alias`` -- Defines alias/synonym for option values
399
- ``alt_name`` -- Alternative name for an option
400
- ``checker`` -- A function for checking whether a particular value for
401
the option is valid
402
- ``default`` -- The default value of the option
403
- ``description`` -- Documentation string
404
- ``link_to`` -- Links to an option for this set of options to an
405
option in another :class:`GlobalOptions`
406
- ``setter`` -- A function (class method) which is called whenever this
407
option changes
408
- ``values`` -- A dictionary of the legal values for this option (this
409
automatically defines the corresponding ``checker``). This dictionary
410
gives the possible options, as keys, together with a brief description
411
of them.
412
- ``case_sensitive`` -- (Default: ``True``) ``True`` or ``False`` depending on
413
whether the values of the option are case sensitive.
414
415
Options and their values can be abbreviated provided that this
416
abbreviation is a prefix of a unique option.
417
418
Calling the options with no arguments results in the list of
419
current options being printed.
420
421
EXAMPLES::
422
423
sage: from sage.structure.global_options import GlobalOptions
424
sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='End of Fancy documentation',
425
... entree=dict(default='soup',
426
... description='The first course of a meal',
427
... values=dict(soup='soup of the day', bread='oven baked'),
428
... alias=dict(rye='bread')),
429
... appetizer=dict(alt_name='entree'),
430
... main=dict(default='pizza', description='Main meal',
431
... values=dict(pizza='thick crust', pasta='penne arrabiata'),
432
... case_sensitive=False),
433
... dessert=dict(default='espresso', description='Dessert',
434
... values=dict(espresso='life begins again',
435
... cake='waist begins again',
436
... cream='fluffy white stuff')),
437
... tip=dict(default=10, description='Reward for good service',
438
... checker=lambda tip: tip in range(0,20))
439
... )
440
sage: menu
441
options for menu
442
sage: menu(entree='s') # unambiguous abbreviations are allowed
443
sage: menu(t=15);
444
sage: (menu['tip'], menu('t'))
445
(15, 15)
446
sage: menu()
447
Current options for menu
448
- dessert: espresso
449
- entree: soup
450
- main: pizza
451
- tip: 15
452
sage: menu.reset(); menu()
453
Current options for menu
454
- dessert: espresso
455
- entree: soup
456
- main: pizza
457
- tip: 10
458
sage: menu['tip']=40
459
Traceback (most recent call last):
460
...
461
ValueError: 40 is not a valid value for tip in the options for menu
462
sage: menu(m='p') # ambiguous abbreviations are not allowed
463
Traceback (most recent call last):
464
...
465
ValueError: p is not a valid value for main in the options for menu
466
467
The documentation for the options class is automatically generated from the
468
information which specifies the options::
469
470
Fancy documentation
471
-------------------
472
473
OPTIONS:
474
475
- dessert: (default: espresso)
476
Dessert
477
478
- ``cake`` -- waist begins again
479
- ``cream`` -- fluffy white stuff
480
- ``espresso`` -- life begins again
481
482
- entree: (default: soup)
483
The first course of a meal
484
485
- ``bread`` -- oven baked
486
- ``rye`` -- alias for bread
487
- ``soup`` -- soup of the day
488
489
- main: (default: pizza)
490
Main meal
491
492
- ``pasta`` -- penne arrabiata
493
- ``pizza`` -- thick crust
494
495
- tip: (default: 10)
496
Reward for good service
497
498
End of Fancy documentation
499
500
See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.
501
502
The possible values for an individual option can be obtained by
503
(trying to) set it equal to '?'::
504
505
sage: menu(des='?')
506
- ``dessert`` -- (default: ``espresso``)
507
Dessert
508
<BLANKLINE>
509
- ``cake`` -- waist begins again
510
- ``cream`` -- fluffy white stuff
511
- ``espresso`` -- life begins again
512
<BLANKLINE>
513
Current value: espresso
514
"""
515
def __init__(self, name, doc='', end_doc='', **options):
516
r"""
517
Initialize ``self``.
518
519
TESTS::
520
521
sage: from sage.structure.global_options import GlobalOptions
522
sage: menu = GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='End of Fancy documentation',
523
... entree=dict(default='soup',
524
... description='The first course of a meal',
525
... values=dict(soup='soup of the day', bread='oven baked'),
526
... alias=dict(rye='bread')),
527
... appetizer=dict(alt_name='entree'),
528
... main=dict(default='pizza', description='Main meal',
529
... values=dict(pizza='thick crust', pasta='penne arrabiata'),
530
... case_sensitive=False),
531
... dessert=dict(default='espresso', description='Dessert',
532
... values=dict(espresso='life begins again',
533
... cake='waist begins again',
534
... cream='fluffy white stuff')),
535
... tip=dict(default=10, description='Reward for good service',
536
... checker=lambda tip: tip in range(0,20))
537
... )
538
sage: specials = GlobalOptions('specials menu', doc='More fancy doc...',
539
... entree=dict(link_to=(menu, 'entree')),
540
... main_specials=dict(default='salmon', description='main course specials',
541
... values=dict(salmon='a fish', crab='Sebastian'))
542
... )
543
sage: specials['entree'] = 'rye'
544
sage: menu['entree']
545
'bread'
546
547
sage: alias_test = GlobalOptions( name='alias_test',
548
... doc="Test aliases with case sensitivity",
549
... test_opt=dict(default="Upper",
550
... description='Starts with an uppercase',
551
... values=dict(Upper="Starts with uppercase",
552
... lower="only lowercase"),
553
... case_sensitive=False,
554
... alias=dict(UpperAlias="Upper", lower_alias="lower")) )
555
sage: alias_test['test_opt'] = 'Lower_Alias'
556
sage: alias_test['test_opt']
557
'lower'
558
sage: alias_test['test_opt'] = 'upperalias'
559
sage: alias_test['test_opt']
560
'Upper'
561
"""
562
self._name=name
563
# initialise the various dictionaries used by GlobalOptions
564
self._alias={} # alias for the values of some options
565
self._alt_names={} # alternative names for some options
566
self._checker={} # validity checkers for each option
567
self._default_value={} # the default options
568
self._doc={} # the linked options force us to keep a dictionary of doc strings
569
self._linked_value={} # linked to other global options as (link, linked_option)
570
self._setter={} # a dictionary of the list of setters
571
self._value={} # the current options
572
self._values={} # a dictionary of lists of the legal values for each option
573
self._display_values={} # a dictionary of the output of the values
574
self._case_sensitive = {} # a dictionary of booleans indicating to check case sensitivity
575
for option in options:
576
self._add_option(option, options[option])
577
578
# Finally, we build the doc string for the options
579
# First we strip common white space off the front of doc and end_doc
580
if len(doc)>0:
581
lines=doc.splitlines()
582
m=min(len(line)-len(line.lstrip()) for line in lines if len(line)>0)
583
doc='\n'.join(line[m:] for line in lines)
584
585
if len(end_doc)>0:
586
lines=end_doc.splitlines()
587
m=min(len(line)-len(line.lstrip()) for line in lines if len(line)>0)
588
end_doc='\n'.join(line[m:] for line in lines)
589
590
self.__doc__='{start}\n\nOPTIONS:\n\n{options}\n\n\n{end_doc}\n\n{g_opts}'.format(
591
start=doc, end_doc=end_doc,
592
options='\n'.join(self._doc[opt] for opt in sorted(self._doc)),
593
g_opts='See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.'
594
)
595
596
597
def _repr_(self):
598
r"""
599
Return a string representation for this collection of options.
600
601
EXAMPLES::
602
603
sage: from sage.structure.global_options import GlobalOptions
604
sage: FoodOptions=GlobalOptions('daily meal',
605
... food=dict(default='apple', values=dict(apple='a nice fruit',pear='fruit')),
606
... drink=dict(default='water', values=dict(water='wet',milk='white')))
607
sage: FoodOptions
608
options for daily meal
609
"""
610
return 'options for %s'%self._name
611
612
613
def __call__(self, *get_value, **set_value):
614
r"""
615
Get or set value of the option ``option``.
616
617
EXAMPLES::
618
619
sage: from sage.structure.global_options import GlobalOptions
620
sage: FoodOptions=GlobalOptions('daily meal',
621
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
622
... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),
623
... beverage=dict(alt_name='drink'))
624
sage: FoodOptions()
625
Current options for daily meal
626
- drink: water
627
- food: apple
628
sage: FoodOptions('food')
629
'apple'
630
sage: FoodOptions(food="pair"); FoodOptions()
631
Current options for daily meal
632
- drink: water
633
- food: pair
634
sage: FoodOptions('beverage')
635
'water'
636
sage: FoodOptions(food="apple", drink="coffee"); FoodOptions()
637
Current options for daily meal
638
- drink: coffee
639
- food: apple
640
"""
641
if get_value==() and set_value=={}:
642
print 'Current %s' % self
643
options=self._value.keys()+self._linked_value.keys()
644
for x in self._alt_names:
645
options.remove(x)
646
options.sort()
647
width=1+max(len(option) for option in options)
648
print '\n'.join(' - {:{}} {}'.format(option+':',width,self[option])
649
for option in options)
650
651
# return these options
652
if get_value!=():
653
if len(get_value)==1:
654
return self[get_value[0]]
655
else:
656
return [self[option] for option in get_value]
657
658
# set these options
659
if set_value!=[]:
660
for option in set_value:
661
self[option]=set_value[option]
662
663
664
def __getitem__(self, option):
665
r"""
666
Return the current value of the option ``option``.
667
668
EXAMPLES::
669
670
sage: from sage.structure.global_options import GlobalOptions
671
sage: FoodOptions=GlobalOptions('daily meal',
672
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
673
... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),
674
... beverage=dict(alt_name='drink'))
675
sage: FoodOptions['drink']
676
'water'
677
sage: FoodOptions['d']
678
'water'
679
680
.. NOTE::
681
682
This is redundant with the ``__call__`` syntax::
683
684
sage: FoodOptions('f')
685
'apple'
686
687
but it makes for an intuitive syntax that the user is
688
likely to expect.
689
"""
690
option=self._match_option(option)
691
if option in self._linked_value:
692
link,linked_opt=self._linked_value[option]
693
return link[linked_opt]
694
elif option in self._value:
695
if self._display_values.has_key(option):
696
return self._display_values[option][self._value[option]]
697
return self._value[option]
698
699
700
def __setitem__(self, option, value):
701
r"""
702
The ``__setitem__`` method is used to change the current values of the
703
options. It also checks that the supplied options are valid and changes
704
any alias to its generic value.
705
706
EXAMPLES::
707
708
sage: from sage.structure.global_options import GlobalOptions
709
sage: FoodOptions=GlobalOptions('daily meal',
710
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
711
... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')))
712
sage: FoodOptions['drink']='coffee'; FoodOptions()
713
Current options for daily meal
714
- drink: coffee
715
- food: apple
716
sage: FoodOptions(drink='w'); FoodOptions()
717
Current options for daily meal
718
- drink: water
719
- food: apple
720
sage: FoodOptions(drink='?')
721
- ``drink`` -- (default: ``water``)
722
<BLANKLINE>
723
- ``coffee`` -- a lifestyle
724
- ``water`` -- a drink
725
<BLANKLINE>
726
Current value: water
727
"""
728
option=self._match_option(option)
729
if not callable(value):
730
value=self._match_value(option, value)
731
732
if value=='?': # return help
733
print('%s\nCurrent value: %s' % (self._doc[option], self[option]))
734
return # we do not want to call the setter below
735
736
elif option in self._linked_value:
737
link, linked_opt=self._linked_value[option]
738
link[linked_opt]=value
739
740
else:
741
self._value[option]=value
742
743
if option in self._setter:
744
# if a setter function exists then call it with the associated
745
# class, option and value
746
self._setter[option](option, value)
747
748
def _add_option(self, option, specifications):
749
r"""
750
Add an option.
751
752
INPUT:
753
754
- ``option`` -- a string
755
- ``specifications`` -- a dictionary
756
757
.. SEEALSO::
758
759
:class:`GlobalOptions` for a description of the required
760
``specifications``.
761
762
EXAMPLES::
763
764
sage: from sage.structure.global_options import GlobalOptions
765
sage: FoodOptions = GlobalOptions('daily meal',
766
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
767
... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),
768
... beverage=dict(alt_name='drink')) # indirect doctest
769
sage: FoodOptions()
770
Current options for daily meal
771
- drink: water
772
- food: apple
773
"""
774
doc={} # will be used to build the doc string
775
option = option.lower()
776
self._values[option] = []
777
self._case_sensitive[option] = True # ``True`` by default
778
for spec in sorted(specifications): # NB: options processed alphabetically!
779
if spec=='alias':
780
self._alias[option]=specifications[spec]
781
self._values[option]+=specifications[spec].keys()
782
for opt in specifications[spec]:
783
doc[opt] = 'alias for ``%s``'%specifications[spec][opt]
784
elif spec == 'alt_name':
785
self._alt_names[option] = specifications[spec]
786
self._linked_value[option] = (self, specifications[spec])
787
doc = '- ``%s`` -- alternative name for ``%s``'%(option, specifications[spec].lower())
788
elif spec=='checker':
789
if not callable(specifications[spec]):
790
raise ValueError('the checker for %s must be callable'%option)
791
self._checker[option]=specifications[spec]
792
elif spec=='default':
793
self._default_value[option]=specifications[spec]
794
elif spec=='link_to':
795
if (isinstance(specifications[spec], tuple) and len(specifications[spec]) == 2 and
796
isinstance(specifications[spec][0], GlobalOptions)):
797
link, linked_opt = specifications['link_to'] # for sanity
798
if linked_opt in link._value:
799
self._linked_value[option] = specifications['link_to']
800
link, linked_opt = specifications['link_to'] # for sanity
801
doc = link._doc[linked_opt]
802
elif linked_opt in link._linked_value:
803
self._linked_value[option] = link._linked_value[linked_opt]
804
doc = link._doc[linked_opt]
805
else:
806
raise ValueError("could not find link to {1} in {0}".format(*specifications[spec]))
807
else:
808
raise ValueError("linked options must be specified as a string: 'linked_option' or a tuple: (link,linked_option)")
809
elif spec=='setter':
810
if callable(specifications[spec]):
811
self._setter[option]=specifications[spec]
812
else:
813
raise ValueError('the setter for %s must be a function' % option)
814
elif spec=='values':
815
for val in specifications[spec]:
816
doc[val] = specifications[spec][val]
817
doc.update(specifications[spec])
818
if self._case_sensitive[option]:
819
self._values[option] += [val for val in specifications[spec].keys()]
820
self._display_values[option] = {val:val for val in specifications[spec].keys()}
821
else:
822
self._values[option] += [val.lower() for val in specifications[spec].keys()]
823
self._display_values[option] = {val.lower():val for val in specifications[spec].keys()}
824
elif spec == 'case_sensitive':
825
if not specifications[spec]:
826
for opt in self._values:
827
self._display_values[option] = {val.lower():val for val in self._values[option]}
828
self._values[option] = [val.lower() for val in self._values[option]]
829
if self._alias.has_key(option):
830
self._alias[option] = {k.lower():v.lower() for k,v in self._alias[option].iteritems()}
831
self._case_sensitive[option] = bool(specifications[spec])
832
elif spec!='description':
833
raise ValueError('Initialization error in Global options for %s: %s not recognized!'%(self._name, spec))
834
835
# now build the doc string for this option
836
if doc == {} and not 'description' in specifications:
837
raise ValueError('no documentation specified for %s in the %s' % (option, self))
838
839
self._doc[option]='' # a hack to put option in self._doc because __setitem__ calls _match_option
840
if option in self._linked_value:
841
self._doc[option]=doc
842
else:
843
width = max(len(v) for v in doc.keys()) + 4 if doc!={} else 4
844
if len(doc) > 0:
845
self._doc[option.lower()]='- ``{}`` -- (default: ``{}``)\n{}\n{}\n'.format(
846
option, self.default_value(option),
847
' %s\n'%specifications['description'] if 'description' in specifications else '',
848
'\n'.join(' - {:{}} -- {}'.format('``'+val+'``',width,doc[val]) for val in sorted(doc)))
849
else:
850
self._doc[option.lower()]='- ``{}`` -- (default: ``{}``)\n{}'.format(
851
option, self.default_value(option),
852
' %s\n'%specifications['description'] if 'description' in specifications else '')
853
854
# sanity check for non-linked options
855
if not option in self._linked_value:
856
if 'default' not in specifications:
857
raise ValueError('a default value for %s must be given' % option)
858
859
if not (option in self._checker or option in self._values):
860
raise ValueError('a value checker or a list of valid values for %s must be given' % option)
861
862
# finally, set, check and process the default value using __setitem__
863
self[option]=self._default_value[option]
864
self._default_value[option]=self._value[option] # in case the default is an alias
865
866
867
def _match_option(self, option):
868
r"""
869
Check whether ``option`` is the name of an option of ``self``, or a
870
prefix thereof.
871
872
INPUT:
873
874
- ``option`` -- a string
875
876
EXAMPLES::
877
878
sage: from sage.structure.global_options import GlobalOptions
879
sage: FoodOptions=GlobalOptions('daily meal',
880
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
881
... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')))
882
sage: FoodOptions('food') # indirect doctest
883
'apple'
884
sage: FoodOptions('f')
885
'apple'
886
"""
887
# the keys of self._doc is a list of the options, both normal and linked
888
option = option.lower()
889
890
if option in self._doc: return option
891
892
# as it is not an option try and match it with a prefix to an option
893
matches=[opt for opt in self._doc if opt.startswith(option)]
894
if len(matches)>0 and all(m.startswith(matches[0]) for m in matches):
895
return matches[0]
896
elif len(matches)>1:
897
raise ValueError('%s is an ambiguous option for %s'%(option, self._name))
898
899
# if we are still here this is not a good option!
900
raise ValueError('%s is not an option for %s' % (option, self._name))
901
902
903
def _match_value(self, option, value):
904
r"""
905
Check whether ``value`` is a valid value for ``option``.
906
907
INPUT:
908
909
- ``option`` -- a string: the name of an option, or prefix thereof
910
- ``value`` -- a value or ``'?'``
911
912
EXAMPLES::
913
914
sage: from sage.structure.global_options import GlobalOptions
915
sage: FoodOptions=GlobalOptions('daily meal',
916
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
917
... drink=dict(default='water', values=dict(water='a drink',wine='a lifestyle')))
918
sage: FoodOptions(f='a') # indirect doctest
919
sage: FoodOptions('f')
920
'apple'
921
sage: FoodOptions(d='wi'); FoodOptions('f')
922
'apple'
923
sage: FoodOptions(d='w')
924
Traceback (most recent call last):
925
...
926
ValueError: w is not a valid value for drink in the options for daily meal
927
"""
928
if value == "?": return value # help on this value
929
930
if option in self._linked_value:
931
link, linked_opt = self._linked_value[option]
932
return link._match_value(linked_opt, value)
933
934
orig_value = value
935
936
# convert to proper case if it is a string
937
if isinstance(value, str) and not self._case_sensitive[option]:
938
value = value.lower()
939
940
if option in self._values:
941
if value in self._values[option]:
942
if option in self._alias and value in self._alias[option]:
943
return self._alias[option][value]
944
return value
945
946
# as it is not a value try and match it with a prefix of a value
947
matches=[val for val in self._values[option] if val.startswith(value)]
948
if len(matches)>0 and all(m.startswith(matches[0]) for m in matches):
949
val=matches[0]
950
if option in self._alias and val in self._alias[option]:
951
return self._alias[option][val]
952
return val
953
954
if option in self._checker and self._checker[option](value):
955
return value
956
957
# if we are still here this is not a good value!
958
959
# replace any value alias with its "real" value
960
if option in self._alias and value in self._alias[option]:
961
orig_value = self._alias[option][value]
962
raise ValueError('%s is not a valid value for %s in the %s'%(orig_value, option, self))
963
964
965
def default_value(self, option):
966
r"""
967
Return the default value of the option.
968
969
EXAMPLES::
970
971
sage: from sage.structure.global_options import GlobalOptions
972
sage: FoodOptions=GlobalOptions('daily meal',
973
... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),
974
... drink=dict(default='water', values=dict(water='a drink',wine='a lifestyle')))
975
sage: FoodOptions.default_value('food')
976
'apple'
977
"""
978
option=self._match_option(option)
979
if option in self._default_value:
980
return self._default_value[option]
981
else:
982
link, linked_opt=self._linked_value[option]
983
return link._default_value(linked_opt)
984
985
986
def dispatch(self, obj, dispatch_to, option, *args, **kargs):
987
r"""
988
.. TODO:: title
989
990
The *dispatchable* options are options which dispatch related methods of
991
the corresponding class - or user defined methods which are passed to
992
:class:`GlobalOptions`. The format for specifying a dispatchable option
993
is to include ``dispatch_to = <option name>`` in the specifications for
994
the options and then to add the options to the (element) class. Each
995
option is then assumed to be a method of the element class with a name
996
of the form ``<option name> + '_' + <current vale for this option'``.
997
These options are called by the element class via::
998
999
return self.options.dispatch(self, dispatch_to, option, *args, **kargs)
1000
1001
Note that the argument ``self`` is necessary here because the
1002
dispatcher is a method of the options class and not of
1003
``self``. The value of the option can also be set to a
1004
user-defined function, with arguments ``self`` and ``option``;
1005
in this case the user's function is called instead.
1006
1007
EXAMPLES:
1008
1009
Here is a contrived example::
1010
1011
sage: from sage.structure.global_options import GlobalOptions
1012
sage: DelimitedListOptions=GlobalOptions('list delimiters',
1013
... delim=dict(default='b', values={'b':'brackets', 'p':'parentheses'}))
1014
sage: class DelimitedList(CombinatorialObject):
1015
... options=DelimitedListOptions
1016
... def _repr_b(self): return '[%s]' % ','.join('%s'%i for i in self._list)
1017
... def _repr_p(self): return '(%s)' % ','.join('%s'%i for i in self._list)
1018
... def _repr_(self): return self.options.dispatch(self, '_repr_','delim')
1019
sage: dlist=DelimitedList([1,2,3]); dlist
1020
[1,2,3]
1021
sage: dlist.options(delim='p'); dlist
1022
(1,2,3)
1023
sage: dlist.options(delim=lambda self: '<%s>' % ','.join('%s'%i for i in self._list)); dlist
1024
<1,2,3>
1025
"""
1026
if callable(self._value[option]):
1027
try:
1028
return self._value[option](obj, *args, **kargs)
1029
except TypeError:
1030
raise ValueError('the user defined dispatcher function failed!')
1031
else:
1032
if dispatch_to[-1]=='_': dispatch_to=dispatch_to[:-1]
1033
dispatch=getattr(obj,dispatch_to+'_'+self._value[option])
1034
return dispatch(*args, **kargs)
1035
1036
raise ValueError('%s is not a dispatchable option!' % option)
1037
1038
1039
def reset(self, option=None):
1040
r"""
1041
Reset options to their default value.
1042
1043
INPUT:
1044
1045
- ``option`` -- (Default: ``None``) The name of an option as a string
1046
or ``None``. If ``option`` is specified only this option is reset to
1047
its default value; otherwise all options are reset.
1048
1049
EXAMPLES::
1050
1051
sage: from sage.structure.global_options import GlobalOptions
1052
sage: opts=GlobalOptions('daily meal',
1053
... food=dict(default='bread', values=dict(bread='rye bread', salmon='a fish')),
1054
... drink=dict(default='water',values=dict(water='essential for life',wine='essential')))
1055
sage: opts(food='salmon'); opts()
1056
Current options for daily meal
1057
- drink: water
1058
- food: salmon
1059
sage: opts.reset('drink'); opts()
1060
Current options for daily meal
1061
- drink: water
1062
- food: salmon
1063
sage: opts.reset(); opts()
1064
Current options for daily meal
1065
- drink: water
1066
- food: bread
1067
"""
1068
if option is None:
1069
for option in self._default_value:
1070
self._value[option] = self._default_value[option]
1071
if not self._case_sensitive[option] and isinstance(self._value[option],str):
1072
self._value[option] = self._value[option].lower()
1073
for option in self._linked_value:
1074
link, linked_opt=self._linked_value[option]
1075
link.reset(linked_opt)
1076
else:
1077
option=self._match_option(option)
1078
if option in self._default_value:
1079
self._value[option] = self._default_value[option]
1080
if not self._case_sensitive[option] and isinstance(self._value[option],str):
1081
self._value[option] = self._value[option].lower()
1082
elif option in self._linked_value:
1083
link, linked_opt=self._linked_value[option]
1084
link.reset(linked_opt)
1085
1086
1087