X Tutup
Skip to content

Commit e14825a

Browse files
tests for line accessors
passing tests for line accessors
1 parent 5c8ef06 commit e14825a

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

bpython/curtsiesfrontend/line.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Extracting and changing portions of the current line
2+
3+
All functions take cursor offset from the beginning of the line and the line of python code,
4+
and return None, or a tuple of the start index, end index, and the word"""
5+
6+
import re
7+
8+
def current_word(cursor_offset, line):
9+
"""the object.attribute.attribute just before or under the cursor"""
10+
pos = cursor_offset
11+
matches = list(re.finditer(r'[\w_][\w0-9._]*[(]?', line))
12+
start = pos
13+
end = pos
14+
word = None
15+
for m in matches:
16+
if m.start() < pos and m.end() >= pos:
17+
start = m.start()
18+
end = m.end()
19+
word = m.group()
20+
if word is None:
21+
return None
22+
return (start, end, word)
23+
24+
def current_dict_key(cursor_offset, line):
25+
"""If in dictionary completion, return the current key"""
26+
matches = list(re.finditer(r'[\w_][\w0-9._]*\[([\w0-9._(), ]*)', line))
27+
for m in matches:
28+
if m.start(1) <= cursor_offset and m.end(1) >= cursor_offset:
29+
return (m.start(1), m.end(1), m.group(1))
30+
return None
31+
32+
def current_dict(cursor_offset, line):
33+
"""If in dictionary completion, return the dict that should be used"""
34+
matches = list(re.finditer(r'([\w_][\w0-9._]*)\[([\w0-9._(), ]*)', line))
35+
for m in matches:
36+
if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset:
37+
return (m.start(1), m.end(1), m.group(1))
38+
return None
39+
40+
def current_string(cursor_offset, line):
41+
"""If inside a string, return the string (excluding quotes)
42+
43+
Weaker than bpython.Repl's current_string, because that checks that a string is a string
44+
based on previous lines in the buffer"""
45+
matches = list(re.finditer('''(?P<open>(?:""")|"|(?:''\')|')(.*?)(?P=open)''', line))
46+
for m in matches:
47+
if m.start(2) <= cursor_offset and m.end(2) >= cursor_offset:
48+
return m.start(2), m.end(2), m.group(2)
49+
return None
50+
51+
def current_object(cursor_offset, line):
52+
"""If in attribute completion, the object on which attribute should be looked up"""
53+
match = current_word(cursor_offset, line)
54+
if match is None: return None
55+
start, end, word = match
56+
matches = list(re.finditer(r'([\w_][\w0-9_]*)[.]', word))
57+
s = ''
58+
for m in matches:
59+
if m.end(1) + start < cursor_offset:
60+
if s:
61+
s += '.'
62+
s += m.group(1)
63+
if not s:
64+
return None
65+
return start, start+len(s), s
66+
67+
def current_object_attribute(cursor_offset, line):
68+
"""If in attribute completion, the attribute being completed"""
69+
match = current_word(cursor_offset, line)
70+
if match is None: return None
71+
start, end, word = match
72+
matches = list(re.finditer(r'([\w_][\w0-9_]*)[.]?', word))
73+
for m in matches[1:]:
74+
if m.start(1) + start <= cursor_offset and m.end(1) + start >= cursor_offset:
75+
return m.start(1) + start, m.end(1) + start, m.group(1)
76+
return None
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import unittest
2+
import re
3+
4+
from bpython.curtsiesfrontend.line import current_word, current_dict_key, current_dict, current_string, current_object, current_object_attribute
5+
6+
7+
def cursor(s):
8+
"""'ab|c' -> (2, 'abc')"""
9+
cursor_offset = s.index('|')
10+
line = s[:cursor_offset] + s[cursor_offset+1:]
11+
return cursor_offset, line
12+
13+
def decode(s):
14+
"""'a<bd|c>d' -> ((3, 'abcd'), (1, 3, 'bdc'))"""
15+
16+
if not ((s.count('<') == s.count('>') == 1 or s.count('<') == s.count('>') == 0)
17+
and s.count('|') == 1):
18+
raise ValueError('match helper needs <, and > to occur just once')
19+
matches = list(re.finditer(r'[<>|]', s))
20+
assert len(matches) in [1, 3], [m.group() for m in matches]
21+
d = {}
22+
for i, m in enumerate(matches):
23+
d[m.group(0)] = m.start() - i
24+
s = s[:m.start() - i] + s[m.end() - i:]
25+
assert len(d) in [1,3], 'need all the parts just once! %r' % d
26+
27+
if '<' in d:
28+
return (d['|'], s), (d['<'], d['>'], s[d['<']:d['>']])
29+
else:
30+
return (d['|'], s), None
31+
32+
class LineTestCase(unittest.TestCase):
33+
def assertAccess(self, s):
34+
r"""Asserts that self.func matches as described
35+
by s, which uses a little language to describe matches:
36+
37+
abcd<efg>hijklmnopqrstuvwx|yz
38+
/|\ /|\ /|\
39+
| | |
40+
the function should the current cursor position
41+
match this "efg" is between the x and y
42+
"""
43+
(cursor_offset, line), match = decode(s)
44+
self.assertEqual(self.func(cursor_offset, line), match)
45+
46+
class TestHelpers(LineTestCase):
47+
def test_I(self):
48+
self.assertEqual(cursor('asd|fgh'), (3, 'asdfgh'))
49+
50+
def test_decode(self):
51+
self.assertEqual(decode('a<bd|c>d'), ((3, 'abdcd'), (1, 4, 'bdc')))
52+
self.assertEqual(decode('a|<bdc>d'), ((1, 'abdcd'), (1, 4, 'bdc')))
53+
self.assertEqual(decode('a<bdc>d|'), ((5, 'abdcd'), (1, 4, 'bdc')))
54+
55+
def test_assert_access(self):
56+
def dumb_func(cursor_offset, line):
57+
return (0, 2, 'ab')
58+
self.func = dumb_func
59+
self.assertAccess('<a|b>d')
60+
61+
class TestCurrentWord(LineTestCase):
62+
def setUp(self):
63+
self.func = current_word
64+
65+
def test_simple(self):
66+
self.assertAccess('|')
67+
self.assertAccess('|asdf')
68+
self.assertAccess('<a|sdf>')
69+
self.assertAccess('<asdf|>')
70+
self.assertAccess('<asdfg|>')
71+
self.assertAccess('asdf + <asdfg|>')
72+
self.assertAccess('<asdfg|> + asdf')
73+
74+
def test_inside(self):
75+
self.assertAccess('<asd|>')
76+
self.assertAccess('<asd|fg>')
77+
78+
def test_dots(self):
79+
self.assertAccess('<Object.attr1|>')
80+
self.assertAccess('<Object.attr1.attr2|>')
81+
self.assertAccess('<Object.att|r1.attr2>')
82+
self.assertAccess('stuff[stuff] + {123: 456} + <Object.attr1.attr2|>')
83+
self.assertAccess('stuff[<asd|fg>]')
84+
self.assertAccess('stuff[asdf[<asd|fg>]')
85+
86+
class TestCurrentDictKey(LineTestCase):
87+
def setUp(self):
88+
self.func = current_dict_key
89+
def test_simple(self):
90+
self.assertAccess('asdf|')
91+
self.assertAccess('asdf|')
92+
self.assertAccess('asdf[<>|')
93+
self.assertAccess('asdf[<>|]')
94+
self.assertAccess('object.dict[<abc|>')
95+
self.assertAccess('asdf|')
96+
self.assertAccess('asdf[<(>|]')
97+
self.assertAccess('asdf[<(1>|]')
98+
self.assertAccess('asdf[<(1,>|]')
99+
self.assertAccess('asdf[<(1, >|]')
100+
self.assertAccess('asdf[<(1, 2)>|]')
101+
102+
class TestCurrentDict(LineTestCase):
103+
def setUp(self):
104+
self.func = current_dict
105+
def test_simple(self):
106+
self.assertAccess('asdf|')
107+
self.assertAccess('asdf|')
108+
self.assertAccess('<asdf>[|')
109+
self.assertAccess('<asdf>[|]')
110+
self.assertAccess('<object.dict>[abc|')
111+
self.assertAccess('asdf|')
112+
113+
class TestCurrentString(LineTestCase):
114+
def setUp(self):
115+
self.func = current_string
116+
def test_simple(self):
117+
self.assertAccess('"<as|df>"')
118+
self.assertAccess('"<asdf|>"')
119+
self.assertAccess('"<|asdf>"')
120+
self.assertAccess("'<asdf|>'")
121+
self.assertAccess("'<|asdf>'")
122+
self.assertAccess("'''<asdf|>'''")
123+
self.assertAccess('"""<asdf|>"""')
124+
self.assertAccess('asdf.afd("a") + "<asdf|>"')
125+
126+
class TestCurrentObject(LineTestCase):
127+
def setUp(self):
128+
self.func = current_object
129+
def test_simple(self):
130+
self.assertAccess('<Object>.attr1|')
131+
self.assertAccess('<Object>.|')
132+
self.assertAccess('Object|')
133+
self.assertAccess('Object|.')
134+
self.assertAccess('<Object>.|')
135+
self.assertAccess('<Object.attr1>.attr2|')
136+
self.assertAccess('<Object>.att|r1.attr2')
137+
self.assertAccess('stuff[stuff] + {123: 456} + <Object.attr1>.attr2|')
138+
self.assertAccess('stuff[asd|fg]')
139+
self.assertAccess('stuff[asdf[asd|fg]')
140+
141+
class TestCurrentAttribute(LineTestCase):
142+
def setUp(self):
143+
self.func = current_object_attribute
144+
def test_simple(self):
145+
self.assertAccess('Object.<attr1|>')
146+
self.assertAccess('Object.attr1.<attr2|>')
147+
self.assertAccess('Object.<att|r1>.attr2')
148+
self.assertAccess('stuff[stuff] + {123: 456} + Object.attr1.<attr2|>')
149+
self.assertAccess('stuff[asd|fg]')
150+
self.assertAccess('stuff[asdf[asd|fg]')
151+
self.assertAccess('Object.attr1.<|attr2>')
152+
self.assertAccess('Object.<attr1|>.attr2')
153+
154+
if __name__ == '__main__':
155+
unittest.main()

0 commit comments

Comments
 (0)
X Tutup