| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 81d6044 commit fe49677
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -153,3 +153,19 @@ def format(self, tbtext, lexer): | |||
| 153 | 153 | else: | |
| 154 | 154 | cur_line.append((token,text)) | |
| 155 | 155 | assert cur_line == [], cur_line | |
| 156 | + | ||
| 157 | + | ||
| 158 | + def code_finished_will_parse(s, compiler): | ||
| 159 | + """Returns a tuple of whether the buffer could be complete and whether it will parse | ||
| 160 | + | ||
| 161 | + True, True means code block is finished and no predicted parse error | ||
| 162 | + True, False means code block is finished because a parse error is predicted | ||
| 163 | + False, True means code block is unfinished | ||
| 164 | + False, False isn't possible - an predicted error makes code block done""" | ||
| 165 | + try: | ||
| 166 | + finished = bool(compiler(s)) | ||
| 167 | + code_will_parse = True | ||
| 168 | + except (ValueError, SyntaxError, OverflowError): | ||
| 169 | + finished = True | ||
| 170 | + code_will_parse = False | ||
| 171 | + return finished, code_will_parse | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,28 @@ | |||
| 1 | + """Tools for preparing code to be run in the REPL (removing blank lines, etc)""" | ||
| 2 | + import re | ||
| 3 | + | ||
| 4 | + from bpython.curtsiesfrontend.interpreter import code_finished_will_parse | ||
| 5 | + | ||
| 6 | + #TODO specifically catch IndentationErrors instead of any syntax errors | ||
| 7 | + | ||
| 8 | + def indent_empty_lines(s, compiler): | ||
| 9 | + """Indents blank lines that would otherwise cause early compilation | ||
| 10 | + | ||
| 11 | + Only really works if starting on a new line""" | ||
| 12 | + lines = s.split('\n') | ||
| 13 | + ends_with_newline = False | ||
| 14 | + if lines and not lines[-1]: | ||
| 15 | + ends_with_newline = True | ||
| 16 | + lines.pop() | ||
| 17 | + result_lines = [] | ||
| 18 | + | ||
| 19 | + for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']): | ||
| 20 | + if len(line) == 0: | ||
| 21 | + p_indent = re.match(r'\s*', p_line).group() | ||
| 22 | + n_indent = re.match(r'\s*', n_line).group() | ||
| 23 | + result_lines.append(min([p_indent, n_indent], key=len) + line) | ||
| 24 | + else: | ||
| 25 | + result_lines.append(line) | ||
| 26 | + | ||
| 27 | + return '\n'.join(result_lines) + ('\n' if ends_with_newline else '') | ||
| 28 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,3 @@ | |||
| 1 | - import code | ||
| 2 | 1 | import contextlib | |
| 3 | 2 | import errno | |
| 4 | 3 | import functools | |
@@ -17,7 +16,6 @@ | |||
| 17 | 16 | from pygments import format | |
| 18 | 17 | from bpython._py3compat import PythonLexer | |
| 19 | 18 | from pygments.formatters import TerminalFormatter | |
| 20 | - from interpreter import Interp | ||
| 21 | 19 | ||
| 22 | 20 | import blessings | |
| 23 | 21 | ||
@@ -45,6 +43,8 @@ | |||
| 45 | 43 | from bpython.curtsiesfrontend import events as bpythonevents | |
| 46 | 44 | from bpython.curtsiesfrontend.parse import parse as bpythonparse | |
| 47 | 45 | from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter | |
| 46 | + from bpython.curtsiesfrontend.preprocess import indent_empty_lines | ||
| 47 | + from bpython.curtsiesfrontend.interpreter import Interp, code_finished_will_parse | ||
| 48 | 48 | ||
| 49 | 49 | #TODO other autocomplete modes (also fix in other bpython implementations) | |
| 50 | 50 | ||
@@ -457,7 +457,7 @@ def process_control_event(self, e): | |||
| 457 | 457 | if ctrl_char is not None: | |
| 458 | 458 | return self.process_event(ctrl_char) | |
| 459 | 459 | simple_events = just_simple_events(e.events) | |
| 460 | - source = bad_empty_lines_removed(''.join(simple_events)) | ||
| 460 | + source = indent_empty_lines(''.join(simple_events), self.interp.compile) | ||
| 461 | 461 | ||
| 462 | 462 | with self.in_paste_mode(): | |
| 463 | 463 | for ee in source: | |
@@ -713,7 +713,7 @@ def send_session_to_external_editor(self, filename=None): | |||
| 713 | 713 | text = self.send_to_external_editor(for_editor) | |
| 714 | 714 | lines = text.split('\n') | |
| 715 | 715 | from_editor = [line for line in lines if line[:4] != '### '] | |
| 716 | - source = bad_empty_lines_removed('\n'.join(from_editor)) | ||
| 716 | + source = indent_empty_lines('\n'.join(from_editor), self.interp.compile) | ||
| 717 | 717 | self.history = source.split('\n') | |
| 718 | 718 | self.reevaluate(insert_into_history=True) | |
| 719 | 719 | self.current_line = lines[-1][4:] | |
@@ -828,7 +828,8 @@ def push(self, line, insert_into_history=True): | |||
| 828 | 828 | code_to_run = '\n'.join(self.buffer) | |
| 829 | 829 | ||
| 830 | 830 | logger.debug('running %r in interpreter', self.buffer) | |
| 831 | - c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer)) | ||
| 831 | + c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer), | ||
| 832 | + self.interp.compile) | ||
| 832 | 833 | self.saved_predicted_parse_error = not code_will_parse | |
| 833 | 834 | if c: | |
| 834 | 835 | logger.debug('finished - buffer cleared') | |
@@ -1429,63 +1430,6 @@ def just_simple_events(event_list): | |||
| 1429 | 1430 | simple_events.append(e) | |
| 1430 | 1431 | return simple_events | |
| 1431 | 1432 | ||
| 1432 | - def code_finished_will_parse(s): | ||
| 1433 | - """Returns a tuple of whether the buffer could be complete and whether it will parse | ||
| 1434 | - | ||
| 1435 | - True, True means code block is finished and no predicted parse error | ||
| 1436 | - True, False means code block is finished because a parse error is predicted | ||
| 1437 | - False, True means code block is unfinished | ||
| 1438 | - False, False isn't possible - an predicted error makes code block done""" | ||
| 1439 | - try: | ||
| 1440 | - finished = bool(code.compile_command(s)) | ||
| 1441 | - code_will_parse = True | ||
| 1442 | - except (ValueError, SyntaxError, OverflowError): | ||
| 1443 | - finished = True | ||
| 1444 | - code_will_parse = False | ||
| 1445 | - return finished, code_will_parse | ||
| 1446 | - | ||
| 1447 | - def bad_empty_lines_removed(s): | ||
| 1448 | - """Removes empty lines that would cause unfinished input to be evaluated""" | ||
| 1449 | - # If there's a syntax error followed by an empty line, remove the empty line | ||
| 1450 | - lines = s.split('\n') | ||
| 1451 | - #TODO this should be our interpreter object making this decision so it | ||
| 1452 | - # can be compiler directive (__future__ statement) -aware | ||
| 1453 | - #TODO specifically catch IndentationErrors instead of any syntax errors | ||
| 1454 | - | ||
| 1455 | - current_block = [] | ||
| 1456 | - complete_blocks = [] | ||
| 1457 | - for i, line in enumerate(s.split('\n')): | ||
| 1458 | - current_block.append(line) | ||
| 1459 | - could_be_finished, valid = code_finished_will_parse('\n'.join(current_block)) | ||
| 1460 | - if could_be_finished and valid: | ||
| 1461 | - complete_blocks.append(current_block) | ||
| 1462 | - current_block = [] | ||
| 1463 | - continue | ||
| 1464 | - elif could_be_finished and not valid: | ||
| 1465 | - if complete_blocks: | ||
| 1466 | - complete_blocks[-1].extend(current_block) | ||
| 1467 | - current_block = complete_blocks.pop() | ||
| 1468 | - if len(current_block) < 2: | ||
| 1469 | - return s #TODO return partial result instead of giving up | ||
| 1470 | - last_line = current_block.pop(len(current_block) - 2) | ||
| 1471 | - assert not last_line, last_line | ||
| 1472 | - new_finished, new_valid = code_finished_will_parse('\n'.join(current_block)) | ||
| 1473 | - if new_valid and new_finished: | ||
| 1474 | - complete_blocks.append(current_block) | ||
| 1475 | - current_block = [] | ||
| 1476 | - elif new_valid: | ||
| 1477 | - continue | ||
| 1478 | - else: | ||
| 1479 | - return s #TODO return partial result instead of giving up | ||
| 1480 | - | ||
| 1481 | - else: | ||
| 1482 | - return s #TODO return partial result instead of giving up | ||
| 1483 | - else: | ||
| 1484 | - continue | ||
| 1485 | - return '\n'.join(['\n'.join(block) | ||
| 1486 | - for block in complete_blocks + [current_block] | ||
| 1487 | - if block]) | ||
| 1488 | - | ||
| 1489 | 1433 | ||
| 1490 | 1434 | #TODO this needs some work to function again and be useful for embedding | |
| 1491 | 1435 | def simple_repl(): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,22 @@ | |||
| 1 | + # careful: whitespace is very important in this file | ||
| 2 | + # also, this code runs - so everything should be a noop | ||
| 3 | + | ||
| 4 | + class BlankLineBetweenMethods(object): | ||
| 5 | + def method1(self): | ||
| 6 | + pass | ||
| 7 | + | ||
| 8 | + def method2(self): | ||
| 9 | + pass | ||
| 10 | + | ||
| 11 | + def BlankLineInFunction(self): | ||
| 12 | + return 7 | ||
| 13 | + | ||
| 14 | + pass | ||
| 15 | + | ||
| 16 | + #StartTest-blank_lines_in_for_loop | ||
| 17 | + for i in range(2): | ||
| 18 | + pass | ||
| 19 | + | ||
| 20 | + pass | ||
| 21 | + #EndTest | ||
| 22 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,20 @@ | |||
| 1 | + #careful! Whitespace is very important in this file | ||
| 2 | + | ||
| 3 | + class BlankLineBetweenMethods(object): | ||
| 4 | + def method1(self): | ||
| 5 | + pass | ||
| 6 | + | ||
| 7 | + def method2(self): | ||
| 8 | + pass | ||
| 9 | + | ||
| 10 | + def BlankLineInFunction(self): | ||
| 11 | + return 7 | ||
| 12 | + | ||
| 13 | + pass | ||
| 14 | + | ||
| 15 | + #StartTest-blank_lines_in_for_loop | ||
| 16 | + for i in range(2): | ||
| 17 | + pass | ||
| 18 | + | ||
| 19 | + pass | ||
| 20 | + #EndTest | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,10 +1,11 @@ | |||
| 1 | 1 | # coding: utf8 | |
| 2 | 2 | import code | |
| 3 | + from contextlib import contextmanager | ||
| 4 | + from functools import partial | ||
| 3 | 5 | import os | |
| 6 | + from StringIO import StringIO | ||
| 4 | 7 | import sys | |
| 5 | 8 | import tempfile | |
| 6 | - from contextlib import contextmanager | ||
| 7 | - from StringIO import StringIO | ||
| 8 | 9 | ||
| 9 | 10 | import unittest | |
| 10 | 11 | try: | |
@@ -16,6 +17,7 @@ def skip(f): | |||
| 16 | 17 | py3 = (sys.version_info[0] == 3) | |
| 17 | 18 | ||
| 18 | 19 | from bpython.curtsiesfrontend import repl as curtsiesrepl | |
| 20 | + from bpython.curtsiesfrontend import interpreter | ||
| 19 | 21 | from bpython import config | |
| 20 | 22 | from bpython import args | |
| 21 | 23 | ||
@@ -28,24 +30,28 @@ def setup_config(conf): | |||
| 28 | 30 | setattr(config_struct, key, value) | |
| 29 | 31 | return config_struct | |
| 30 | 32 | ||
| 33 | + | ||
| 31 | 34 | class TestCurtsiesRepl(unittest.TestCase): | |
| 32 | 35 | ||
| 33 | 36 | def setUp(self): | |
| 34 | 37 | self.repl = create_repl() | |
| 35 | 38 | ||
| 39 | + def cfwp(self, source): | ||
| 40 | + return interpreter.code_finished_will_parse(source, self.repl.interp.compile) | ||
| 41 | + | ||
| 36 | 42 | def test_code_finished_will_parse(self): | |
| 37 | 43 | self.repl.buffer = ['1 + 1'] | |
| 38 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) | ||
| 44 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) | ||
| 39 | 45 | self.repl.buffer = ['def foo(x):'] | |
| 40 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (False, True)) | ||
| 46 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True)) | ||
| 41 | 47 | self.repl.buffer = ['def foo(x)'] | |
| 42 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) | ||
| 48 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) | ||
| 43 | 49 | self.repl.buffer = ['def foo(x):', 'return 1'] | |
| 44 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False)) | ||
| 50 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) | ||
| 45 | 51 | self.repl.buffer = ['def foo(x):', ' return 1'] | |
| 46 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) | ||
| 52 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) | ||
| 47 | 53 | self.repl.buffer = ['def foo(x):', ' return 1', ''] | |
| 48 | - self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True)) | ||
| 54 | + self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) | ||
| 49 | 55 | ||
| 50 | 56 | def test_external_communication(self): | |
| 51 | 57 | self.assertEqual(type(self.repl.help_text()), type(b'')) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,6 @@ | |||
| 1 | 1 | import unittest | |
| 2 | 2 | ||
| 3 | 3 | from bpython.curtsiesfrontend import interpreter | |
| 4 | - from bpython.curtsiesfrontend.repl import bad_empty_lines_removed | ||
| 5 | 4 | from curtsies.fmtfuncs import * | |
| 6 | 5 | ||
| 7 | 6 | class TestInterpreter(unittest.TestCase): | |
@@ -42,14 +41,3 @@ def g(): | |||
| 42 | 41 | self.assertEquals(str(plain('').join(a)), str(expected)) | |
| 43 | 42 | self.assertEquals(plain('').join(a), expected) | |
| 44 | 43 | ||
| 45 | - class TestPreprocessing(unittest.TestCase): | ||
| 46 | - def test_bad_empty_lines_removed(self): | ||
| 47 | - self.assertEqual(bad_empty_lines_removed("def foo():\n" | ||
| 48 | - " return 1\n" | ||
| 49 | - "\n" | ||
| 50 | - " pass\n"), | ||
| 51 | - "def foo():\n" | ||
| 52 | - " return 1\n" | ||
| 53 | - " pass\n") | ||
| 54 | - | ||
| 55 | - | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,73 @@ | |||
| 1 | + from code import compile_command as compiler | ||
| 2 | + from functools import partial | ||
| 3 | + import difflib | ||
| 4 | + import inspect | ||
| 5 | + import re | ||
| 6 | + import unittest | ||
| 7 | + | ||
| 8 | + from bpython.curtsiesfrontend.interpreter import code_finished_will_parse | ||
| 9 | + from bpython.curtsiesfrontend.preprocess import indent_empty_lines | ||
| 10 | + | ||
| 11 | + from bpython.test.fodder import original as original, processed | ||
| 12 | + | ||
| 13 | + indent_empty = partial(indent_empty_lines, compiler=compiler) | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + def get_fodder_source(test_name): | ||
| 17 | + pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,) | ||
| 18 | + print repr(pattern) | ||
| 19 | + orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL) | ||
| 20 | + for module in [original, processed]] | ||
| 21 | + | ||
| 22 | + if not orig: | ||
| 23 | + raise ValueError("Can't locate test %s in original fodder file" % (test_name,)) | ||
| 24 | + if not xformed: | ||
| 25 | + raise ValueError("Can't locate test %s in processed fodder file" % (test_name,)) | ||
| 26 | + return orig.group(1), xformed.group(1) | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + class TestPreprocessing(unittest.TestCase): | ||
| 30 | + | ||
| 31 | + def assertCompiles(self, source): | ||
| 32 | + finished, parsable = code_finished_will_parse(source, compiler) | ||
| 33 | + return finished and parsable | ||
| 34 | + | ||
| 35 | + def test_indent_empty_lines_nops(self): | ||
| 36 | + self.assertEqual(indent_empty('hello'), 'hello') | ||
| 37 | + | ||
| 38 | + def assertShowWhitespaceEqual(self, a, b): | ||
| 39 | + self.assertEqual( | ||
| 40 | + indent_empty(a), b, | ||
| 41 | + ''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True), | ||
| 42 | + b.replace(' ', '~').splitlines(True), | ||
| 43 | + fromfile='original', | ||
| 44 | + tofile='processed', | ||
| 45 | + n=5))) | ||
| 46 | + | ||
| 47 | + def assertDefinitionIndented(self, obj): | ||
| 48 | + name = obj.__name__ | ||
| 49 | + obj2 = getattr(processed, name) | ||
| 50 | + orig = inspect.getsource(obj) | ||
| 51 | + xformed = inspect.getsource(obj2) | ||
| 52 | + self.assertShowWhitespaceEqual(indent_empty(orig), xformed) | ||
| 53 | + self.assertCompiles(xformed) | ||
| 54 | + | ||
| 55 | + def assertLinesIndented(self, test_name): | ||
| 56 | + orig, xformed = get_fodder_source(test_name) | ||
| 57 | + self.assertShowWhitespaceEqual(indent_empty(orig), xformed) | ||
| 58 | + self.assertCompiles(xformed) | ||
| 59 | + | ||
| 60 | + def assertIndented(self, obj_or_name): | ||
| 61 | + if isinstance(obj_or_name, str): | ||
| 62 | + self.assertLinesIndented(obj_or_name) | ||
| 63 | + else: | ||
| 64 | + self.assertDefinitionIndented(obj_or_name) | ||
| 65 | + | ||
| 66 | + def test_empty_line_between_methods(self): | ||
| 67 | + self.assertIndented(original.BlankLineBetweenMethods) | ||
| 68 | + | ||
| 69 | + def test_empty_line_within_class(self): | ||
| 70 | + self.assertIndented(original.BlankLineInFunction) | ||
| 71 | + | ||
| 72 | + def test_blank_lines_in_for_loop(self): | ||
| 73 | + self.assertIndented('blank_lines_in_for_loop') | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments