1from fontParts.base.base import BaseDict, dynamicProperty, reference
2from fontParts.base import normalizers
3from fontParts.base.deprecated import DeprecatedLib, RemovedLib
4
5
6class BaseLib(BaseDict, DeprecatedLib, RemovedLib):
7
8    """
9    A Lib object. This object normally created as part of a
10    :class:`BaseFont`. An orphan Lib object can be created like this::
11
12        >>> lib = RLib()
13
14    This object behaves like a Python dictionary. Most of the dictionary
15    functionality comes from :class:`BaseDict`, look at that object for the
16    required environment implementation details.
17
18    Lib uses :func:`normalizers.normalizeLibKey` to normalize the key of
19    the ``dict``, and :func:`normalizers.normalizeLibValue` to normalize the
20    value of the ``dict``.
21    """
22
23    keyNormalizer = normalizers.normalizeLibKey
24    valueNormalizer = normalizers.normalizeLibValue
25
26    def _reprContents(self):
27        contents = []
28        if self.glyph is not None:
29            contents.append("in glyph")
30            contents += self.glyph._reprContents()
31        if self.font:
32            contents.append("in font")
33            contents += self.font._reprContents()
34        return contents
35
36    # -------
37    # Parents
38    # -------
39
40    # Glyph
41
42    _glyph = None
43
44    glyph = dynamicProperty("glyph", "The lib's parent glyph.")
45
46    def _get_glyph(self):
47        if self._glyph is None:
48            return None
49        return self._glyph()
50
51    def _set_glyph(self, glyph):
52        if self._font is not None:
53            raise AssertionError("font for lib already set")
54        if self._glyph is not None and self._glyph() != glyph:
55            raise AssertionError("glyph for lib already set and is not same as glyph")
56        if glyph is not None:
57            glyph = reference(glyph)
58        self._glyph = glyph
59
60    # Font
61
62    _font = None
63
64    font = dynamicProperty("font", "The lib's parent font.")
65
66    def _get_font(self):
67        if self._font is not None:
68            return self._font()
69        elif self._glyph is not None:
70            return self.glyph.font
71        return None
72
73    def _set_font(self, font):
74        if self._font is not None and self._font() != font:
75            raise AssertionError("font for lib already set and is not same as font")
76        if self._glyph is not None:
77            raise AssertionError("glyph for lib already set")
78        if font is not None:
79            font = reference(font)
80        self._font = font
81
82    # Layer
83
84    layer = dynamicProperty("layer", "The lib's parent layer.")
85
86    def _get_layer(self):
87        if self._glyph is None:
88            return None
89        return self.glyph.layer
90
91    # ---------------------
92    # RoboFab Compatibility
93    # ---------------------
94
95    def remove(self, key):
96        """
97        Removes a key from the Lib. **key** will be
98        a :ref:`type-string` that is the key to
99        be removed.
100
101        This is a backwards compatibility method.
102        """
103        del self[key]
104
105    def asDict(self):
106        """
107        Return the Lib as a ``dict``.
108
109        This is a backwards compatibility method.
110        """
111        d = {}
112        for k, v in self.items():
113            d[k] = v
114        return d
115
116    # -------------------
117    # Inherited Functions
118    # -------------------
119
120    def __contains__(self, key):
121        """
122        Tests to see if a lib name is in the Lib.
123        **key** will be a :ref:`type-string`.
124        This returns a ``bool`` indicating if the **key**
125        is in the Lib. ::
126
127            >>> "public.glyphOrder" in font.lib
128            True
129        """
130        return super(BaseLib, self).__contains__(key)
131
132    def __delitem__(self, key):
133        """
134        Removes **key** from the Lib. **key** is a :ref:`type-string`.::
135
136            >>> del font.lib["public.glyphOrder"]
137        """
138        super(BaseLib, self).__delitem__(key)
139
140    def __getitem__(self, key):
141        """
142        Returns the contents of the named lib. **key** is a
143        :ref:`type-string`.
144        The returned value will be a ``list`` of the lib contents.::
145
146            >>> font.lib["public.glyphOrder"]
147            ["A", "B", "C"]
148
149        It is important to understand that any changes to the returned lib
150        contents will not be reflected in the Lib object. If one wants to
151        make a change to the lib contents, one should do the following::
152
153            >>> lib = font.lib["public.glyphOrder"]
154            >>> lib.remove("A")
155            >>> font.lib["public.glyphOrder"] = lib
156        """
157        return super(BaseLib, self).__getitem__(key)
158
159    def __iter__(self):
160        """
161        Iterates through the Lib, giving the key for each iteration. The
162        order that the Lib will iterate though is not fixed nor is it
163        ordered.::
164
165            >>> for key in font.lib:
166            >>>     print key
167            "public.glyphOrder"
168            "org.robofab.scripts.SomeData"
169            "public.postscriptNames"
170        """
171        return super(BaseLib, self).__iter__()
172
173    def __len__(self):
174        """
175        Returns the number of keys in Lib as an ``int``.::
176
177            >>> len(font.lib)
178            5
179        """
180        return super(BaseLib, self).__len__()
181
182    def __setitem__(self, key, items):
183        """
184        Sets the **key** to the list of **items**. **key**
185        is the lib name as a :ref:`type-string` and **items** is a
186        ``list`` of items as :ref:`type-string`.
187
188            >>> font.lib["public.glyphOrder"] = ["A", "B", "C"]
189        """
190        super(BaseLib, self).__setitem__(key, items)
191
192    def clear(self):
193        """
194        Removes all keys from Lib,
195        resetting the Lib to an empty dictionary. ::
196
197            >>> font.lib.clear()
198        """
199        super(BaseLib, self).clear()
200
201    def get(self, key, default=None):
202        """
203        Returns the contents of the named key.
204        **key** is a :ref:`type-string`, and the returned values will
205        either be ``list`` of key contents or ``None`` if no key was
206        found. ::
207
208            >>> font.lib["public.glyphOrder"]
209            ["A", "B", "C"]
210
211        It is important to understand that any changes to the returned key
212        contents will not be reflected in the Lib object. If one wants to
213        make a change to the key contents, one should do the following::
214
215            >>> lib = font.lib["public.glyphOrder"]
216            >>> lib.remove("A")
217            >>> font.lib["public.glyphOrder"] = lib
218        """
219        return super(BaseLib, self).get(key, default)
220
221    def items(self):
222        """
223        Returns a list of ``tuple`` of each key name and key items.
224        Keys are :ref:`type-string` and key members are a ``list``
225        of :ref:`type-string`. The initial list will be unordered.
226
227            >>> font.lib.items()
228            [("public.glyphOrder", ["A", "B", "C"]),
229             ("public.postscriptNames", {'be': 'uni0431', 'ze': 'uni0437'})]
230        """
231        return super(BaseLib, self).items()
232
233    def keys(self):
234        """
235        Returns a ``list`` of all the key names in Lib. This list will be
236        unordered.::
237
238            >>> font.lib.keys()
239            ["public.glyphOrder", "org.robofab.scripts.SomeData",
240             "public.postscriptNames"]
241        """
242        return super(BaseLib, self).keys()
243
244    def pop(self, key, default=None):
245        """
246        Removes the **key** from the Lib and returns the ``list`` of
247        key members. If no key is found, **default** is returned.
248        **key** is a :ref:`type-string`. This must return either
249        **default** or a ``list`` of items as :ref:`type-string`.
250
251            >>> font.lib.pop("public.glyphOrder")
252            ["A", "B", "C"]
253        """
254        return super(BaseLib, self).pop(key, default)
255
256    def update(self, otherLib):
257        """
258        Updates the Lib based on **otherLib**. *otherLib** is a
259        ``dict`` of keys. If a key from **otherLib** is in Lib
260        the key members will be replaced by the key members from
261        **otherLib**. If a key from **otherLib** is not in the Lib,
262        it is added to the Lib. If Lib contain a key name that is not
263        in *otherLib**, it is not changed.
264
265            >>> font.lib.update(newLib)
266        """
267        super(BaseLib, self).update(otherLib)
268
269    def values(self):
270        """
271        Returns a ``list`` of each named key's members. This will be a list
272        of lists, the key members will be a ``list`` of :ref:`type-string`.
273        The initial list will be unordered.
274
275            >>> font.lib.items()
276            [["A", "B", "C"], {'be': 'uni0431', 'ze': 'uni0437'}]
277        """
278        return super(BaseLib, self).values()
279