summaryrefslogtreecommitdiff
path: root/scss/functions/library.py
blob: ce4a45c49f4ffda5fcd52e57ed6485341bcc1a90 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
"""Defines `FunctionLibrary`, a class that collects functions to be exposed to
SCSS during compilation.
"""

# TODO the constant versions of this should be frozen
class FunctionLibrary(object):
    """Contains a set of functions to be exposed to SCSS.

    Functions are keyed by both their name _and_ arity; this allows for
    function overloading somewhat beyond what Python easily allows, and is
    required for several functions in the standard Sass library.

    Functions may also have arbitrary arity, in which case they accept any
    number of arguments, though a function of the same name with a specific
    arity takes precedence.
    """

    def __init__(self):
        self._functions = {}

    def derive(self):
        """Returns a new registry object, pre-populated with all the functions
        in this registry.
        """
        new = type(self)()
        new.inherit(self)
        return new

    def inherit(self, *other_libraries):
        """Import all the functions from the given other libraries into this one.

        Note that existing functions ARE NOT replaced -- which also means that
        functions from the first library take precedence over functions from
        the second library, etc.
        """
        new_functions = dict()

        # dict.update replaces keys; go through the other libraries in reverse,
        # so the first one wins
        for library in reversed(other_libraries):
            new_functions.update(library._functions)

        new_functions.update(self._functions)
        self._functions = new_functions

    def register(self, name, argc=None):
        """Decorator for adding a function to this library."""
        # XXX: this should allow specifying names of keyword arguments, as
        # well.  currently none of these functions support kwargs, i think.
        # XXX automatically inferring the name and args would be...
        # interesting
        # XXX also it would be nice if a function which EXISTS but has the
        # wrong number of args threw a useful error; atm i think it'll become
        # literal (yikes!)

        key = (name, argc)

        def decorator(f):
            self._functions[key] = f
            return f

        return decorator

    def add(self, function, name, argc=None):
        """Add a function to this library imperatively."""

        key = (name, argc)
        self._functions[key] = function

    def lookup(self, name, argc=None):
        """Find a function given its name and the number of arguments it takes.
        Falls back to a function with the same name that takes any number of
        arguments.
        """
        # Try the given arity first
        key = name, argc
        if key in self._functions:
            return self._functions[key]

        # Fall back to arbitrary-arity (or KeyError if neither exists)
        return self._functions[name, None]