Пример #1
0
    def testResolvingNoOptions(self):
        # An options container without any registered options must resolve to
        # an empty dict.

        oc = OptionsContainer()

        self.assertEqual(oc.resolve(), dict())
Пример #2
0
    def __init__(self, **kwargs):
        '''
        All kwargs will be used as options, except:
          * tag => use as tag(s)
        '''

        # Create the tag list and remove the tag argument from kwargs.        
        if 'tag' in kwargs:
            self.tags = make_tags_list(kwargs['tag'])
            del kwargs['tag']
        else:
            self.tags = []

        # Create the instance-level options and initialize them from the
        # remaining kwargs.
        #self.options = OptionsContainer(self.__class__.options)
        self.options = OptionsContainer()
        # Subclasses must override this method. We cannot do this in the
        # subclass constructor because it must be done before setting the kwargs
        # as options.
        self.register_options()
        self.options.set(**kwargs)

        # Add the instance-level set method. See __set for an explanation. 
        self.set = self.__set
Пример #3
0
    def testResolvingNoValues(self):
        # If not values are set, all values must resolve to notSpecified.

        oc = OptionsContainer()
        oc.register("color")
        oc.register("width")
        oc.register("pattern")

        self.assertEqual(oc.resolve(), {
            "color"  : notSpecified,
            "width"  : notSpecified,
            "pattern": notSpecified,
        })
Пример #4
0
class Item(metaclass=ItemMetaclass):
    '''
    Represents an item in the tree. Items typically contain (a) other items, and
    (b) item containers.

    An Item *instance* has the following attributes:
      * An "options" property (of type OptionsContainer), which contains the
        options for this specific instance.
      * A "set" method as a shortcut for setting these options.
      * A (potentially empty) set of tags to allow selective application of
        options (a tag is similar to a class in CSS).
      
    An Item *subclass* has the following (class) attributes:
      * An item accessor which will return a template item instance for a given
        tag specification, which can be a string or the empty slice to specify
        the default template. (TODO really empty slice?)
      * An "all" property as a shortcut for [:]
      * A "set" method as a shortcut for [:].set 
      * A constructor taking options like the set method

    Item subclasses should call the Item constructor with all *args and **kwargs
    and define a register_options method to register the options, like so:
        self.options.register("color", False, "black")
    Note that the Item constructor, which runs before the Item subclass
    constructor, sets the initial options. The options must already be
    registered at this point, so this cannot be done by the Item subclass
    constructor. 
    '''
    
    def __init__(self, **kwargs):
        '''
        All kwargs will be used as options, except:
          * tag => use as tag(s)
        '''

        # Create the tag list and remove the tag argument from kwargs.        
        if 'tag' in kwargs:
            self.tags = make_tags_list(kwargs['tag'])
            del kwargs['tag']
        else:
            self.tags = []

        # Create the instance-level options and initialize them from the
        # remaining kwargs.
        #self.options = OptionsContainer(self.__class__.options)
        self.options = OptionsContainer()
        # Subclasses must override this method. We cannot do this in the
        # subclass constructor because it must be done before setting the kwargs
        # as options.
        self.register_options()
        self.options.set(**kwargs)

        # Add the instance-level set method. See __set for an explanation. 
        self.set = self.__set


    ##############
    ## Children ##
    ##############

    def children(self):
        return [(name, attribute)
            for name, attribute in self.__dict__.items()
            if isinstance(attribute, Item)]

    def containers(self):
        return [(name, attribute)
            for name, attribute in self.__dict__.items()
            if isinstance(attribute, ItemContainer)]


    #############
    ## Options ##
    #############

    def register_options(self):
        raise NotImplementedError(
            "Item subclasses must implement the register_options method")

    def __set(self, **kwargs):
        '''
        A setter shortcut for the instance-level options.
        
        This can't be called "set" because there is already a class method with
        the same name (defined in the metaclass) and Python does not have
        separate namespaces for class methods and instance methods. Therefore,
        this method will be assigned to the name of "set" in the instance
        namespace by the constructor.
        '''
        self.options.set(**kwargs)

    def resolve_options(self, templates = None, inherited = None, indent="", verbose = False):
        def p(*args, **kwargs):
            if verbose:
                print(indent, *args, **kwargs)
                
        p("Resolve options for", self)
        p("* Templates:", templates)
        p("* Tags:", self.tags)

        # Determine the applicable templates: the ones kindly selected by our
        # parent, plus the matching templates from our own class.  
        templates = templates or []
        templates = templates + type(self).matching_templates(self.tags)
        template_option_containers = [t.options for t in templates]

        inherited = inherited or dict()

        # Determine the options for self
        own_options = self.options.resolve(template_option_containers, inherited)
        #print(indent+"* Own options: {}".format(own_options))

        # Determine the options for direct children (recursively)
        children_options = {}
        for name, child in self.children():
            p("* Child", name)
            child_templates = [
                getattr(template, name)
                for template in templates
            ]

            child_inherited = own_options

            child_options = child.resolve_options(child_templates, child_inherited, indent = indent+"  ", verbose = verbose)
            children_options.update(child_options)

        # Determine the options for children in containers (recursively)
        containers_options = {}
        for name, container in self.containers():
            p("* Container", name, container)
            template_containers = [
                getattr(template, name)
                for template in templates + [self]
            ]
            p("* Template_containers", template_containers)
            
            for child in container.items:
                # Select the matching templates for the child
                child_templates = []
                for container in template_containers:
                    child_templates += container.matching_templates(child.tags)

                child_inherited = own_options

                child_options = child.resolve_options(child_templates, own_options, indent = indent+"  ", verbose = verbose)
                containers_options.update(child_options)

        result = {}
        result[self] = own_options
        result.update(children_options)
        result.update(containers_options)
        return result
