webstring / webstring / base.py

  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
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# -*- coding: utf-8 -*-
'''webstring base'''

import string
from keyword import kwlist

from stuf.six import items

# exceptions
_exceptions = [
    'maximum allowed repetitions exceeded',
    'invalid template source',
    'invalid type for formatting',
    'not all arguments converted during formatting',
    'not enough arguments for format', '', '',
    'invalid inline template source',
    'delimiter "$" or "%" not found',
    'invalid template filter type',
]
_Stemplate = string.Template
# illegal characters for Python names
_ichar = '()[]{}@,:.`=;+-*/%&|^><\'"#\\$?!~'
# reserve webstring specific words
kwlist.extend([
    'append', 'atemplates', 'current', 'default', 'exclude', 'fromfile',
    'fromstring', 'groupmark', 'include', 'mark', 'max', 'pipe', 'purge',
    'render', 'repeat', 'reset', 'template', 'templates', 'text', 'update',
    'write',
])
_reserve, _keywords = string.maketrans('', ''), frozenset(kwlist)


def _checkname(name):
    '''
    Ensures :class:`str` is a legal Python name.
    '''
    # remove characters that are illegal in a Python name
    if '}' not in name:
        name = name.replace('.', '_').translate(_reserve, _ichar)
    # handle XML namespaced names
    else:
        name = name.split('}')[1].replace('.', '_').translate(_reserve, _ichar)
    # add _ if value is a Python keyword
    if name in _keywords:
        return ''.join([name, '_'])
    return name


class _Base(object):

    '''
    Template base.
    '''

    def __init__(self, auto, **kw):
        # controls if templates become object attrs of a root/group template
        self._auto = auto

    def __repr__(self):
        return '<Template "%s" at %x>' % (self.__name__, id(self))

    def __str__(self):
        return self.render()

    def __add__(self, data):
        '''
        Inserts data or another template's fields after the internal
        template and returns a modified copy of the template.
        '''
        self.__iadd__(data)
        newself = self.current
        self.reset()
        return newself

    __radd__ = __add__

    def __mul__(self, num):
        '''
        Inserts a copy of the internal field after the internal field "num"
        number of times and returns a modified copy of the template.
        '''
        self.__imul__(num)
        newself = self.current
        self.reset()
        return newself

    __rmul__ = __mul__

    def __imul__(self, num):
        '''
        Inserts a copy of the internal field after the internal field "num"
        number of times and the template (self) is returned modified.
        '''
        # ensure "num" is not greater than maximum allowed repetitions
        if num <= self.max:
            tmp = self.current
            # concatenate "number" number of copies of self to self
            iadd = self.__iadd__
            for _ in xrange(num - 1):
                iadd(tmp.current)
            return self
        raise TypeError(_exceptions[0])

    def __mod__(self, data):
        '''
        Substitutes text data into the internal template and returns a
        modified copy of the template.
        '''
        self.__imod__(data)
        newself = self.current
        self.reset()
        return newself

    def __pow__(self, data):
        '''
        For each item in a tuple, the internal template is copied, the item is
        substituted into the copy's template, and the copy is inserted after
        the internal template. Finally, a modified copy of the template
        is returned.
        '''
        self.__ipow__(data)
        newself = self.current
        self.reset()
        return newself

    def __ipow__(self, data):
        '''
        For each item in a tuple, the internal template is copied, the content
        of the item is substituted into the copy's template, and the copy is
        inserted after the internal template. Finally, the modified template
        (self) is returned.
        '''
        if len(data) <= self.max:
            if isinstance(data, tuple):
                # put data in correct order
                data = list(reversed(data))
            else:
                raise TypeError('invalid type for formatting')
            dpop = data.pop
            # substitute content into existing field
            self.__imod__(dpop())
            # concatenate a new field with self for each item
            repeat = self.repeat
            while data:
                repeat(dpop())
            return self
        # raise exception if data length > maximum allowed repetitions
        raise TypeError(_exceptions[0])

    def _setmark(self, mark):
        # sets template variable delimiter
        self._mark = mark

    def pipe(self, info=None, format='xml'):
        '''
        Returns the string output of the internal template and resets the
        template.

        :keyword info: data to substitute into a template
        :keyword str format: document format
        '''
        output = self.render(info, format)
        self.reset()
        return output

    def repeat(self, data=None):
        '''
        Copies the original state of the internal template, inserts it after
        the interal template, and, optionally, substitutes data into it.

        :keyword data: data to substitute into a template
        '''
        if data is not None:
            self.__iadd__(self.default.__imod__(data))
        else:
            self.__iadd__(self.default)

    def write(self, path, info=None, format='xml'):
        '''
        Writes a template's :class:`str` output to a file.

        :argument path: output file path
        :keyword info: data to substitute into a template
        :keyword str format document format
        '''
        open(path, 'wb').write(self.render(info, format))


