
Enumerations
============

Overview
--------

A large number of settings in PowerPoint are a selection between a small,
finite set of discrete choices. Often, in the XML, these are represented by
a simple type defined as an enumeration of a set of short string values. This
page describes the strategy for providing access to these sets of values in
the various parts of the library that require them.


Notes
-----

* The general strategy is to provide a set of classes, one for each
  enumeration, that serves as the single source for all reference and
  behavior for that enumeration.
* These enumerations correspond one-to-one with Microsoft API enumerations.
* The naming is based on the Microsoft enumeration. .. all-caps snake case
  from the original mixed-case.


Definitions
-----------

member
    What constitutes a member of an enumeration? What attributes do all
    members have in common?

out-of-band member
    Is there such a thing? Or is there just an out-of-band value?

get-value
    ... value returned to indicate the current state of a property

set-value
    ... value passed to indicate the desired state of a property


Feature requirements
--------------------

Design principle: All required values and mappings are defined in the
enumeration class. All required names, data structures, and methods are
generated by the metaclass. They don't have to be generated manually.

* [X] feature: dotted name access

      enumeration values can be accessed from an enumeration class with
      dotted notation, e.g. ``MSO_FOO.BAR_BAZ``

* [X] feature: enumeration value behaves as an int

* [X] feature: __str__ value of enum member is the member name and int value

      In order to provide the developer with a directly readable name for an
      enumeration value (rather than a raw int), each enum values shall have
      a __str__ method that returns the member name. E.g.::

          >>> print("text_frame.auto_size is '%s'" % text_frame.auto_size)
          text_frame.auto_size is 'SHAPE_TO_FIT_TEXT (2)'

* [X] feature: out-of-band values -- like None, True, and False

      From time to time it is desireable to allow return and setting values
      that are not named members of the enumeration to provide a more
      Pythonic protocol for a property that uses the enumeration. For
      example::

          >>> run.underline
          None
          >>> run.underline = True
          >>> str(run.underline)
          'SINGLE'
          >>> run.underline = False
          >>> str(run.underline)
          'NONE'
          >>> run.underline = WD_UNDERLINE.DOUBLE
          >>> str(run.underline)
          'DOUBLE'

* [X] feature: alias decorator

      one or more convenience aliases can be declared for an Enumeration
      subclass by use of an @alias decorator

* [X] feature: setting validation

      In order to allow invalid input values to raise ValueError exceptions
      at the API method level, the enumeration shall provide an
      is_valid_setting() class method that returns False if the value passed
      as an argument is not a member of the enumeration. E.g.::

          assert MSO_AUTO_SIZE.is_member('foobar') is False

* [X] feature: to_XML translation

  + [X] issue: Not all enumerations have mappings to XML attribute values.
        Consider creating an XmlEnumeration subclass that provides this extra
        feature.

  + [X] issue: There exist 'return-only' enumeration values, out-of-band
        values used to indicate none of the other values apply, for example
        MIXED = -2 to indicate the call was made on a range that contains
        items not all having the same value.

      To provide a single authoritative mapping from enumeration member
      values and the XML attribute values they correspond to, the Enumeration
      class shall provide a to_xml(member_value) method that returns the XML
      value corresponding to the passed member value.

      The method shall raise ValueError if the passed value is not a member
      of the enumeration.

      The special XML value |None| indicates the attribute should be removed
      altogether and should be returned where that is the appropriate
      behavior.

      Where the Python value is an out-of-band value such as |None|, |True|,
      or |False|, there must be a valid XML value or |None| as the mapping.

* [X] feature: docstrings

      To provide built-in documentation for different options and to provide
      a source for option descriptions for a generated documentation page,
      each enumeration member shall have as its docstring a description of
      the member and the behavior that setting specifies in its context.

* [X] feature: auto-generate documentation

  + [X] add __ms_name__ var => 'MsoAutoSize'
  + [X] add __url__ var => 'http:// ...'

      In order to provide single-source for both code and documentation, the
      Enumeration class shall provide a documentation page generation method
      that generates Sphinx RST source for a documentation page for the
      enumeration. This page source shall be available as either the
      docstring (__doc__) value of the class or its __str__ value. The
      supplied docstring shall be pre-pended to the generated portion after
      the top matter such as page title and reference link are generated.

