├── .gitignore
├── .travis.yml
├── README.md
├── cut.py
├── cutter
├── __init__.py
└── utils.py
├── setup.py
├── test
├── __init__.py
├── test__init__.py
└── test_utils.py
└── tox.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | .tox
2 | build
3 | dist
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 2.6
4 | - 2.7
5 | - 3.2
6 | - 3.3
7 | - pypy
8 | install: pip install .
9 | script: py.test
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cutter
2 |
3 | Cutter is a small python module that add some sugar on top of list like objects to ease list traversal.
4 |
5 | ## Usage
6 |
7 | ```python
8 | from cutter import cut
9 |
10 | numbers = [
11 | ['one', 'two', 'three'],
12 | ['un', 'deux', 'trois'],
13 | ['uno', 'due', 'tre']
14 | ]
15 |
16 | >>> cut(numbers)[1] # Take the second element of every sublist
17 | ['two', 'deux', 'due']*
18 |
19 | >>> cut(numbers)[1, 2] # Take the third letter of every second element of every sublist
20 | ['o', 'u', 'e'].
21 |
22 | >>> cut(numbers)[1][2] # Idem
23 | ['o', 'u', 'e'].
24 |
25 | >>> cut(numbers)[1, 2].upper() # Upper case the third letter of every second element of every sublist
26 | ['O', 'U', 'E']
27 |
28 | >>> cut(numbers)[...] # Flatten the list for one level
29 | ['one', 'two', 'three', 'un', 'deux', 'trois', 'uno', 'due', 'tre']
30 |
31 | >>> cut(numbers)[..., 0] # First letter of every elements
32 | ['o', 't', 't', 'u', 'd', 't', 'u', 'd', 't'].
33 | ```
34 |
35 | ## Syntaxic sugars
36 |
37 | There are two syntaxic sugars for an even easier use:
38 |
39 | ### The |_ syntax
40 |
41 | ```python
42 | from cutter import _
43 |
44 | >>> numbers |_ [1]
45 | ['two', 'deux', 'due']*
46 |
47 | >>> (numbers |_ [1] |_ [2] |_ .upper)()
48 | ['O', 'U', 'E']
49 |
50 | >>> numbers |_ [...]
51 | ['one', 'two', 'three', 'un', 'deux', 'trois', 'uno', 'due', 'tre']
52 |
53 | >>> numbers |_ [..., 0]
54 | ['o', 't', 't', 'u', 'd', 't', 'u', 'd', 't'].
55 |
56 | ```
57 | ### The ! syntax
58 | This syntax is meant for use in shells.
59 |
60 | It is for example used in [wdb](https://github.com/Kozea/wdb) and the bundled cut.py interpreter.
61 |
62 | Cutter provide a `bang_compile` function which is a wrapper of the python builtin `compile` function.
63 |
64 |
65 | ```python
66 | # This code muste be compiled with cutter.utils.bang_compile
67 |
68 | >>> numbers!1
69 | ['two', 'deux', 'due']*
70 |
71 | >>> numbers!1!2!upper()
72 | ['O', 'U', 'E']
73 |
74 | >>> numbers!*
75 | ['one', 'two', 'three', 'un', 'deux', 'trois', 'uno', 'due', 'tre']
76 |
77 | >>> numbers!*!0
78 | ['o', 't', 't', 'u', 'd', 't', 'u', 'd', 't'].
79 | ```
80 |
81 | This syntax use the python tokenizer and ast to make it work. This is really useful when debugging to inspect list content.
82 |
83 | ### Use the ! syntax in interpreter
84 |
85 | This is at your own risk but you can add:
86 |
87 | ```python
88 |
89 | try:
90 | from cutter.utils import bang_compile, cut_as_builtin
91 | from code import InteractiveConsole
92 | import codeop
93 | except ImportError:
94 | pass
95 | else:
96 | sys.ps1 = "\001\033[1;36m\002!\001\033[1;32m\002>> \001\033[1;37m\002"
97 | codeop.compile = bang_compile
98 | cut_as_builtin()
99 | try:
100 | InteractiveConsole().interact('')
101 | except Exception:
102 | from traceback import print_exc
103 | print_exc()
104 | sys.exit(0)
105 | ```
106 |
107 | in your `~/.pythonrc`
108 |
109 | ### More
110 |
111 | Cutter works with dictionaries too:
112 | ```python
113 | cut(dict)['key']
114 | ```
115 |
116 | slices:
117 | ```python
118 | cut(list)[:5]
119 | ```
120 |
121 |
122 | For more examples see the test files : [test](/test)
123 |
124 | Cutter is compatible with at least: python 2.6, 2.7, 3.2, 3.3, 3.4 and pypy and is licensed under [lgpl v3](http://www.gnu.org/licenses/lgpl.html)
125 |
--------------------------------------------------------------------------------
/cut.py:
--------------------------------------------------------------------------------
1 | #!/bin/env python
2 |
3 | from code import InteractiveConsole
4 | from cutter.utils import bang_compile, cut_as_builtin
5 | import codeop
6 | import os
7 |
8 |
9 | filename = os.environ.get('PYTHONSTARTUP')
10 | if filename and os.path.isfile(filename):
11 | exec(open(filename).read())
12 |
13 |
14 | codeop.compile = bang_compile
15 | cut_as_builtin()
16 | InteractiveConsole().interact()
17 |
--------------------------------------------------------------------------------
/cutter/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of cutter
3 | #
4 | # Python list cutter tool
5 | # Copyright © 2013 Florian Mounier
6 | #
7 | # This library is free software: you can redistribute it and/or modify it under
8 | # the terms of the GNU Lesser General Public License as published by the Free
9 | # Software Foundation, either version 3 of the License, or (at your option) any
10 | # later version.
11 | #
12 | # This library is distributed in the hope that it will be useful, but WITHOUT
13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 | # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15 | # details.
16 | #
17 | # You should have received a copy of the GNU Lesser General Public License
18 | # along with pygal. If not, see .
19 | """
20 | cutter - Python list cutter tool
21 |
22 | """
23 |
24 | __version__ = '0.5.0'
25 |
26 |
27 | class EllipsisGetter(object):
28 | def __getitem__(self, key):
29 | return key
30 |
31 | ellipsis = EllipsisGetter()[...]
32 |
33 |
34 | is_string = lambda s: (
35 | isinstance(s, bytes) or
36 | isinstance(s, str))
37 |
38 | is_iterable = lambda i: (
39 | hasattr(i, '__iter__') and not
40 | isinstance(i, dict) and not
41 | is_string(i))
42 |
43 |
44 | def flatten(iterable):
45 | return [
46 | item
47 | for subiterable in iterable
48 | for item in flatten(subiterable)
49 | ] if is_iterable(iterable) else [iterable]
50 |
51 |
52 | class attr_cut(list):
53 |
54 | def __init__(self, iterable):
55 | self._ellipsis_at_next = getattr(iterable, '_ellipsis_at_next', False)
56 | super(attr_cut, self).__init__(iterable)
57 |
58 | @staticmethod
59 | def _cut(iterable, *args):
60 | """Cut a list by index or arg"""
61 | void = object()
62 | if not args:
63 | args = 0,
64 |
65 | if len(args) > 1:
66 | iterable = cut._cut(iterable, *args[:-1])
67 | index = args[-1]
68 | if index == ellipsis:
69 | return flatten(iterable)
70 |
71 | def cut_item(item):
72 | if isinstance(item, dict):
73 | return item.get(index, getattr(item, str(index), void))
74 |
75 | if hasattr(item, '__getitem__') and isinstance(
76 | index, (int, slice)):
77 | return (
78 | item.__class__.__getitem__(item, index)
79 | if isinstance(index, slice) or len(item) > index
80 | else void)
81 | return getattr(item, str(index), void)
82 |
83 | if not any(is_iterable(item) for item in iterable):
84 | # Can't use cut here because if nothing is iterable in the iterable
85 | # we want a real slicing list.
86 | # For cut chains use [1, 2] instead of [1][2]
87 | cut_cls = attr_cut
88 | else:
89 | cut_cls = cut
90 | return cut_cls([
91 | x for x in map(cut_item, iterable) if x is not void])
92 |
93 | def __getattr__(self, key):
94 | if key == '_ellipsis_at_next':
95 | return object.__getattribute__(self, '_ellipsis_at_next')
96 |
97 | if key == '_':
98 | self._ellipsis_at_next = True
99 | return self
100 |
101 | if self._ellipsis_at_next:
102 | key = ellipsis, key
103 | else:
104 | key = key,
105 |
106 | return self._cut(self, *key)
107 |
108 | def __call__(self, *args, **kwargs):
109 | return [e(*args, **kwargs) for e in self]
110 |
111 | def __repr__(self):
112 | return "%s." % list.__repr__(self)
113 |
114 |
115 | class cut(attr_cut):
116 | def __getitem__(self, key):
117 | if not is_iterable(key):
118 | key = key,
119 | return self._cut(self, *key)
120 |
121 | def __getslice__(self, min, max):
122 | return self._cut(self, slice(min, max))
123 |
124 | def __repr__(self):
125 | return "%s*" % list.__repr__(self)
126 |
127 |
128 | class ReverseCut(object):
129 | def __init__(self, key):
130 | self.key = key
131 |
132 | def __ror__(self, it):
133 | return cut(it)[self.key]
134 |
135 |
136 | class SimpleGetItem(object):
137 | def __getitem__(self, key):
138 | return ReverseCut(key)
139 |
140 | def __getattr__(self, key):
141 | return ReverseCut(key)
142 |
143 |
144 | _ = SimpleGetItem()
145 |
--------------------------------------------------------------------------------
/cutter/utils.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from token import ERRORTOKEN, OP, NAME
3 | from tokenize import generate_tokens, untokenize
4 | import ast
5 | from cutter import cut
6 |
7 | if sys.version_info[0] == 2:
8 | from StringIO import StringIO
9 | else:
10 | from io import StringIO
11 |
12 |
13 | def tokenize_bang(source):
14 | """Browse the source and replace any ! token by ._ast_replace_me_with_cut()
15 | """
16 |
17 | stream = StringIO()
18 | stream.write(source)
19 | stream.seek(0)
20 | tokenized = []
21 | last_token = ''
22 | for token_type, token, srowcol, _, _ in generate_tokens(stream.readline):
23 | if token_type == ERRORTOKEN and token == '!' and srowcol != (1, 0):
24 | tokenized.append((OP, '.'))
25 | if last_token == '\x00':
26 | tokenized.append((NAME, '_'))
27 | else:
28 | tokenized.append((NAME, '_ast_replace_me_with_cut'))
29 | tokenized.append((OP, '('))
30 | tokenized.append((OP, ')'))
31 | last_token = '\x00'
32 | else:
33 | if last_token == '\x00':
34 | if token.isdigit():
35 | tokenized.append((OP, '['))
36 | tokenized.append((token_type, token))
37 | tokenized.append((OP, ']'))
38 | elif token == '*':
39 | tokenized.append((OP, '[...]'))
40 | elif token != '[':
41 | tokenized.append((OP, '.'))
42 | tokenized.append((token_type, token))
43 | else:
44 | tokenized.append((token_type, token))
45 | else:
46 | tokenized.append((token_type, token))
47 |
48 | last_token = token
49 |
50 | return untokenize(tokenized)
51 |
52 |
53 | class CutWrapper(ast.NodeTransformer):
54 | """Replace any thg.sthg._ast_replace_me_with_cut() by cut(thg.sthg)"""
55 |
56 | def visit_Call(self, node):
57 | self.generic_visit(node)
58 | if hasattr(node.func, 'attr') and (
59 | node.func.attr == '_ast_replace_me_with_cut'):
60 | new_node = ast.Call(
61 | func=ast.Name(id='cut', ctx=ast.Load()),
62 | args=[node.func.value],
63 | keywords=[], starargs=None, kwargs=None)
64 | ast.copy_location(new_node, node)
65 | ast.fix_missing_locations(new_node)
66 | return new_node
67 | return node
68 |
69 |
70 | def cut_as_builtin():
71 | __builtins__['cut'] = cut
72 |
73 |
74 | def bang_compile(
75 | source, filename, mode, *args, **kwargs):
76 | try:
77 | source_tokenized = tokenize_bang(source)
78 | ast_ = ast.parse(source_tokenized, mode=mode)
79 | source = CutWrapper().visit(ast_)
80 | except Exception:
81 | pass
82 | rv = compile(source, filename, mode, *args, **kwargs)
83 | return rv
84 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup, find_packages
5 | import os
6 | import re
7 |
8 |
9 | ROOT = os.path.dirname(__file__)
10 | with open(os.path.join(ROOT, 'cutter', '__init__.py')) as fd:
11 | __version__ = re.search("__version__ = '([^']+)'", fd.read()).group(1)
12 |
13 | setup(
14 | name="cutter",
15 | version=__version__,
16 | description="Python list cutter tool",
17 | author="Florian Mounier",
18 | author_email="paradoxxx.zero@gmail.com",
19 | packages=find_packages(),
20 | platforms="Any",
21 | provides=['cutter'],
22 | tests_require=["pytest"],
23 | classifiers=[
24 | "Development Status :: 4 - Beta",
25 | "Intended Audience :: Developers",
26 | "Operating System :: OS Independent",
27 | "Programming Language :: Python :: 2",
28 | "Programming Language :: Python :: 3"])
29 |
--------------------------------------------------------------------------------
/test/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paradoxxxzero/cutter/903aee604e6ccbae7f8db6b9004b8f277f90d07f/test/__init__.py
--------------------------------------------------------------------------------
/test/test__init__.py:
--------------------------------------------------------------------------------
1 | from cutter import cut, flatten, _
2 |
3 | list_of_dict = [
4 | {'a': 'a', 'b': 2, 0: 0},
5 | {'a': None, 'c': 4},
6 | {'z': 5, 'a': 0, 'b': 3, 5: 'foo'}
7 | ]
8 |
9 | list_of_list = [
10 | list(range(4)),
11 | list(reversed(range(4))),
12 | list(range(12, 37, 3))
13 | ]
14 |
15 | list_of_tuple = [
16 | tuple(range(4)),
17 | tuple(reversed(range(4))),
18 | tuple(range(12, 37, 3))
19 | ]
20 |
21 |
22 | class attr_cls(object):
23 | def __init__(self, **kwargs):
24 | self.__dict__.update(kwargs)
25 |
26 |
27 | list_of_obj = [
28 | attr_cls(at1=1, at2=23, at3=None, at4='foo'),
29 | attr_cls(at1=2, at2='bar', at4=attr_cls()),
30 | attr_cls(at1=[2., 4], at2=23, at3=None),
31 | attr_cls(at2={})
32 | ]
33 |
34 |
35 | list_of_dict_of_dict = [
36 | {'A': {'b': 1, 'c': 2},
37 | 'B': {'b': 12, 'd': 21}},
38 | {'C': {'a': 0, 'c': 2},
39 | 'B': {'b': 'u', 'd': None, 'a': {'p': 2, 'T': 12}}},
40 | {'B': {'a': {'T': 4, 'a': 3}, 'b': 17},
41 | 'A': {'p': 'a', 'e': '_', 'b': ''}},
42 | {'D': {'c': '0', 'z': 'w'},
43 | 'C': {'c': 'u', 'p': 'u'}}
44 | ]
45 |
46 | list_of_dict_of_list_of_dict = [
47 | {
48 | 'A': [
49 | {'b': 1, 'c': 2},
50 | {'b': 12, 'd': 21}],
51 | 'B': [
52 | {'a': 0, 'c': 2},
53 | {'b': 'u', 'd': None, 'a': {'p': 2, 'T': 12}}],
54 | }, {
55 | 'B': [
56 | {'a': {'T': 4, 'a': 3}, 'b': 17},
57 | {'p': 'a', 'e': '_', 'b': ''}],
58 | 'D': [
59 | {'c': '0', 'z': 'w'},
60 | {'c': 'u', 'p': 'u'}],
61 | }
62 | ]
63 |
64 |
65 | list_of_list_of_list_of_list = [
66 | [
67 | [
68 | [1, 2, 3],
69 | [4, 5, 6],
70 | [7, 8, 9]
71 | ], [
72 | [10, 11, 12],
73 | [13, 14, 15],
74 | [16, 17, 18]
75 | ]
76 | ],
77 | [
78 | [
79 | [19, 20, 21],
80 | [22, 23, 24],
81 | [25, 26, 27]
82 | ],
83 | [
84 | [28, 29, 30],
85 | [31, 32, 33],
86 | [34, 35, 36]
87 | ]
88 | ]
89 | ]
90 |
91 |
92 | class Cls(object):
93 | def __init__(self, attr):
94 | self.attr = attr
95 |
96 | def get_upper_attr(self):
97 | return self.attr.upper()
98 |
99 |
100 | def test_cut_list_of_dict():
101 | assert cut(list_of_dict)['a'] == ['a', None, 0]
102 | assert cut(list_of_dict)['a'][2] == 0
103 | assert cut(list_of_dict)['a', 2] == []
104 | assert cut(list_of_dict).a == ['a', None, 0]
105 | assert cut(list_of_dict)['b'] == [2, 3]
106 | assert cut(list_of_dict).b == [2, 3]
107 | assert cut(list_of_dict)['j'] == []
108 | assert cut(list_of_dict).j == []
109 | assert cut(list_of_dict)[5] == ['foo']
110 | assert cut(list_of_dict)[2] == []
111 | assert cut(list_of_dict)[0] == [0]
112 |
113 |
114 | def test_cut_cut():
115 | assert cut(list_of_dict) == list_of_dict
116 | assert cut(list_of_dict) == cut(cut(list_of_dict))
117 | assert cut(list_of_dict)['a'] == cut(cut(list_of_dict))['a']
118 | assert repr(cut(list_of_dict)) == repr(list_of_dict) + '*'
119 | assert repr(cut(list_of_dict)['a']) == "['a', None, 0]" + '.'
120 |
121 |
122 | def test_cut_list_of_list():
123 | assert cut(list_of_list)[3] == [3, 0, 21]
124 | assert cut(list_of_list)[1] == [1, 2, 15]
125 | assert cut(list_of_list)[5] == [27]
126 | assert cut(list_of_list)[50] == []
127 | assert cut(list_of_list)[0] == [0, 3, 12]
128 | assert cut(list_of_list)[:2] == [[0, 1], [3, 2], [12, 15]]
129 |
130 |
131 | def test_cut_list_of_tuple():
132 | assert cut(list_of_tuple)[3] == [3, 0, 21]
133 | assert cut(list_of_tuple)[1] == [1, 2, 15]
134 | assert cut(list_of_tuple)[5] == [27]
135 | assert cut(list_of_tuple)[50] == []
136 | assert cut(list_of_tuple)[0] == [0, 3, 12]
137 | assert cut(list_of_tuple)[:2] == [(0, 1), (3, 2), (12, 15)]
138 |
139 |
140 | def test_cut_list_of_obj():
141 | assert cut(list_of_obj)['at1'] == [1, 2, [2., 4]]
142 | assert cut(list_of_obj).at1 == [1, 2, [2., 4]]
143 | assert cut(list_of_obj)['at2'] == [23, 'bar', 23, {}]
144 | assert cut(list_of_obj)['at3'] == [None, None]
145 | assert cut(list_of_obj)['at5'] == []
146 |
147 |
148 | def test_flatten():
149 | assert flatten([1, 2, 3]) == [1, 2, 3]
150 | assert flatten([[1], [2, 3]]) == [1, 2, 3]
151 | assert flatten([[[1, 2], [3]], [4, [5]]]) == [1, 2, 3, 4, 5]
152 | assert flatten([1]) == [1]
153 | assert flatten(['ab']) == ['ab']
154 |
155 |
156 | def test_complex_cuts_list_of_dicts_of_dicts():
157 | assert cut(list_of_dict_of_dict)['A', 'b'] == [1, '']
158 | assert cut(list_of_dict_of_dict)['B', 'b'] == [12, 'u', 17]
159 | assert cut(list_of_dict_of_dict)['B', 'a', 'T'] == [12, 4]
160 |
161 |
162 | def test_complex_cuts_list_of_dicts_of_list_of_dicts():
163 | assert cut(list_of_dict_of_list_of_dict)['A', ..., 'b'] == [1, 12]
164 | assert cut(list_of_dict_of_list_of_dict)['B', ..., 'b'] == ['u', 17, '']
165 | assert cut(list_of_dict_of_list_of_dict)['B', ..., 'a', 'T'] == [12, 4]
166 | assert cut(list_of_dict_of_list_of_dict)['B', 1:, ..., 'a', 'T'] == [12]
167 | assert cut(list_of_dict_of_list_of_dict)['B', :1, ..., 'a', 'T'] == [4]
168 |
169 |
170 | def test_complex_cuts_list_of_list_of_list_of_list():
171 | assert cut(list_of_list_of_list_of_list)[1] == [
172 | [[10, 11, 12], [13, 14, 15], [16, 17, 18]],
173 | [[28, 29, 30], [31, 32, 33], [34, 35, 36]]]
174 |
175 | assert cut(list_of_list_of_list_of_list)[1, 1] == [
176 | [13, 14, 15], [31, 32, 33]
177 | ]
178 | assert cut(list_of_list_of_list_of_list)[1, 1] == (
179 | cut(list_of_list_of_list_of_list)[1][1])
180 |
181 | assert cut(list_of_list_of_list_of_list)[1, 1, 1] == [
182 | 14, 32
183 | ]
184 | assert cut(list_of_list_of_list_of_list)[1, 1, 1] == (
185 | cut(list_of_list_of_list_of_list)[1][1][1])
186 |
187 | assert cut(list_of_list_of_list_of_list)[1, 1, 1, 1] == []
188 |
189 | assert cut(list_of_list_of_list_of_list)[...] == list(range(1, 37))
190 |
191 | def test_cls():
192 | cls = [Cls('a'), Cls('r'), Cls('s')]
193 | assert cut(cls).attr == list('ars')
194 |
195 |
196 | def test_chain():
197 | cls = [Cls([Cls('a'), Cls('h')]), Cls([Cls('s'), Cls('u')])]
198 | assert cut(flatten(cut(cls).attr)).attr == list('ahsu')
199 | assert cut(cls).attr._.attr == list('ahsu')
200 |
201 | assert cut(cut(cls).attr) == cut(cls).attr
202 | assert cut(cut(cls).attr)._.attr == cut(cls).attr._.attr
203 | assert cut(cut(cls).attr._) == cut(cls).attr._
204 | assert cut(cut(cls).attr._)._ellipsis_at_next == cut(
205 | cls).attr._._ellipsis_at_next
206 | assert cut(cut(cls).attr._).attr == cut(cls).attr._.attr
207 | assert cut(cut(cut(cls).attr)._).attr == cut(cls).attr._.attr
208 |
209 |
210 | def test_call():
211 | cls = [Cls('a'), Cls('r'), Cls('s')]
212 | assert cut(cls).get_upper_attr() == list('ARS')
213 | assert cut('cu7').isalpha() == [True, True, False]
214 |
215 |
216 | def test_underscore():
217 | assert list_of_dict | _['a'] == ['a', None, 0]
218 | assert (list_of_dict | _['a'])[2] == 0
219 | assert list_of_dict | _['a', 2] == []
220 | assert list_of_dict | _.a == ['a', None, 0]
221 | assert list_of_dict | _['b'] == [2, 3]
222 | assert list_of_dict | _.b == [2, 3]
223 | assert list_of_dict | _['j'] == []
224 | assert list_of_dict | _.j == []
225 | assert list_of_dict | _[5] == ['foo']
226 | assert list_of_dict | _[2] == []
227 | assert list_of_dict | _[0] == [0]
228 | assert ('cu7' | _['isalpha'])() == [True, True, False]
229 |
--------------------------------------------------------------------------------
/test/test_utils.py:
--------------------------------------------------------------------------------
1 | from cutter import cut
2 | from cutter.utils import bang_compile
3 | from .test__init__ import (
4 | list_of_dict, list_of_list, list_of_tuple, list_of_obj, Cls,
5 | list_of_list_of_list_of_list)
6 | import sys
7 |
8 |
9 | cls = [Cls([Cls('a'), Cls('h')]), Cls([Cls('s'), Cls('u')])]
10 |
11 |
12 | def test_bang_compile_exec():
13 | scope = {'cut': cut}
14 | source = '''a = ['abc', 'def', 'ghi']
15 | b = []
16 | print('Test tokenizer !!')
17 | for i in range(3):
18 | b.append(str(a!2))
19 | b.append('End')
20 | '''
21 | code = bang_compile(source, '', 'exec')
22 |
23 | if sys.version_info[0] > 2:
24 | exec(code, scope, scope)
25 | else:
26 | exec('exec code in scope, scope')
27 | assert 'a' in scope
28 | assert 'b' in scope
29 | assert scope['b'] == [
30 | "['c', 'f', 'i'].",
31 | "['c', 'f', 'i'].",
32 | "['c', 'f', 'i'].",
33 | 'End']
34 |
35 |
36 | def run(code):
37 | return eval(bang_compile(code, '', 'eval'), globals())
38 |
39 |
40 | def test_bang_compile_dict():
41 | assert run('list_of_dict!a') == ['a', None, 0]
42 | assert run('list_of_dict!a[2]') == 0
43 | assert run('list_of_dict!a!2') == []
44 | assert run('list_of_dict!b') == [2, 3]
45 |
46 |
47 | def test_bang_compile_list():
48 | assert run('list_of_list!3') == [3, 0, 21]
49 |
50 |
51 | def test_bang_compile_tuple():
52 | assert run('list_of_tuple!3') == [3, 0, 21]
53 |
54 |
55 | def test_bang_compile_attr_chain():
56 | assert run('cls!attr._.attr') == list('ahsu')
57 | assert run('cls!attr!_!attr') == list('ahsu')
58 | assert run('cls!attr!_.attr') == list('ahsu')
59 | assert run('cls!attr!!attr') == list('ahsu')
60 |
61 |
62 | def test_bang_compile_ellipsis():
63 | assert run('list_of_list_of_list_of_list!*') == list(range(1, 37))
64 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py27,py33,py34,py35,pypy
3 |
4 | [testenv]
5 | deps = pytest-cov
6 | setenv = COVERAGE_FILE=.coverage-{envname}
7 | commands =
8 | coverage erase
9 | py.test --junitxml=junit-{envname}.xml --cov cutter
10 | coverage xml -o coverage-{envname}.xml
11 |
--------------------------------------------------------------------------------