class _Many(_Base):

    '''
    Base for Templates with subtemplates (groups or fields).
    '''

    def __init__(self, auto, maxlen, **kw):
        super(_Many, self).__init__(auto, **kw)
        # sets maximum allowed repetitions of a template
        self._max = maxlen
        # internal tracking structures
        self._fielddict, self._fields, self._filter = dict(), list(), set()

    def __getitem__(self, key):
        # try getting field by position from list
        try:
            return self._fields[key]
        # try getting field by keyword from dictionary
        except TypeError:
            return self._fielddict[key]

    def __delitem__(self, key):
        # handle positional indexes
        try:
            # get field
            obj = self._fields[key]
            # get field name
            for name, element in items(self._fielddict):
                if element == obj:
                    break
        # handle keys
        except TypeError:
            name = key
        # delete object attribute
        self.__delattr__(name)

    def __imod__(self, data):
        '''
        Substitutes text data into each field's template and the modified
        template (self) is returned.
        '''
        # get any templates
        try:
            self.templates(data.pop('templates'))
        except (AttributeError, KeyError):
            pass
        # get any substitutions
        try:
            self.__ipow__(data.pop('subs'))
        except (AttributeError, KeyError):
            pass
        # cache field and data length
        lself, length = len(self._fields), len(data)
        # if number of fields == number of items in data...
        if length == lself:
            fielddict = self._fielddict
            if isinstance(data, dict):
                for key, value in items(data):
                    # if tuple, expand it
                    try:
                        fielddict[key].__ipow__(value)
                    # if dictionary, substitute it
                    except TypeError:
                        fielddict[key].__imod__(value)
            elif isinstance(data, tuple):
                fields = self._fields
                # iterate with index and item through data
                for key, item in enumerate(data):
                    # if item is a tuple, expand it
                    try:
                        fields[key].__ipow__(item)
                    # if dictionary, substitute it
                    except TypeError:
                        fields[key].__imod__(item)
            else:
                raise TypeError(_exceptions[2])
        # return self if no more items in data
        elif length == 0:
            return self
        # raise exception if too many items to match all fields
        elif length > lself:
            raise TypeError(_exceptions[3])
        # raise exception if too few items to match all fields
        elif length < lself:
            raise TypeError(_exceptions[4])
        return self

    def __contains__(self, key):
        # if a field of a given name is in a template.
        return key in self._fielddict

    def __len__(self):
        # number of fields in a template.
        return len(self._fields)

    def __iter__(self):
        # iterator for the internal field list.
        return iter(self._fields)

    def _setfield(self, key, node):
        # sets a new field.
        self._fields.append(node)
        self._fielddict[key] = node
        # make field attribute of self if automagic on
        if self._auto:
            setattr(self, key, node)

    def _setmark(self, mark):
        # sets the variable delimiter for all subtemplates in a template.
        super(_Many, self)._setmark(mark)
        # set variable delimiter on all children
        for field in self._fields:
            field.mark = mark

    def _setgmark(self, mark):
        # set group delimiter.
        self._groupmark = mark

    def _setmax(self, maxlen):
        '''
        Sets the maximum repetition value for all Templates.
        '''
        # set group or root to max
        self._max = maxlen
        # set max on all children
        for field in self._fields:
            field.max = maxlen

    # property for setting a maximum repetition value
    max = property(lambda self: self._max, _setmax)


class _Field(_Base):

    '''
    Field base.
    '''

    def __init__(self, auto, maxlen, **kw):
        super(_Field, self).__init__(auto, **kw)
        # maximum repetition value and internal trackers
        self.max, self._siblings = maxlen, kw.get('siblings', list())
        self._tempfields = kw.get('tempfields', list())

    def __imod__(self, data):
        '''
        Substitutes text data into the internal element's text and attributes
        and returns this field (self) modified.
        '''
        if isinstance(data, basestring):
            self.text = data
        elif isinstance(data, dict):
            # try popping inline text content
            try:
                self.text = data.pop('text')
            except KeyError:
                pass
            # try popping substitutions
            try:
                self.__ipow__(data.pop('sub'))
            except KeyError:
                pass
        else:
            raise TypeError(_exceptions[2])
        return self


class _Group(_Many):

    '''
    Group base.
    '''

    def __init__(self, auto, maxlen, **kw):
        super(_Group, self).__init__(auto, maxlen, **kw)
        # Internal trackers
        self._siblings = kw.get('siblings', list())
        self._tempfields = kw.get('tempfields', list())


class _Template(_Many):

    '''
    Root template base.
    '''

    def __init__(self, auto, maxlen, **kw):
        super(_Template, self).__init__(auto, maxlen, **kw)

    def append(self, data):
        '''
        Makes a string or another template's internal template part of the
        template's internal template.

        :param data: template or element
        '''
        self.__iadd__(data)

    def exclude(self, *args):
        '''
        Excludes fields or groups from a template.

        :param args: names of a field or group
        '''
        # remove fields from root
        checkname_ = _checkname
        fadd = self._filter.add
        sdelitem = self.__delitem__
        for arg in args:
            name = checkname_(arg)
            # add to internal filter list
            fadd(name)
            # remove field if present
            sdelitem(name)
        # remove fields from children
        for index, field in enumerate(self._fields):
            # run only on groups
            if hasattr(field, 'groupmark'):
                for arg in args:
                    name = checkname_(arg)
                    # remove subfield if present
                    field.__delitem__(name)
                # remove empty groups
                if len(field) == 0:
                    sdelitem(index)

    def include(self, *args):
        '''
        Includes a field or group in a template.

        :param args: names of fields or groups
        '''
        # remove from internal tracker
        self._filter -= set(args)
        self.reset()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.