diff --git a/pudb/debugger.py b/pudb/debugger.py index 71541c8..4dc6802 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -7,31 +7,16 @@ import bdb import gc import os import sys +from functools import partial from types import TracebackType +from pudb.lowlevel import decode_lines from pudb.settings import load_config, save_config +from pudb.py3compat import PY3, raw_input, execfile + CONFIG = load_config() save_config(CONFIG) -from pudb.py3compat import PY3, raw_input, execfile -if PY3: - _next = "__next__" -else: - _next = "next" - -try: - from functools import partial -except ImportError: - def partial(func, *args, **keywords): - def newfunc(*fargs, **fkeywords): - newkeywords = keywords.copy() - newkeywords.update(fkeywords) - return func(*(args + fargs), **newkeywords) - newfunc.func = func - newfunc.args = args - newfunc.keywords = keywords - return newfunc - HELP_TEXT = """\ Welcome to PuDB, the Python Urwid debugger. ------------------------------------------- @@ -555,10 +540,7 @@ class FileSourceCodeProvider(SourceCodeProvider): self.file_name = debugger.canonic(file_name) def __eq__(self, other): - return ( - type(self) == type(other) - and - self.file_name == other.file_name) + return type(self) == type(other) and self.file_name == other.file_name def identifier(self): return self.file_name @@ -585,18 +567,7 @@ class FileSourceCodeProvider(SourceCodeProvider): try: from linecache import getlines lines = getlines(self.file_name) - - from pudb.lowlevel import detect_encoding - source_enc, _ = detect_encoding(getattr(iter(lines), _next)) - - decoded_lines = [] - for l in lines: - if hasattr(l, "decode"): - decoded_lines.append(l.decode(source_enc)) - else: - decoded_lines.append(l) - - return format_source(debugger_ui, decoded_lines, set(breakpoints)) + return format_source(debugger_ui, list(decode_lines(lines)), set(breakpoints)) except: from pudb.lowlevel import format_exception debugger_ui.message("Could not load source file '%s':\n\n%s" % ( @@ -631,22 +602,8 @@ class DirectSourceCodeProvider(SourceCodeProvider): def get_lines(self, debugger_ui): from pudb.source_view import format_source - lines = self.code.split("\n") - - from pudb.lowlevel import detect_encoding - source_enc, _ = detect_encoding(getattr(iter(lines), _next)) - - decoded_lines = [] - for i, l in enumerate(lines): - if hasattr(l, "decode"): - l = l.decode(source_enc) - - if i+1 < len(lines): - l += "\n" - - decoded_lines.append(l) - - return format_source(debugger_ui, decoded_lines, set()) + lines = self.code.splitlines(True) + return format_source(debugger_ui, list(decode_lines(lines)), set()) # }}} diff --git a/pudb/lowlevel.py b/pudb/lowlevel.py index 415b0cc..0f10004 100644 --- a/pudb/lowlevel.py +++ b/pudb/lowlevel.py @@ -86,7 +86,7 @@ def lookup_module(filename): # {{{ file encoding detection -# stolen from Python 3.1's tokenize.py, by Ka-Ping Yee +# the main idea stolen from Python 3.1's tokenize.py, by Ka-Ping Yee import re cookie_re = re.compile("^\s*#.*coding[:=]\s*([-\w.]+)") @@ -95,13 +95,13 @@ if PY3: BOM_UTF8 = BOM_UTF8.decode() -def detect_encoding(readline): +def detect_encoding(lines): """ The detect_encoding() function is used to detect the encoding that should - be used to decode a Python source file. It requires one argment, readline, - in the same way as the tokenize() generator. + be used to decode a Python source file. It requires one argment, lines, + iterable lines stream. - It will call readline a maximum of twice, and return the encoding used + It will read a maximum of two lines, and return the encoding used (as a string) and a list of any lines (left as bytes) it has read in. @@ -113,11 +113,11 @@ def detect_encoding(readline): If no encoding is specified, then the default of 'utf-8' will be returned. """ bom_found = False - encoding = None + line_iterator = iter(lines) def read_or_stop(): try: - return readline() + return next(line_iterator) except StopIteration: return '' @@ -166,6 +166,15 @@ def detect_encoding(readline): return 'utf-8', [first, second] + +def decode_lines(lines): + source_enc, _ = detect_encoding(lines) + + for line in lines: + if hasattr(line, "decode"): + yield line.decode(source_enc) + else: + yield line # }}} diff --git a/test/test_lowlevel.py b/test/test_lowlevel.py new file mode 100644 index 0000000..b7c2f3d --- /dev/null +++ b/test/test_lowlevel.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from pudb.lowlevel import detect_encoding, decode_lines +from pudb.py3compat import PY3 + + +def test_detect_encoding_nocookie(): + lines = ['Test Проверка'] + encoding, _ = detect_encoding(lines) + assert encoding == 'utf-8' + + +def test_detect_encoding_cookie(): + lines = [ + '# coding=utf-8', + 'Test', + 'Проверка' + ] + encoding, _ = detect_encoding(lines) + assert encoding == 'utf-8' + + +def test_decode_lines(): + lines = [ + '# coding=utf-8', + 'Test', + 'Проверка', + ] + if PY3: + assert lines == list(decode_lines(lines)) + else: + assert [l.decode('utf-8') for l in lines] == list(decode_lines(lines)) diff --git a/test/test_source_code_providers.py b/test/test_source_code_providers.py new file mode 100644 index 0000000..b503fb8 --- /dev/null +++ b/test/test_source_code_providers.py @@ -0,0 +1,36 @@ +import pytest # noqa: F401 + +from pudb.debugger import NullSourceCodeProvider, FileSourceCodeProvider, DirectSourceCodeProvider +from pudb.source_view import SourceLine + + +class TestNullSourceCodeProvider: + def test_get_lines(self, mocker): + provider = NullSourceCodeProvider() + result = provider.get_lines(mocker.Mock()) + assert len(result) == 10 + assert isinstance(result[0], SourceLine) + + +class TestFileSourceCodeProvider: + def test_string_file_name(self, mocker): + mock_debugger = mocker.Mock() + mock_debugger.canonic = mocker.Mock(return_value='') + provider = FileSourceCodeProvider(mock_debugger, 'test file name') + result = provider.get_lines(mocker.MagicMock()) + assert len(result) == 1 + assert isinstance(result[0], SourceLine) + + def test_get_lines(self, mocker): + provider = FileSourceCodeProvider(mocker.Mock(), 'test file name') + result = provider.get_lines(mocker.MagicMock()) + assert len(result) == 1 + assert isinstance(result[0], SourceLine) + + +class TestDirectSourceCodeProvider: + def test_get_lines(self, mocker): + provider = DirectSourceCodeProvider(mocker.Mock(), 'test code') + result = provider.get_lines(mocker.Mock()) + assert len(result) == 1 + assert isinstance(result[0], SourceLine)