Path: blob/master/src/sage/structure/global_options.py
8814 views
r"""1Global options23The :class:`GlobalOptions` class provides a generic mechanism for4setting and accessing **global** options for parents in one or several5related classes, typically for customizing the representation of their6elements. This class will eventually also support setting options on a7parent by parent basis.89.. SEEALSO::1011For better examples of :class:`GlobalOptions` in action see12:meth:`sage.combinat.partition.Partitions.global_options` and13:meth:`sage.combinat.tableau.Tableaux.global_options`.1415.. _construction_section:1617Construction of options classes18-------------------------------1920The general setup for creating a set of global options is:2122.. code-block:: python2324MyOptions=GlobalOptions('option name',25doc='Nice options',26first_option=dict(default='default value',27description='Changes the functionality of _repr_',28values=dict(with_bells='causes _repr_ to print with bells',29with_whistles='causes _repr_ to print with whistles',30...),31aliases=dict(bells='option1', whistles='option2', ...),32second_option=dict(...),33third_option=dict(),34end_doc='end of options documentation'35)3637Each option is specified as a dictionary which describes the possible38values for the option and its documentation. The possible entries in this39dictionary are:4041- ``alias`` -- Allows for several option values to do the same thing.4243- ``alt_name`` -- An alternative name for this option.4445- ``checker`` -- A validation function which returns whether a user46supplied value is valid or not. This is typically useful for large47lists of legal values such as `\NN`.4849- ``default`` -- Gives the default value for the option.5051- ``description`` -- A one line description of the option.5253- ``link_to`` -- Links this option to another one in another set of54global options. This is used for example to allow55:class:`Partitions` and :class:`Tableaux` to share the same56``convention`` option.5758- ``setter`` -- A function which is called **after** the value of the59option is changed.6061- ``values`` -- A dictionary assigning each valid value for the option62to a short description of what it does.6364- ``case_sensitive`` -- (Default: ``True``) ``True`` or ``False`` depending on65whether the values of the option are case sensitive.6667For each option, either a complete list of possible values, via ``values``, or a68validation function, via ``checker``, must be given. The values can be quite69arbitrary, including user-defined functions which customize the default70behaviour of the classes such as the output of ``_repr_`` or :func:`latex`. See71:ref:`dispatcher` below, and :meth:`~GlobalOptions.dispatcher`, for more72information.7374The documentation for the options is automatically constructed by combining the75description of each option with a header and footer which are given by the76following optional, but recommended, arguments:7778- ``doc`` -- The top half of the documentation which appears before the79automatically generated list of options and their possible values.8081- ``end_doc`` -- The second half of the documentation which appears82after the list of options and their values.838485The basic structure for defining a :class:`GlobalOptions` class is best86illustrated by an example::8788sage: from sage.structure.global_options import GlobalOptions89sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='The END!',90... entree=dict(default='soup',91... description='The first course of a meal',92... values=dict(soup='soup of the day', bread='oven baked'),93... alias=dict(rye='bread')),94... appetizer=dict(alt_name='entree'),95... main=dict(default='pizza', description='Main meal',96... values=dict(pizza='thick crust', pasta='penne arrabiata'),97... case_sensitive=False),98... dessert=dict(default='espresso', description='Dessert',99... values=dict(espresso='life begins again',100... cake='waist begins again',101... cream='fluffy, white stuff')),102... tip=dict(default=10, description='Reward for good service',103... checker=lambda tip: tip in range(0,20))104... )105sage: menu106options for menu107108For more details see :class:`GlobalOptions`.109110Accessing and setting option values111-----------------------------------112113All options and their values, when they are strings, are forced to be lower114case. The values of an options class can be set and accessed by calling the115class or by treating the class as an array.116117Continuing the example from :ref:`construction_section`::118119sage: menu()120Current options for menu121- dessert: espresso122- entree: soup123- main: pizza124- tip: 10125sage: menu('dessert')126'espresso'127sage: menu['dessert']128'espresso'129130Note that, provided there is no ambiguity, options and their values can be131abbreviated::132133sage: menu['d']134'espresso'135sage: menu('m','t',des='esp', ent='sou') # get and set several values at once136['pizza', 10]137sage: menu(t=15); menu['tip']13815139sage: menu(e='s', m='Pi'); menu()140Current options for menu141- dessert: espresso142- entree: soup143- main: pizza144- tip: 15145sage: menu(m='P')146Traceback (most recent call last):147...148ValueError: P is not a valid value for main in the options for menu149150151Setter functions152----------------153154Each option of a :class:`GlobalOptions` can be equipped with an optional setter155function which is called **after** the value of the option is changed. In the156following example, setting the option 'add' changes the state of the class by157setting an attribute in this class using a :func:`classmethod`. Note that the158options object is inserted after the creation of the class in order to access159the :func:`classmethod` as ``A.setter``::160161sage: from sage.structure.global_options import GlobalOptions162sage: class A(SageObject):163... state = 0164... @classmethod165... def setter(cls, option, val):166... cls.state += int(val)167...168sage: A.options=GlobalOptions('A',169... add=dict(default=1,170... checker=lambda v: int(v)>0,171... description='An option with a setter',172... setter=A.setter))173sage: a = A(2); a.state1741175sage: a.options()176Current options for A177- add: 1178sage: a.options(add=4)179sage: a.state1805181sage: a.options()182Current options for A183- add: 4184185Another alternative is to construct the options class inside the ``__init__``186method of the class ``A``.187188Documentation for options189-------------------------190191The documentation for a :class:`GlobalOptions` is automatically generated from192the supplied options. For example, the generated documentation for the options193``menu`` defined in :ref:`construction_section` is the following::194195Fancy documentation196-------------------197198OPTIONS:199200- ``appetizer`` -- alternative name for ``entree``201202- ``dessert`` -- (default: ``espresso``)203Dessert204205- ``cake`` -- waist begins again206- ``cream`` -- fluffy, white stuff207- ``espresso`` -- life begins again208209- ``entree`` -- (default: ``soup``)210The first course of a meal211212- ``bread`` -- oven baked213- ``rye`` -- alias for bread214- ``soup`` -- soup of the day215216- ``main`` -- (default: ``pizza``)217Main meal218219- ``pasta`` -- penne arrabiata220- ``pizza`` -- thick crust221222- tip -- (default: 10)223Reward for good service224225The END!226227See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.228229In addition, help on each option, and its list of possible values, can be230obtained by (trying to) set the option equal to '?'::231232sage: menu(des='?')233- ``dessert`` -- (default: ``espresso``)234Dessert235<BLANKLINE>236- ``cake`` -- waist begins again237- ``cream`` -- fluffy, white stuff238- ``espresso`` -- life begins again239<BLANKLINE>240Current value: espresso241242.. _dispatcher:243244Dispatchers245-----------246247The whole idea of a :class:`GlobalOptions` class is that the options change the248default behaviour of the associated classes. This can be done either by simply249checking what the current value of the relevant option is. Another possibility250is to use the options class as a dispatcher to associated methods. To use the251dispatcher feature of a :class:`GlobalOptions` class it is necessary to implement252separate methods for each value of the option where the naming convention for253these methods is that they start with a common prefix and finish with the value254of the option.255256If the value of a dispatchable option is set equal to a (user defined) function257then this function is called instead of a class method.258259For example, the options ``MyOptions`` can be used to dispatch the ``_repr_``260method of the associated class ``MyClass`` as follows:261262.. code-block:: python263264class MyClass(...):265global_options=MyOptions266def _repr_(self):267return self.global_options.dispatch(self,'_repr_','first_option')268def _repr_with_bells(self):269print 'Bell!'270def _repr_with_whistles(self):271print 'Whistles!'272273In this example, ``first_option`` is an option of ``MyOptions`` which takes274values ``bells``, ``whistles``, and so on. Note that it is necessary to make275``self``, which is an instance of ``MyClass``, an argument of the dispatcher276because :meth:`~GlobalOptions.dispatch()` is a method of :class:`GlobalOptions`277and not a method of ``MyClass``. Apart from ``MyOptions``, as it is a method of278this class, the arguments are the attached class (here ``MyClass``), the prefix279of the method of ``MyClass`` being dispatched, the option of ``MyOptions``280which controls the dispatching. All other arguments are passed through to the281corresponding methods of ``MyClass``. In general, a dispatcher is invoked as::282283self.options.dispatch(self, dispatch_to, option, *args, **kargs)284285Usually this will result in the method286``dispatch_to + '_' + MyOptions(options)`` of ``self`` being called with287arguments ``*args`` and ``**kargs`` (if ``dispatch_to[-1] == '_'`` then the288method ``dispatch_to + MyOptions(options)`` is called).289290If ``MyOptions(options)`` is itself a function then the dispatcher will call291this function instead. In this way, it is possible to allow the user to292customise the default behaviour of this method. See293:meth:`~GlobalOptions.dispatch` for an example of how this can be achieved.294295The dispatching capabilities of :class:`GlobalOptions` allows options to be296applied automatically without needing to parse different values of the option297(the cost is that there must be a method for each value). The dispatching298capabilities can also be used to make one option control several methods:299300.. code-block:: python301302def __le__(self, other):303return self.options.dispatch(self, '_le_','cmp', other)304def __ge__(self, other):305return self.options.dispatch(self, '_ge_','cmp', other)306def _le_option_a(self, other):307return ...308def _ge_option_a(self, other):309return ...310def _le_option_b(self, other):311return ...312def _ge_option_b(self, other):313return ...314315See :meth:`~GlobalOptions.dispatch` for more details.316317Doc testing318-----------319320All of the options and their effects should be doc-tested. However, in order321not to break other tests, all options should be returned to their default state322at the end of each test. To make this easier, every :class:`GlobalOptions` class has323a :meth:`~GlobalOptions.reset()` method for doing exactly this.324325326Tests327-----328329TESTS:330331As options classes to not know how they are created they cannot be332pickled::333334sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='The END!',335... entree=dict(default='soup',336... description='The first course of a meal',337... values=dict(soup='soup of the day', bread='oven baked'),338... alias=dict(rye='bread')),339... appetizer=dict(alt_name='entree'),340... main=dict(default='pizza', description='Main meal',341... values=dict(pizza='thick crust', pasta='penne arrabiata'),342... case_sensitive=False),343... dessert=dict(default='espresso', description='Dessert',344... values=dict(espresso='life begins again',345... cake='waist begins again',346... cream='fluffy, white stuff')),347... tip=dict(default=10, description='Reward for good service',348... checker=lambda tip: tip in range(0,20))349... )350sage: TestSuite(menu).run(skip='_test_pickling')351352.. WARNING::353354Default values for :class:`GlobalOptions` can be automatically overridden by355calling the individual instances of the :class:`GlobalOptions` class inside356``$HOME/.sage/init.sage``. However, this needs to be disabled by developers357when they are writing or checking doc-tests. Another possibly would be to358:meth:`~GlobalOptions.reset` all options before and after all doct-tests359which are dependent on particular values of options.360361AUTHORS:362363- Andrew Mathas (2013): initial version364"""365#*****************************************************************************366# Copyright (C) 2013 Andrew Mathas <andrew dot mathas at sydney dot edu dot au>367#368# Distributed under the terms of the GNU General Public License (GPL)369# http://www.gnu.org/licenses/370#*****************************************************************************371372from __builtin__ import str373from sage.structure.sage_object import SageObject374375class GlobalOptions(SageObject):376r"""377The :class:`GlobalOptions` class is a generic class for setting and378accessing global options for ``sage`` objects. It takes as inputs a379``name`` for the collection of options and a dictionary of dictionaries380which specifies the individual options. The allowed/expected keys in the381dictionary are the following:382383INPUTS:384385- ``name`` -- Specifies a name for the options class (required)386387- ``doc`` -- Initial documentation string388389- ``end_doc`` -- Final documentation string390391- ``<options>=dict(...)`` -- Dictionary specifying an option392393The options are specified by keyword arguments with their values394being a dictionary which describes the option. The395allowed/expected keys in the dictionary are:396397- ``alias`` -- Defines alias/synonym for option values398- ``alt_name`` -- Alternative name for an option399- ``checker`` -- A function for checking whether a particular value for400the option is valid401- ``default`` -- The default value of the option402- ``description`` -- Documentation string403- ``link_to`` -- Links to an option for this set of options to an404option in another :class:`GlobalOptions`405- ``setter`` -- A function (class method) which is called whenever this406option changes407- ``values`` -- A dictionary of the legal values for this option (this408automatically defines the corresponding ``checker``). This dictionary409gives the possible options, as keys, together with a brief description410of them.411- ``case_sensitive`` -- (Default: ``True``) ``True`` or ``False`` depending on412whether the values of the option are case sensitive.413414Options and their values can be abbreviated provided that this415abbreviation is a prefix of a unique option.416417Calling the options with no arguments results in the list of418current options being printed.419420EXAMPLES::421422sage: from sage.structure.global_options import GlobalOptions423sage: menu=GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='End of Fancy documentation',424... entree=dict(default='soup',425... description='The first course of a meal',426... values=dict(soup='soup of the day', bread='oven baked'),427... alias=dict(rye='bread')),428... appetizer=dict(alt_name='entree'),429... main=dict(default='pizza', description='Main meal',430... values=dict(pizza='thick crust', pasta='penne arrabiata'),431... case_sensitive=False),432... dessert=dict(default='espresso', description='Dessert',433... values=dict(espresso='life begins again',434... cake='waist begins again',435... cream='fluffy white stuff')),436... tip=dict(default=10, description='Reward for good service',437... checker=lambda tip: tip in range(0,20))438... )439sage: menu440options for menu441sage: menu(entree='s') # unambiguous abbreviations are allowed442sage: menu(t=15);443sage: (menu['tip'], menu('t'))444(15, 15)445sage: menu()446Current options for menu447- dessert: espresso448- entree: soup449- main: pizza450- tip: 15451sage: menu.reset(); menu()452Current options for menu453- dessert: espresso454- entree: soup455- main: pizza456- tip: 10457sage: menu['tip']=40458Traceback (most recent call last):459...460ValueError: 40 is not a valid value for tip in the options for menu461sage: menu(m='p') # ambiguous abbreviations are not allowed462Traceback (most recent call last):463...464ValueError: p is not a valid value for main in the options for menu465466The documentation for the options class is automatically generated from the467information which specifies the options::468469Fancy documentation470-------------------471472OPTIONS:473474- dessert: (default: espresso)475Dessert476477- ``cake`` -- waist begins again478- ``cream`` -- fluffy white stuff479- ``espresso`` -- life begins again480481- entree: (default: soup)482The first course of a meal483484- ``bread`` -- oven baked485- ``rye`` -- alias for bread486- ``soup`` -- soup of the day487488- main: (default: pizza)489Main meal490491- ``pasta`` -- penne arrabiata492- ``pizza`` -- thick crust493494- tip: (default: 10)495Reward for good service496497End of Fancy documentation498499See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.500501The possible values for an individual option can be obtained by502(trying to) set it equal to '?'::503504sage: menu(des='?')505- ``dessert`` -- (default: ``espresso``)506Dessert507<BLANKLINE>508- ``cake`` -- waist begins again509- ``cream`` -- fluffy white stuff510- ``espresso`` -- life begins again511<BLANKLINE>512Current value: espresso513"""514def __init__(self, name, doc='', end_doc='', **options):515r"""516Initialize ``self``.517518TESTS::519520sage: from sage.structure.global_options import GlobalOptions521sage: menu = GlobalOptions('menu', doc='Fancy documentation\n'+'-'*19, end_doc='End of Fancy documentation',522... entree=dict(default='soup',523... description='The first course of a meal',524... values=dict(soup='soup of the day', bread='oven baked'),525... alias=dict(rye='bread')),526... appetizer=dict(alt_name='entree'),527... main=dict(default='pizza', description='Main meal',528... values=dict(pizza='thick crust', pasta='penne arrabiata'),529... case_sensitive=False),530... dessert=dict(default='espresso', description='Dessert',531... values=dict(espresso='life begins again',532... cake='waist begins again',533... cream='fluffy white stuff')),534... tip=dict(default=10, description='Reward for good service',535... checker=lambda tip: tip in range(0,20))536... )537sage: specials = GlobalOptions('specials menu', doc='More fancy doc...',538... entree=dict(link_to=(menu, 'entree')),539... main_specials=dict(default='salmon', description='main course specials',540... values=dict(salmon='a fish', crab='Sebastian'))541... )542sage: specials['entree'] = 'rye'543sage: menu['entree']544'bread'545546sage: alias_test = GlobalOptions( name='alias_test',547... doc="Test aliases with case sensitivity",548... test_opt=dict(default="Upper",549... description='Starts with an uppercase',550... values=dict(Upper="Starts with uppercase",551... lower="only lowercase"),552... case_sensitive=False,553... alias=dict(UpperAlias="Upper", lower_alias="lower")) )554sage: alias_test['test_opt'] = 'Lower_Alias'555sage: alias_test['test_opt']556'lower'557sage: alias_test['test_opt'] = 'upperalias'558sage: alias_test['test_opt']559'Upper'560"""561self._name=name562# initialise the various dictionaries used by GlobalOptions563self._alias={} # alias for the values of some options564self._alt_names={} # alternative names for some options565self._checker={} # validity checkers for each option566self._default_value={} # the default options567self._doc={} # the linked options force us to keep a dictionary of doc strings568self._linked_value={} # linked to other global options as (link, linked_option)569self._setter={} # a dictionary of the list of setters570self._value={} # the current options571self._values={} # a dictionary of lists of the legal values for each option572self._display_values={} # a dictionary of the output of the values573self._case_sensitive = {} # a dictionary of booleans indicating to check case sensitivity574for option in options:575self._add_option(option, options[option])576577# Finally, we build the doc string for the options578# First we strip common white space off the front of doc and end_doc579if len(doc)>0:580lines=doc.splitlines()581m=min(len(line)-len(line.lstrip()) for line in lines if len(line)>0)582doc='\n'.join(line[m:] for line in lines)583584if len(end_doc)>0:585lines=end_doc.splitlines()586m=min(len(line)-len(line.lstrip()) for line in lines if len(line)>0)587end_doc='\n'.join(line[m:] for line in lines)588589self.__doc__='{start}\n\nOPTIONS:\n\n{options}\n\n\n{end_doc}\n\n{g_opts}'.format(590start=doc, end_doc=end_doc,591options='\n'.join(self._doc[opt] for opt in sorted(self._doc)),592g_opts='See :class:`~sage.structure.global_options.GlobalOptions` for more features of these options.'593)594595596def _repr_(self):597r"""598Return a string representation for this collection of options.599600EXAMPLES::601602sage: from sage.structure.global_options import GlobalOptions603sage: FoodOptions=GlobalOptions('daily meal',604... food=dict(default='apple', values=dict(apple='a nice fruit',pear='fruit')),605... drink=dict(default='water', values=dict(water='wet',milk='white')))606sage: FoodOptions607options for daily meal608"""609return 'options for %s'%self._name610611612def __call__(self, *get_value, **set_value):613r"""614Get or set value of the option ``option``.615616EXAMPLES::617618sage: from sage.structure.global_options import GlobalOptions619sage: FoodOptions=GlobalOptions('daily meal',620... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),621... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),622... beverage=dict(alt_name='drink'))623sage: FoodOptions()624Current options for daily meal625- drink: water626- food: apple627sage: FoodOptions('food')628'apple'629sage: FoodOptions(food="pair"); FoodOptions()630Current options for daily meal631- drink: water632- food: pair633sage: FoodOptions('beverage')634'water'635sage: FoodOptions(food="apple", drink="coffee"); FoodOptions()636Current options for daily meal637- drink: coffee638- food: apple639"""640if get_value==() and set_value=={}:641print 'Current %s' % self642options=self._value.keys()+self._linked_value.keys()643for x in self._alt_names:644options.remove(x)645options.sort()646width=1+max(len(option) for option in options)647print '\n'.join(' - {:{}} {}'.format(option+':',width,self[option])648for option in options)649650# return these options651if get_value!=():652if len(get_value)==1:653return self[get_value[0]]654else:655return [self[option] for option in get_value]656657# set these options658if set_value!=[]:659for option in set_value:660self[option]=set_value[option]661662663def __getitem__(self, option):664r"""665Return the current value of the option ``option``.666667EXAMPLES::668669sage: from sage.structure.global_options import GlobalOptions670sage: FoodOptions=GlobalOptions('daily meal',671... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),672... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),673... beverage=dict(alt_name='drink'))674sage: FoodOptions['drink']675'water'676sage: FoodOptions['d']677'water'678679.. NOTE::680681This is redundant with the ``__call__`` syntax::682683sage: FoodOptions('f')684'apple'685686but it makes for an intuitive syntax that the user is687likely to expect.688"""689option=self._match_option(option)690if option in self._linked_value:691link,linked_opt=self._linked_value[option]692return link[linked_opt]693elif option in self._value:694if self._display_values.has_key(option):695return self._display_values[option][self._value[option]]696return self._value[option]697698699def __setitem__(self, option, value):700r"""701The ``__setitem__`` method is used to change the current values of the702options. It also checks that the supplied options are valid and changes703any alias to its generic value.704705EXAMPLES::706707sage: from sage.structure.global_options import GlobalOptions708sage: FoodOptions=GlobalOptions('daily meal',709... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),710... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')))711sage: FoodOptions['drink']='coffee'; FoodOptions()712Current options for daily meal713- drink: coffee714- food: apple715sage: FoodOptions(drink='w'); FoodOptions()716Current options for daily meal717- drink: water718- food: apple719sage: FoodOptions(drink='?')720- ``drink`` -- (default: ``water``)721<BLANKLINE>722- ``coffee`` -- a lifestyle723- ``water`` -- a drink724<BLANKLINE>725Current value: water726"""727option=self._match_option(option)728if not callable(value):729value=self._match_value(option, value)730731if value=='?': # return help732print('%s\nCurrent value: %s' % (self._doc[option], self[option]))733return # we do not want to call the setter below734735elif option in self._linked_value:736link, linked_opt=self._linked_value[option]737link[linked_opt]=value738739else:740self._value[option]=value741742if option in self._setter:743# if a setter function exists then call it with the associated744# class, option and value745self._setter[option](option, value)746747def _add_option(self, option, specifications):748r"""749Add an option.750751INPUT:752753- ``option`` -- a string754- ``specifications`` -- a dictionary755756.. SEEALSO::757758:class:`GlobalOptions` for a description of the required759``specifications``.760761EXAMPLES::762763sage: from sage.structure.global_options import GlobalOptions764sage: FoodOptions = GlobalOptions('daily meal',765... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),766... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')),767... beverage=dict(alt_name='drink')) # indirect doctest768sage: FoodOptions()769Current options for daily meal770- drink: water771- food: apple772"""773doc={} # will be used to build the doc string774option = option.lower()775self._values[option] = []776self._case_sensitive[option] = True # ``True`` by default777for spec in sorted(specifications): # NB: options processed alphabetically!778if spec=='alias':779self._alias[option]=specifications[spec]780self._values[option]+=specifications[spec].keys()781for opt in specifications[spec]:782doc[opt] = 'alias for ``%s``'%specifications[spec][opt]783elif spec == 'alt_name':784self._alt_names[option] = specifications[spec]785self._linked_value[option] = (self, specifications[spec])786doc = '- ``%s`` -- alternative name for ``%s``'%(option, specifications[spec].lower())787elif spec=='checker':788if not callable(specifications[spec]):789raise ValueError('the checker for %s must be callable'%option)790self._checker[option]=specifications[spec]791elif spec=='default':792self._default_value[option]=specifications[spec]793elif spec=='link_to':794if (isinstance(specifications[spec], tuple) and len(specifications[spec]) == 2 and795isinstance(specifications[spec][0], GlobalOptions)):796link, linked_opt = specifications['link_to'] # for sanity797if linked_opt in link._value:798self._linked_value[option] = specifications['link_to']799link, linked_opt = specifications['link_to'] # for sanity800doc = link._doc[linked_opt]801elif linked_opt in link._linked_value:802self._linked_value[option] = link._linked_value[linked_opt]803doc = link._doc[linked_opt]804else:805raise ValueError("could not find link to {1} in {0}".format(*specifications[spec]))806else:807raise ValueError("linked options must be specified as a string: 'linked_option' or a tuple: (link,linked_option)")808elif spec=='setter':809if callable(specifications[spec]):810self._setter[option]=specifications[spec]811else:812raise ValueError('the setter for %s must be a function' % option)813elif spec=='values':814for val in specifications[spec]:815doc[val] = specifications[spec][val]816doc.update(specifications[spec])817if self._case_sensitive[option]:818self._values[option] += [val for val in specifications[spec].keys()]819self._display_values[option] = {val:val for val in specifications[spec].keys()}820else:821self._values[option] += [val.lower() for val in specifications[spec].keys()]822self._display_values[option] = {val.lower():val for val in specifications[spec].keys()}823elif spec == 'case_sensitive':824if not specifications[spec]:825for opt in self._values:826self._display_values[option] = {val.lower():val for val in self._values[option]}827self._values[option] = [val.lower() for val in self._values[option]]828if self._alias.has_key(option):829self._alias[option] = {k.lower():v.lower() for k,v in self._alias[option].iteritems()}830self._case_sensitive[option] = bool(specifications[spec])831elif spec!='description':832raise ValueError('Initialization error in Global options for %s: %s not recognized!'%(self._name, spec))833834# now build the doc string for this option835if doc == {} and not 'description' in specifications:836raise ValueError('no documentation specified for %s in the %s' % (option, self))837838self._doc[option]='' # a hack to put option in self._doc because __setitem__ calls _match_option839if option in self._linked_value:840self._doc[option]=doc841else:842width = max(len(v) for v in doc.keys()) + 4 if doc!={} else 4843if len(doc) > 0:844self._doc[option.lower()]='- ``{}`` -- (default: ``{}``)\n{}\n{}\n'.format(845option, self.default_value(option),846' %s\n'%specifications['description'] if 'description' in specifications else '',847'\n'.join(' - {:{}} -- {}'.format('``'+val+'``',width,doc[val]) for val in sorted(doc)))848else:849self._doc[option.lower()]='- ``{}`` -- (default: ``{}``)\n{}'.format(850option, self.default_value(option),851' %s\n'%specifications['description'] if 'description' in specifications else '')852853# sanity check for non-linked options854if not option in self._linked_value:855if 'default' not in specifications:856raise ValueError('a default value for %s must be given' % option)857858if not (option in self._checker or option in self._values):859raise ValueError('a value checker or a list of valid values for %s must be given' % option)860861# finally, set, check and process the default value using __setitem__862self[option]=self._default_value[option]863self._default_value[option]=self._value[option] # in case the default is an alias864865866def _match_option(self, option):867r"""868Check whether ``option`` is the name of an option of ``self``, or a869prefix thereof.870871INPUT:872873- ``option`` -- a string874875EXAMPLES::876877sage: from sage.structure.global_options import GlobalOptions878sage: FoodOptions=GlobalOptions('daily meal',879... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),880... drink=dict(default='water', values=dict(water='a drink',coffee='a lifestyle')))881sage: FoodOptions('food') # indirect doctest882'apple'883sage: FoodOptions('f')884'apple'885"""886# the keys of self._doc is a list of the options, both normal and linked887option = option.lower()888889if option in self._doc: return option890891# as it is not an option try and match it with a prefix to an option892matches=[opt for opt in self._doc if opt.startswith(option)]893if len(matches)>0 and all(m.startswith(matches[0]) for m in matches):894return matches[0]895elif len(matches)>1:896raise ValueError('%s is an ambiguous option for %s'%(option, self._name))897898# if we are still here this is not a good option!899raise ValueError('%s is not an option for %s' % (option, self._name))900901902def _match_value(self, option, value):903r"""904Check whether ``value`` is a valid value for ``option``.905906INPUT:907908- ``option`` -- a string: the name of an option, or prefix thereof909- ``value`` -- a value or ``'?'``910911EXAMPLES::912913sage: from sage.structure.global_options import GlobalOptions914sage: FoodOptions=GlobalOptions('daily meal',915... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),916... drink=dict(default='water', values=dict(water='a drink',wine='a lifestyle')))917sage: FoodOptions(f='a') # indirect doctest918sage: FoodOptions('f')919'apple'920sage: FoodOptions(d='wi'); FoodOptions('f')921'apple'922sage: FoodOptions(d='w')923Traceback (most recent call last):924...925ValueError: w is not a valid value for drink in the options for daily meal926"""927if value == "?": return value # help on this value928929if option in self._linked_value:930link, linked_opt = self._linked_value[option]931return link._match_value(linked_opt, value)932933orig_value = value934935# convert to proper case if it is a string936if isinstance(value, str) and not self._case_sensitive[option]:937value = value.lower()938939if option in self._values:940if value in self._values[option]:941if option in self._alias and value in self._alias[option]:942return self._alias[option][value]943return value944945# as it is not a value try and match it with a prefix of a value946matches=[val for val in self._values[option] if val.startswith(value)]947if len(matches)>0 and all(m.startswith(matches[0]) for m in matches):948val=matches[0]949if option in self._alias and val in self._alias[option]:950return self._alias[option][val]951return val952953if option in self._checker and self._checker[option](value):954return value955956# if we are still here this is not a good value!957958# replace any value alias with its "real" value959if option in self._alias and value in self._alias[option]:960orig_value = self._alias[option][value]961raise ValueError('%s is not a valid value for %s in the %s'%(orig_value, option, self))962963964def default_value(self, option):965r"""966Return the default value of the option.967968EXAMPLES::969970sage: from sage.structure.global_options import GlobalOptions971sage: FoodOptions=GlobalOptions('daily meal',972... food=dict(default='apple', values=dict(apple='a fruit',pair='of what?')),973... drink=dict(default='water', values=dict(water='a drink',wine='a lifestyle')))974sage: FoodOptions.default_value('food')975'apple'976"""977option=self._match_option(option)978if option in self._default_value:979return self._default_value[option]980else:981link, linked_opt=self._linked_value[option]982return link._default_value(linked_opt)983984985def dispatch(self, obj, dispatch_to, option, *args, **kargs):986r"""987.. TODO:: title988989The *dispatchable* options are options which dispatch related methods of990the corresponding class - or user defined methods which are passed to991:class:`GlobalOptions`. The format for specifying a dispatchable option992is to include ``dispatch_to = <option name>`` in the specifications for993the options and then to add the options to the (element) class. Each994option is then assumed to be a method of the element class with a name995of the form ``<option name> + '_' + <current vale for this option'``.996These options are called by the element class via::997998return self.options.dispatch(self, dispatch_to, option, *args, **kargs)9991000Note that the argument ``self`` is necessary here because the1001dispatcher is a method of the options class and not of1002``self``. The value of the option can also be set to a1003user-defined function, with arguments ``self`` and ``option``;1004in this case the user's function is called instead.10051006EXAMPLES:10071008Here is a contrived example::10091010sage: from sage.structure.global_options import GlobalOptions1011sage: DelimitedListOptions=GlobalOptions('list delimiters',1012... delim=dict(default='b', values={'b':'brackets', 'p':'parentheses'}))1013sage: class DelimitedList(CombinatorialObject):1014... options=DelimitedListOptions1015... def _repr_b(self): return '[%s]' % ','.join('%s'%i for i in self._list)1016... def _repr_p(self): return '(%s)' % ','.join('%s'%i for i in self._list)1017... def _repr_(self): return self.options.dispatch(self, '_repr_','delim')1018sage: dlist=DelimitedList([1,2,3]); dlist1019[1,2,3]1020sage: dlist.options(delim='p'); dlist1021(1,2,3)1022sage: dlist.options(delim=lambda self: '<%s>' % ','.join('%s'%i for i in self._list)); dlist1023<1,2,3>1024"""1025if callable(self._value[option]):1026try:1027return self._value[option](obj, *args, **kargs)1028except TypeError:1029raise ValueError('the user defined dispatcher function failed!')1030else:1031if dispatch_to[-1]=='_': dispatch_to=dispatch_to[:-1]1032dispatch=getattr(obj,dispatch_to+'_'+self._value[option])1033return dispatch(*args, **kargs)10341035raise ValueError('%s is not a dispatchable option!' % option)103610371038def reset(self, option=None):1039r"""1040Reset options to their default value.10411042INPUT:10431044- ``option`` -- (Default: ``None``) The name of an option as a string1045or ``None``. If ``option`` is specified only this option is reset to1046its default value; otherwise all options are reset.10471048EXAMPLES::10491050sage: from sage.structure.global_options import GlobalOptions1051sage: opts=GlobalOptions('daily meal',1052... food=dict(default='bread', values=dict(bread='rye bread', salmon='a fish')),1053... drink=dict(default='water',values=dict(water='essential for life',wine='essential')))1054sage: opts(food='salmon'); opts()1055Current options for daily meal1056- drink: water1057- food: salmon1058sage: opts.reset('drink'); opts()1059Current options for daily meal1060- drink: water1061- food: salmon1062sage: opts.reset(); opts()1063Current options for daily meal1064- drink: water1065- food: bread1066"""1067if option is None:1068for option in self._default_value:1069self._value[option] = self._default_value[option]1070if not self._case_sensitive[option] and isinstance(self._value[option],str):1071self._value[option] = self._value[option].lower()1072for option in self._linked_value:1073link, linked_opt=self._linked_value[option]1074link.reset(linked_opt)1075else:1076option=self._match_option(option)1077if option in self._default_value:1078self._value[option] = self._default_value[option]1079if not self._case_sensitive[option] and isinstance(self._value[option],str):1080self._value[option] = self._value[option].lower()1081elif option in self._linked_value:1082link, linked_opt=self._linked_value[option]1083link.reset(linked_opt)1084108510861087