Skip to content

Commit b78e707

Browse files
authored
Merge pull request #30 from amathael/master
Created wrap_many(*args) function
2 parents 7995be8 + 9aa0d71 commit b78e707

File tree

2 files changed

+107
-13
lines changed

2 files changed

+107
-13
lines changed

tempy/tempy.py

+56-8
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
# @author: Federico Cerchiari <[email protected]>
33
"""Main Tempy classes"""
44
import html
5+
from collections import deque, Iterable
56
from copy import copy
6-
from uuid import uuid4
7-
from itertools import chain
87
from functools import wraps
9-
from collections import deque
8+
from itertools import chain
109
from types import GeneratorType
10+
from uuid import uuid4
1111

12-
from .exceptions import TagError, WrongContentError, DOMModByKeyError, DOMModByIndexError
12+
from .exceptions import TagError, WrongContentError, WrongArgsError, DOMModByKeyError, DOMModByIndexError
1313
from .tempyrepr import REPRFinder
1414

1515

1616
class DOMGroup:
1717
"""Wrapper used to manage element insertion."""
18+
1819
def __init__(self, name, obj):
1920
super().__init__()
2021
if not name and issubclass(obj.__class__, DOMElement):
@@ -167,7 +168,7 @@ def __imul__(self, n):
167168
if n == 0:
168169
self.parent.pop(self._own_index)
169170
return self
170-
return self.after(self * (n-1))
171+
return self.after(self * (n - 1))
171172

172173
def to_code(self, pretty=False):
173174
ret = []
@@ -252,6 +253,7 @@ def _iter_child_renders(self, pretty=False):
252253
# this trick is used to avoid circular imports
253254
class Patched(tempyREPR_cls, DOMElement):
254255
pass
256+
255257
child = Patched(child)
256258
try:
257259
yield child.render(pretty=pretty)
@@ -272,14 +274,17 @@ def content_receiver(reverse=False):
272274
Takes args and kwargs and calls the decorated method one time for each argument provided.
273275
The reverse parameter should be used for prepending (relative to self) methods.
274276
"""
277+
275278
def _receiver(func):
276279
@wraps(func)
277280
def wrapped(inst, *tags, **kwtags):
278281
for i, dom_group in yield_domgroups(tags, kwtags, reverse):
279282
inst._stable = False
280283
func(inst, i, dom_group)
281284
return inst
285+
282286
return wrapped
287+
283288
return _receiver
284289

285290
def _insert(self, dom_group, idx=None, prepend=False):
@@ -298,7 +303,7 @@ def _insert(self, dom_group, idx=None, prepend=False):
298303
for i_group, elem in enumerate(dom_group):
299304
if elem is not None:
300305
# Element insertion in this DOMElement childs
301-
self.childs.insert(idx+i_group, elem)
306+
self.childs.insert(idx + i_group, elem)
302307
# Managing child attributes if needed
303308
if issubclass(elem.__class__, DOMElement):
304309
elem.parent = self
@@ -393,7 +398,6 @@ def append_to(self, father):
393398

394399
def wrap(self, other):
395400
"""Wraps this element inside another empty tag."""
396-
# TODO: make multiple with content_receiver
397401
if other.childs:
398402
raise TagError(self, 'Wrapping in a non empty Tag is forbidden.')
399403
if self.parent:
@@ -402,6 +406,51 @@ def wrap(self, other):
402406
other.append(self)
403407
return self
404408

409+
def wrap_many(self, *args, strict=False):
410+
"""Wraps different copies of this element inside all empty tags
411+
listed in params or param's (non-empty) iterators.
412+
413+
Returns list of copies of this element wrapped inside args
414+
or None if not succeeded, in the same order and same structure,
415+
i.e. args = (Div(), (Div())) -> value = (A(...), (A(...)))
416+
417+
If on some args it must raise TagError, it will only if strict is True,
418+
otherwise it will do nothing with them and return Nones on their positions"""
419+
420+
for arg in args:
421+
is_elem = arg and isinstance(arg, DOMElement)
422+
is_elem_iter = (not is_elem and arg and isinstance(arg, Iterable) and
423+
isinstance(iter(arg).__next__(), DOMElement))
424+
if not (is_elem or is_elem_iter):
425+
raise WrongArgsError(self, 'Argument {} is not DOMElement nor iterable of DOMElements'.format(arg))
426+
427+
wcopies = []
428+
failure = []
429+
430+
def wrap_next(tag, idx):
431+
nonlocal wcopies, failure
432+
next_copy = self.__copy__()
433+
try:
434+
return next_copy.wrap(tag)
435+
except TagError:
436+
failure.append(idx)
437+
return next_copy
438+
439+
for arg_idx, arg in enumerate(args):
440+
if isinstance(arg, DOMElement):
441+
wcopies.append(wrap_next(arg, (arg_idx, -1)))
442+
else:
443+
iter_wcopies = []
444+
for iter_idx, t in enumerate(arg):
445+
iter_wcopies.append(wrap_next(t, (arg_idx, iter_idx)))
446+
wcopies.append(type(arg)(iter_wcopies))
447+
448+
if failure and strict:
449+
raise TagError(self, 'Wrapping in a non empty Tag is forbidden, failed on arguments ' +
450+
', '.join(list(map(lambda idx: str(idx[0]) if idx[1] == -1 else '[{1}] of {0}'.format(*idx),
451+
failure))))
452+
return wcopies
453+
405454
def wrap_inner(self, other):
406455
self.move_childs(other)
407456
self(other)
@@ -599,7 +648,6 @@ def render(self, *args, **kwargs):
599648

600649

601650
class Escaped(DOMElement):
602-
603651
def __init__(self, content, **kwargs):
604652
super().__init__(**kwargs)
605653
self._render = content

tests/test_DOMElement.py

+51-5
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
"""
55
import unittest
66