Пример #5
0
    def testResolvingTemplate(self):
        # Create an options container with a number of options. The name of the
        # option indicates in which of the options containers the value will be
        # set.
        oc = OptionsContainer()
        oc.register("a")   # Set in template a
        oc.register("b")   # Set in template b
        oc.register("ab")  # Set in both templates
        oc.register("x")   # Set in this options container
        oc.register("xa")  # Set in this options container and template a
        oc.register("xb")  # Set in this options container and template b
        oc.register("xab") # Set in this options container and both templates

        # Create the templates - they are copies of the options container
        template_a = OptionsContainer(oc)
        template_b = OptionsContainer(oc)
        templates = [template_a, template_b]

        # In all options containers, set the respective options.
        oc        .set(x = "x", xa = "x", xb = "x", xab = "x")
        template_a.set(a = "a", ab = "a", xa = "a", xab = "a")
        template_b.set(b = "b", ab = "b", xb = "b", xab = "b")

        # Without templates
        self.assertEqual(oc.resolve(), {
            "a"  : notSpecified,
            "b"  : notSpecified,
            "ab" : notSpecified,
            "x"  : "x",
            "xa" : "x",
            "xb" : "x",
            "xab": "x",
        })

        # With only template a
        self.assertEqual(oc.resolve(templates = [template_a]), {
            "a"  : "a",
            "b"  : notSpecified,
            "ab" : "a",
            "x"  : "x",
            "xa" : "x",
            "xb" : "x",
            "xab": "x",
        })

        # With only template b
        self.assertEqual(oc.resolve(templates = [template_b]), {
            "a"  : notSpecified,
            "b"  : "b",
            "ab" : "b",
            "x"  : "x",
            "xa" : "x",
            "xb" : "x",
            "xab": "x",
        })

        # With both templates
        self.assertEqual(oc.resolve(templates = [template_a, template_b]), {
            "a"  : "a",
            "b"  : "b",
            "ab" : "a",
            "x"  : "x",
            "xa" : "x",
            "xb" : "x",
            "xab": "x",
        })

        # With both templates (inverse order)
        self.assertEqual(oc.resolve(templates = [template_b, template_a]), {
            "a"  : "a",
            "b"  : "b",
            "ab" : "b",
            "x"  : "x",
            "xa" : "x",
            "xb" : "x",
            "xab": "x",
        })
Пример #6
0
    def testResolvingIndirect(self):
        oc = OptionsContainer()
        oc.register("direct_only")
        oc.register("indirect_only")
        oc.register("both")

        oc.set         (direct_only    = "direct_value"  )
        oc.set         (both           = "direct_value"  )
        oc.set_indirect("indirect_only", "indirect_value")
        oc.set_indirect("both"         , "indirect_value")

        self.assertEqual(oc.resolve(), {
            "direct_only"  : "direct_value"  ,
            "indirect_only": "indirect_value",
            "both"         : "direct_value"  ,
        })
Пример #7
0
    def testResolvingValue(self):
        # Test setting values (single value, or multiple values in one call) and
        # overwriting previously-set values.

        # Define an OC and register options
        oc = OptionsContainer()
        oc.register("color")
        oc.register("width")
        oc.register("pattern")

        # Set a single value
        oc.set(color = "red")
        # Set multiple values at once
        oc.set(width = 2, pattern = "solid")

        self.assertEqual(oc.resolve(), {
            "color"  : "red",
            "width"  : 2,
            "pattern": "solid",
        })

        # Overwrite two values
        oc.set(color="green", width = 3)

        self.assertEqual(oc.resolve(), {
            "color"  : "green",
            "width"  : 3,
            "pattern": "solid",
        })
Пример #8
0
    def testCopying(self):
        oc1 = OptionsContainer()
        oc1.register("color")
        oc1.register("width")
        oc1.register("pattern")

        oc2 = OptionsContainer(oc1)

        # Set different values and make sure they don't overwrite each other
        oc1.set(color="red")
        self.assertEqual(oc1.resolve(pruneNotSpecified = True), {"color": "red"})
        self.assertEqual(oc2.resolve(pruneNotSpecified = True), {})

        oc2.set(color="green")
        self.assertEqual(oc1.resolve(pruneNotSpecified = True), {"color": "red"})
        self.assertEqual(oc2.resolve(pruneNotSpecified = True), {"color": "green"})

        oc1.set(color="blue")
        self.assertEqual(oc1.resolve(pruneNotSpecified = True), {"color": "blue"})
        self.assertEqual(oc2.resolve(pruneNotSpecified = True), {"color": "green"})
