├── .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 | --------------------------------------------------------------------------------