7+
from tempy.elements import Tag, TagAttrs
8+
from tempy.exceptions import WrongContentError, WrongArgsError, TagError, DOMModByKeyError, DOMModByIndexError
79
from tempy.tags import Div, A, P, Html, Head, Body
810
from tempy.tempy import DOMElement, DOMGroup, Escaped
9-
from tempy.elements import Tag, TagAttrs
10-
from tempy.exceptions import WrongContentError, TagError, DOMModByKeyError, DOMModByIndexError
1111

1212

1313
class TestDOMelement(unittest.TestCase):
14-
1514
def setUp(self):
1615
self.page = Html()
1716

@@ -84,13 +83,13 @@ def test_after(self):
8483
new1 = Div().append_to(self.page)
8584
new2 = Div()
8685
new1.after(new2)
87-
self.assertEqual(new1._own_index, new2._own_index-1)
86+
self.assertEqual(new1._own_index, new2._own_index - 1)
8887

8988
def test_before(self):
9089
new1 = Div().append_to(self.page)
9190
new2 = Div()
9291
new1.before(new2)
93-
self.assertEqual(new1._own_index, new2._own_index+1)
92+
self.assertEqual(new1._own_index, new2._own_index + 1)
9493

9594
def test_prepend(self):
9695
self.page(Div(), Div())
@@ -134,6 +133,53 @@ def test_wrap(self):
134133
self.assertTrue(to_wrap in container)
135134
self.assertTrue(container in outermost)
136135

136+
def test_wrap_many(self):
137+
def flatten(cnt):
138+
res = []
139+
for el in cnt:
140+
if isinstance(el, DOMElement):
141+
res.append(el)
142+
else:
143+
res.extend(el)
144+
return res
145+
146+
def test_return_values(inp, outp):
147+
self.assertEqual(len(inp), len(outp))
148+
for _ in range(len(inp)):
149+
t1, t2 = type(inp[_]), type(outp[_])
150+
self.assertTrue(t1 == t2 or
151+
issubclass(t1, DOMElement) and issubclass(t2, DOMElement))
152+
153+
def test_correctly_wrapped(child, parent):
154+
self.assertTrue(child in parent)
155+
self.assertTrue(child.get_parent() == parent)
156+
157+
# check if it works correct with correct arguments
158+
args = (Div(), [Div(), Div()], (Div(), Div()))
159+
new = A().wrap_many(*args)
160+
test_return_values(args, new)
161+
for c, p in zip(flatten(new), flatten(args)):
162+
test_correctly_wrapped(c, p)
163+
164+
# check if it raises TagError with strict and returns None without
165+
args = (Div()(A()), (Div(), Div()))
166+
with self.assertRaisesRegex(TagError, r'^.+arguments 0$'):
167+
A().wrap_many(*args, strict=True)
168+
new = A().wrap_many(*args)
169+
self.assertIs(new[0].get_parent(), None)
170+
171+
args = (Div()(A()), (Div(), Div()(A())))
172+
with self.assertRaisesRegex(TagError, r'^.+arguments 0, \[1\] of 1'):
173+
A().wrap_many(*args, strict=True)
174+
new = A().wrap_many(*args)
175+
self.assertIs(new[0].get_parent(), None)
176+
self.assertIs(new[1][1].get_parent(), None)
177+
178+
# check if it raises WrongArgsError
179+
args = (Div(), '')
180+
with self.assertRaises(WrongArgsError):
181+
A().wrap_many(*args)
182+
137183
def test_replace_with(self):
138184
old = Div().append_to(self.page)
139185
old.replace_with(A())

0 commit comments

Comments
 (0)