Пример #9
0
    def testResolvingDefault(self):
        # Define an OC and register options
        oc = OptionsContainer()
        oc.register("color"  , default = "black")
        oc.register("width"  , default = 1)
        oc.register("pattern", default = "solid")

        self.assertEqual(oc.resolve(), {
            "color"  : "black",
            "width"  : 1,
            "pattern": "solid",
        })

        # Set some (but not all) values
        oc.set(width = 3)
        oc.set(pattern = "dashed")

        self.assertEqual(oc.resolve(), {
            "color"  : "black",  # Still at its default value
            "width"  : 3,
            "pattern": "dashed",
        })
Пример #10
0
    def testResolvingDeferred(self):
        # Define an OC and register options
        oc = OptionsContainer()
        oc.register("markerFillColor"      , defer = "markerColor")
        oc.register("color"                                       )
        oc.register("markerBackgroundColor", defer = "markerColor")
        oc.register("markerColor"          , defer = "color")
        oc.register("markerLineColor"      , defer = "markerColor")

        optionNames = oc._optionNames();
        self.assertLess(optionNames.index("markerColor"), optionNames.index("markerFillColor"      ))
        self.assertLess(optionNames.index("markerColor"), optionNames.index("markerLineColor"      ))
        self.assertLess(optionNames.index("markerColor"), optionNames.index("markerBackgroundColor"))
        self.assertLess(optionNames.index("color")      , optionNames.index("markerColor"          ))

        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": notSpecified,
            "markerFillColor"      : notSpecified,
            "markerLineColor"      : notSpecified,
            "markerColor"          : notSpecified,
            "color"                : notSpecified,
        })

        # Set markerBackgroundColor, it must not affect anything else
        oc.set(markerBackgroundColor = "red")
        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": "red",
            "markerFillColor"      : notSpecified,
            "markerLineColor"      : notSpecified,
            "markerColor"          : notSpecified,
            "color"                : notSpecified,
        })

        # Set color, it must affect direct (markerColor) and indirect
        # (markerFillColor, markerLineColor) deferring.
        oc.set(color = "yellow")
        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": "red",
            "markerFillColor"      : "yellow",
            "markerLineColor"      : "yellow",
            "markerColor"          : "yellow",
            "color"                : "yellow",
        })

        # Set markerFillColor, it must now have that value
        oc.set(markerFillColor = "green")
        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": "red",
            "markerFillColor"      : "green",
            "markerLineColor"      : "yellow",
            "markerColor"          : "yellow",
            "color"                : "yellow",
        })

        # Set markerColor, it must affect markerLineColor (the only one without
        # explicit value so far)
        oc.set(markerColor = "blue")
        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": "red",
            "markerFillColor"      : "green",
            "markerLineColor"      : "blue",
            "markerColor"          : "blue",
            "color"                : "yellow",
        })

        # Set markerLineColor (now everything has an explicit value)
        oc.set(markerLineColor = "indigo")
        self.assertEqual(oc.resolve(), {
            "markerBackgroundColor": "red",
            "markerFillColor"      : "green",
            "markerLineColor"      : "indigo",
            "markerColor"          : "blue",
            "color"                : "yellow",
        })
Пример #11
0
    def testResolvingInherited(self):
        # Define an OC and register options
        # Options: v - value set, i - inherit, p - parent value set
        # Inherit is True for values including "i".
        oc = OptionsContainer()
        oc.register("vip", inherit = True)
        oc.register("vi" , inherit = True)
        oc.register("vp" )
        oc.register("v"  )
        oc.register("ip" , inherit = True)
        oc.register("i"  , inherit = True)
        oc.register("p"  )

        # Set the values of this options container (values including "v")
        oc.set(vip = "this")
        oc.set(vi  = "this")
        oc.set(vp  = "this")
        oc.set(v   = "this")

        # Set the parent values (values including "p")
        parent_values = {
            "vip": "parent",
            "vp" : "parent",
            "ip" : "parent",
            "p"  : "parent",
        }

        # Resolve without inherited values
        self.assertEqual(oc.resolve(), {
            "vip": "this",
            "vi" : "this",
            "vp" : "this",
            "v"  : "this",
            "ip" : notSpecified,
            "i"  : notSpecified,
            "p"  : notSpecified,
        })

        # Resolve with inherited values
        self.assertEqual(oc.resolve(inherited=parent_values), {
            "vip": "this",
            "vi" : "this",
            "vp" : "this",
            "v"  : "this",
            "ip" : "parent",
            "i"  : notSpecified,  # Parent has the value, but value is not inherited
            "p"  : notSpecified,  # Value is inherited, but parent does not have the value
        })