* [X] convert all existing enums to new type to make sure they all work

  + [X] MSO_AUTO_SIZE
  + [X] MSO_VERTICAL_ANCHOR (__init__ -> enum.text)
  + [X] MSO_FILL_TYPE (__init__ -> enum.dml)
  + [X] MSO_COLOR_TYPE (__init__ -> enum.dml)
  + [X] others ...
  + [>] PP_PARAGRAPH_ALIGNMENT (constants -> enum.text)
  + [X] TEXT_ALIGN_TYPE (constants -> delete)
  + [X] TEXT_ANCHORING_TYPE (constants -> delete)
  + [X] MSO_SHAPE_TYPE (constants.MSO -> enum.shapes.MSO_SHAPE_TYPE)
  + [X] MSO.ANCHOR_* (constants -> MSO_VERTICAL_ANCHOR)
  + [X] PP_PLACEHOLDER_TYPE: PH_TYPE_* in spec.py PpPlaceholderType
  + [X] ST_Direction: PH_ORIENT_*
  + [X] ST_PlaceholderSize: PH_SZ_*
  + [X] MSO_AUTO_SHAPE_TYPE (constants -> enum.shapes)
  + [X] What is pml_parttypes doing in spec? I thought that went a long time
        ago? must be dead code now.
  + [X] fix all enum references in documentation
  + [X] adjust all examples that use enum or constants
  + [X] spec: pretty sure default content types belong in opc somewhere

* [ ] ... starting to think that combining to_xml() and from_xml() into enums
      is not the best idea:

  + [ ] There are two separate concerns: (1) provide a symbolic reference for
        use in the API to specify an enumerated option. (2) provide one of
        potentially several mappings between that value and values in
        different domains.

  + [ ] could create custom Enumeration subclasses to accommodate special
        cases where there are mappings beyond to and from an XML attribute
        value.

  + [ ] could create value objects that perhaps encapsulate those mappings.
        I suppose that's what Enumeration is now.

* [X] issue: does to/from XML translation replace an ST_* class in oxml?
      (Probably can't fully replace, need validation and other bits as well.
      Might be best to wait until a few examples have accumulated.)

      In order to support DRY translation between enumeration values and XML
      simple type (ST_*) attribute values, the Enumeration class shall
      provide both to_xml(enum_value) and from_xml(attr_value) methods where
      enumeration values map to XML attribute values.

* [ ] issue: how handle case where more than one XML to name mapping is
      possible? Like both True and SINGLE set underline to 'single'? Where if
      anywhere does that cause a conflict?

      The other direction of this is when more than one XML value should map
      to None.

* [ ] issue: how add clsdict._xml_to_member dict unconditionally to
      subclasses of XmlEnumeration? use __init__ instead of __new__ for that
      part::

        def __init__(cls, clsname, bases, clsdict):
            cls._collect_valid_settings = []

      - init time might be too late, no handy ref to metaclass?
      - could catch __new__ on separate XmlMappedEnumMember metaclass and add
        the attribute(s) then.

* [ ] deprecate prior versions:

  + [ ] MSO_VERTICAL_ANCHOR from enum/__init__.py

* [ ] consider adding a feature whereby aliases can be defined for an enum
      member name

* [ ] consider what requirements an ST_* enumeration might have, aside from
      or in addition to an API enumeration

* [ ] need a page or whatever in the user guide to talk about how fills are
      applied and give example code

* [ ] how could enumeration documentation be generated automatically from
      enumerations as part of the Sphinx run?


Code Sketches
-------------

enabling an @alias decorator to define aliases for an enumeration class::

    #!/usr/bin/env python

    from __future__ import absolute_import, print_function


    def alias(*aliases):
        """
        Decorating a class with @alias('FOO', 'BAR', ..) allows the class to
        be referenced by each of the names provided as arguments.
        """
        def decorator(cls):
            for alias in aliases:
                globals()[alias] = cls
            return cls
        return decorator


    @alias('BAR', 'BAZ')
    class FOO(object):
        BAR = 'Foobarish'


    print("FOO.BAR => '%s'" % FOO.BAR)
    print("BAR.BAR => '%s'" % BAR.BAR)  # noqa
    print("BAZ.BAR => '%s'" % BAZ.BAR)  # noqa
